From 7b8e295647092a806484d4aa22e5a5dd77479f33 Mon Sep 17 00:00:00 2001 From: Codeman maintainer Date: Wed, 4 Nov 2026 16:51:56 +0100 Subject: [PATCH] fix: retire the OpenCode Console breach notice A new install has no seen list, so every new OpenCode user was still warned about an October incident that is long over. The mechanism stays for the next notice; an empty list is now a valid file, and the validators keep running over whatever it holds. Co-Authored-By: Claude Opus 5.5 --- docs/releasing.md | 2 +- src/web/public/security-notices.json | 22 +--------------------- test/security-notices.test.ts | 23 +++++------------------ 3 files changed, 7 insertions(+), 40 deletions(-) diff --git a/docs/releasing.md b/docs/releasing.md index fc1f7fe5..097a03a9 100644 --- a/docs/releasing.md +++ b/docs/releasing.md @@ -59,4 +59,4 @@ A security notice is a sticky warning card about something outside Codeman that - `title` (optional) is the card's bold line; keep it short. `link.url` must be https. `i18n` is optional; a missing string falls back to the English one. - Run `npx prettier --write src/web/public/security-notices.json` and `npm test -- test/security-notices.test.ts`, which validates the file (unique ids, real dates, https links, every `cli` a stock registry id, a zh-CN title wherever there is an English one). -**Retiring one**: let it expire, or delete the entry once it has. Removing it early stops it on the next release only; browsers already showing it keep the card until they close it or reload. +**Retiring one**: let it expire, or delete the entry once it has. Removing it early stops it on the next release only; browsers already showing it keep the card until they close it or reload. Until then a fresh install gets it too, because a new browser has no seen list, so give an `expires` no later than the warning stays useful. An empty list (`{ "notices": [] }`) is a valid file. diff --git a/src/web/public/security-notices.json b/src/web/public/security-notices.json index 13402a07..b6ec72f1 100644 --- a/src/web/public/security-notices.json +++ b/src/web/public/security-notices.json @@ -1,21 +1 @@ -{ - "notices": [ - { - "id": "opencode-console-breach-2026-10", - "severity": "warning", - "cli": "opencode", - "published": "2026-10-17", - "expires": "2026-12-31", - "title": "OpenCode Console breach: check your API keys", - "message": "OpenCode reported unauthorized access to a legacy system on 17 October. Provider API keys (OpenAI, Anthropic and others) saved in some OpenCode Console workspaces may have been exposed. If OpenCode emailed you about it, revoke and replace those keys and check their recent usage. If you got no email, OpenCode says you were not affected.", - "link": { "label": "Read OpenCode's notice", "url": "https://x.com/opencode/status/2111519637049319818" }, - "i18n": { - "zh-CN": { - "title": "OpenCode Console 遭入侵:请检查你的 API 密钥", - "message": "OpenCode 于 10 月 17 日报告,其一个旧系统遭到未授权访问,部分用户保存在 OpenCode Console 工作区中的提供商 API 密钥(OpenAI、Anthropic 等)可能已泄露。如果你收到了 OpenCode 的相关邮件,请撤销并更换这些密钥,同时检查近期用量。OpenCode 表示,没有收到邮件的用户不受影响。", - "label": "查看 OpenCode 公告" - } - } - } - ] -} +{ "notices": [] } diff --git a/test/security-notices.test.ts b/test/security-notices.test.ts index bc099517..236e5866 100644 --- a/test/security-notices.test.ts +++ b/test/security-notices.test.ts @@ -333,8 +333,7 @@ describe('the shipped security-notices.json', () => { const nonBlank = (value: unknown) => typeof value === 'string' && value.trim().length > 0; it('is a list of notices with unique, stable ids', () => { - expect(Array.isArray(notices)).toBe(true); - expect(notices.length).toBeGreaterThan(0); + expect(Array.isArray(notices)).toBe(true); // empty is fine: every notice has been retired const all = notices.map((n) => n.id); expect(new Set(all).size).toBe(all.length); for (const id of all) expect(id).toMatch(/^[a-z0-9][a-z0-9-]*$/); @@ -383,22 +382,10 @@ describe('the shipped security-notices.json', () => { } }); - it('shows the OpenCode notice, with its title, only to OpenCode users', () => { - const breach = notices.find((n) => n.id === 'opencode-console-breach-2026-10'); - expect(breach?.cli).toBe('opencode'); - const [shown] = core.select(notices, { available: { opencode: true }, now: NOW }); - expect(shown).toMatchObject({ - id: 'opencode-console-breach-2026-10', - title: 'OpenCode Console breach: check your API keys', - label: "Read OpenCode's notice", - url: 'https://x.com/opencode/status/2111519637049319818', - }); - expect(core.select(notices, { available: { opencode: true }, now: NOW, lang: 'zh-CN' })[0].title).toBe( - 'OpenCode Console 遭入侵:请检查你的 API 密钥' - ); - expect(core.select(notices, { available: { opencode: false, claude: true }, now: NOW })).toEqual([]); - expect(core.select(notices, { available: {}, now: NOW })).toEqual([]); - expect(core.select(notices, { available: { opencode: true }, now: at(2027, 1, 1) })).toEqual([]); + it('no longer ships the retired OpenCode Console notice, even to a fresh OpenCode install', () => { + // It stayed in the file past its use, and a new install has no seen list, so everyone got it. + expect(notices.map((n) => n.id)).not.toContain('opencode-console-breach-2026-10'); + expect(core.select(notices, { available: { opencode: true }, seen: [], now: NOW })).toEqual([]); }); it('is fetched by the page and never generated by the release script', () => {