Fix the open security issues: web-ext 10, scopes, shared copies from chromium a1967a610, forget token #17

Merged
tiagoagueda merged 2 commits from sec2-fixes into main 2026-10-19 09:50:42 +00:00
Owner

What and why

#13 (SEC2-053) dev chain. web-ext 8.10.0 -> 10.8.0, npm audit fix: 13 advisories (3 critical) become 1, node-forge (high, GHSA-86w9-cpqp-85rv), which has no fixed release and is reached only through web-ext's Android bridge, not by lint or build. scripts/audit.mjs (the script postulo-chromium runs) fails CI on any other high or critical advisory and names that one; CI runs it after npm ci (npm run audit). Plain npm audit --audit-level=high would be red today for something nobody can fix.

#14 (SEC2-085) scopes. The settings no longer say "captures and nothing more". The extension names the two scopes it can use (src/lib/scopes.js): captures and listings:bind; the hint says to tick only the ones the person wants it to use; Test warns (error style) when the token holds any other scope ("can do more than this extension needs (read, ...)") or neither of ours; README names both. Taken as the issue's second branch in spirit (one token, either or both scopes) without choosing for the user, because neither call exists yet (#1, #9 not built).

#15 (SEC2-086) shared copies. shared.json ref 7998de37 -> postulo-chromium a1967a61052d11996100acad860356b7abf6b419; languages.js and messages.js added to the shared files (the new i18n.js imports them). api.js now throws Refusal("notPostulo") for a 2xx that is not JSON (test: test/token.test.mjs). English sentences the new shared code asks for (notPostulo, noCredentialsInAddress, storageFull) added, worded for Thunderbird (the "no sentence speaks of a browser" test passes).

#16 (SEC2-087) token at rest. README ("Privacy") and the options page say the token is stored unencrypted, Thunderbird's primary password does not cover it, and it works until revoked in Postulo. New Forget token button removes token from storage.local (address kept); tested against a storage stub that no token key is left.

Decision for the user (the core is not edited)

postulo/docs/THREAT-MODEL.md:21, the row "Someone with the token of a mail client", says its token holds listings:bind and nothing else. This extension will also use captures for #1 (and #9's recommendation is one token with both). The extension is now honest about both scopes and warns when a token is wider than them, but the row and the extension agree only if you either change the row to "listings:bind, and captures once the extension captures" with the union of the worst cases, or decide on two tokens (one per scope), which needs a code change when #1 and #9 land. I did not pick; nothing in the extension calls either scope today.

Translations

Weblate-only rule kept: only English was written. fr and pt-PT lose tokenHint and lacksCaptures (removal is allowed by the guard) and lack the new sentences, so the build leaves them out of the package until Weblate has filled them (scripts/build.mjs warns). The next Weblate sync should cover: tokenHintScopes, tokenStored, tokenNeither, tokenTooWide, forgetToken, tokenForgotten, notPostulo, noCredentialsInAddress, storageFull.

Tests

npm test: 31 pass (25 before; 6 new in test/token.test.mjs, the scope-name test in test/i18n.test.mjs rewritten). node scripts/guard.mjs, shared:check and npm run lint pass (0 errors; 2 warnings: messagesRead, known, and web-ext 10's new data_collection_permissions notice, left alone: declaring "none" would be false once capturing exists). Not proven here, only in a real Thunderbird: that the options page renders the new paragraphs and the Forget button works against the real storage.local, that Test shows the warning against a real token, and that the build with web-ext 10 installs.

Closes #13, #14, #15, #16

🤖 Generated with Claude Code

## What and why **#13 (SEC2-053) dev chain.** `web-ext` 8.10.0 -> 10.8.0, `npm audit fix`: 13 advisories (3 critical) become 1, `node-forge` (high, GHSA-86w9-cpqp-85rv), which has **no fixed release** and is reached only through web-ext's Android bridge, not by lint or build. `scripts/audit.mjs` (the script postulo-chromium runs) fails CI on any other high or critical advisory and names that one; CI runs it after `npm ci` (`npm run audit`). Plain `npm audit --audit-level=high` would be red today for something nobody can fix. **#14 (SEC2-085) scopes.** The settings no longer say "`captures` and nothing more". The extension names the two scopes it can use (`src/lib/scopes.js`): `captures` and `listings:bind`; the hint says to tick only the ones the person wants it to use; *Test* warns (error style) when the token holds any other scope ("can do more than this extension needs (read, ...)") or neither of ours; README names both. Taken as the issue's second branch in spirit (one token, either or both scopes) without choosing for the user, because neither call exists yet (#1, #9 not built). **#15 (SEC2-086) shared copies.** `shared.json` ref `7998de37` -> postulo-chromium `a1967a61052d11996100acad860356b7abf6b419`; `languages.js` and `messages.js` added to the shared files (the new `i18n.js` imports them). `api.js` now throws `Refusal("notPostulo")` for a 2xx that is not JSON (test: `test/token.test.mjs`). English sentences the new shared code asks for (`notPostulo`, `noCredentialsInAddress`, `storageFull`) added, worded for Thunderbird (the "no sentence speaks of a browser" test passes). **#16 (SEC2-087) token at rest.** README ("Privacy") and the options page say the token is stored unencrypted, Thunderbird's primary password does not cover it, and it works until revoked in Postulo. New **Forget token** button removes `token` from `storage.local` (address kept); tested against a storage stub that no `token` key is left. ## Decision for the user (the core is not edited) `postulo/docs/THREAT-MODEL.md:21`, the row "Someone with the token of a mail client", says its token holds `listings:bind` **and nothing else**. This extension will also use `captures` for #1 (and #9's recommendation is one token with both). The extension is now honest about both scopes and warns when a token is wider than them, but the row and the extension agree only if you either change the row to "`listings:bind`, and `captures` once the extension captures" with the union of the worst cases, or decide on two tokens (one per scope), which needs a code change when #1 and #9 land. I did not pick; nothing in the extension calls either scope today. ## Translations Weblate-only rule kept: only English was written. `fr` and `pt-PT` lose `tokenHint` and `lacksCaptures` (removal is allowed by the guard) and lack the new sentences, so the build leaves them out of the package until Weblate has filled them (`scripts/build.mjs` warns). The next Weblate sync should cover: tokenHintScopes, tokenStored, tokenNeither, tokenTooWide, forgetToken, tokenForgotten, notPostulo, noCredentialsInAddress, storageFull. ## Tests `npm test`: 31 pass (25 before; 6 new in `test/token.test.mjs`, the scope-name test in `test/i18n.test.mjs` rewritten). `node scripts/guard.mjs`, `shared:check` and `npm run lint` pass (0 errors; 2 warnings: `messagesRead`, known, and web-ext 10's new `data_collection_permissions` notice, left alone: declaring "none" would be false once capturing exists). Not proven here, only in a real Thunderbird: that the options page renders the new paragraphs and the Forget button works against the real `storage.local`, that *Test* shows the warning against a real token, and that the build with web-ext 10 installs. Closes #13, #14, #15, #16 🤖 Generated with [Claude Code](https://claude.com/claude-code)
web-ext 8.10.0 carried 13 advisories (3 critical). web-ext 10.8.0 and an
`npm audit fix` leave one: node-forge (high), which has no fixed release and is
reached only through web-ext's Android bridge, never by lint or build.
scripts/audit.mjs, the same script postulo-chromium runs, fails CI on any other
high or critical advisory and names that one with a reason and a day to look
again.

Closes #13

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
#14: the settings told the person to make a token with `captures` alone, and
Test judged it by that, while the core's threat model gives a mail client
`listings:bind` and nothing else, and #9 will want both. The extension now
names the two scopes it can use (src/lib/scopes.js: captures to capture,
listings:bind to file a message), asks for only the ones the person wants it
to use, and Test warns, in the error style, when the token holds any other
scope ("can do more than this extension needs") or neither of ours. README
says the same. The threat-model row is not edited here: see the pull request.

#15: shared.json moves from 7998de37 to postulo-chromium a1967a610 and now also
lists languages.js and messages.js, which the new i18n.js imports. api.js now
answers a 200 that is not JSON with Refusal("notPostulo") (tested), refuses
redirects with the token, bounds the address and sets Idempotency-Key. The
sentences api.js and i18n.js ask for (notPostulo, noCredentialsInAddress,
storageFull) are added to the English catalogue, worded for Thunderbird; fr and
pt-PT are Weblate's to fill, and the build leaves a catalogue out until they are.

#16: no WebExtension API encrypts storage, so the README and the settings page
say the token is kept unencrypted, that Thunderbird's primary password does not
cover it and that it works until revoked in Postulo, and a Forget token button
removes it from storage.local (tested against a stub of storage).

fr and pt-PT lose the two sentences (tokenHint, lacksCaptures) that the new ones
replace, which the translation guard allows.

Closes #14
Closes #15
Closes #16

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo-thunderbird!17
No description provided.