SEC2-086: Shared copies are 14 postulo-chromium commits behind, missing the non-JSON-answer and request-hardening fixes to api.js #15

Closed
opened 2026-10-17 10:40:23 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-10-16 in a read-only security review of postulo-thunderbird at 4a10c95, checked by a second, independent pass.

Severity: Informational · Category: Dependencies · Reference: CWE-1104 (Use of Unmaintained Third Party Components); OWASP A06:2021 (Vulnerable and Outdated Components)

Location:

  • shared.json:3 ("ref": "7998de37...")
  • src/lib/api.js:71-82 (readResponse)
  • src/options.js:60-62

Issue

The copies of src/lib/api.js, src/lib/i18n.js, src/style.css and the five icons are
byte-identical (after the eol=lf normalisation .gitattributes and scripts/shared.mjs apply)
to postulo-chromium at the pinned commit 7998de3, so there is no drift in the sense the guard
tests for. The pin itself is stale: postulo-chromium has 14 later commits touching the shared
files (git log 7998de3..b6c1009 -- src/lib/api.js src/lib/i18n.js src/style.css), among them:

  • 2d04678 "Say translated sentences for an unscriptable page and a non-JSON answer": the current
    chromium readResponse throws Refusal("notPostulo") when a 2xx answer is not a JSON object.
    The copy here still does

    try {
      body = await response.json();
    } catch {
      body = null;
    }
    if (response.ok) {
      return body;
    }
    

    so a sign-in page, a reverse proxy's landing page or a captive portal answering 200 text/html
    at /api/v1/me makes whoAmI resolve to null, and options.js:61 (me.scopes) throws a
    TypeError whose engine-written message is what explain() shows, instead of a sentence from
    the catalogue.

  • 41f1e60 (an Idempotency-Key on every capture POST) and b7c8e78 (addresses kept within
    Postulo's limits, /captures/known asked in chunks): request hardening the capture feature (#1)
    will need and would otherwise re-learn.

  • 2d04678/d5f30bd add Accept-Language and a chosen-language catalogue; chromium's i18n.js
    now imports ./languages.js and ./messages.js, which this repository does not have, so
    npm run shared:sync is no longer a plain copy but a small migration (what #7 and #8 are for).

Impact

No exposure today: the only request the skeleton makes is GET /me on Test, and the worst
outcome is an unhelpful error message. The cost is that the next feature is built on a copy of
api.js that lacks fixes its source already has.

Fix

Re-pin: run npm run shared:sync against a postulo-chromium checkout at or after b6c1009, add
languages.js/messages.js to shared.json (or land #7/#8 first), and reword any new catalogue
key that speaks of a browser (the "no sentence speaks of a browser" test enforces it).

Done when shared.json's ref is at or after 2d04678 and a test in test/ feeds
readResponse a 200 response whose body is not JSON and asserts a Refusal with key
notPostulo is thrown.

#7 and #8 (open) are the features that bring languages.js and messages.js.
postulo/postulo-firefox#8 (closed) was the same kind of stale pin for the Firefox build.

Found on 2026-10-16 in a read-only security review of `postulo-thunderbird` at `4a10c95`, checked by a second, independent pass. **Severity:** Informational · **Category:** Dependencies · **Reference:** CWE-1104 (Use of Unmaintained Third Party Components); OWASP A06:2021 (Vulnerable and Outdated Components) **Location:** - `shared.json:3` (`"ref": "7998de37..."`) - `src/lib/api.js:71-82` (`readResponse`) - `src/options.js:60-62` ## Issue The copies of `src/lib/api.js`, `src/lib/i18n.js`, `src/style.css` and the five icons are byte-identical (after the `eol=lf` normalisation `.gitattributes` and `scripts/shared.mjs` apply) to postulo-chromium at the pinned commit `7998de3`, so there is no drift in the sense the guard tests for. The pin itself is stale: postulo-chromium has 14 later commits touching the shared files (`git log 7998de3..b6c1009 -- src/lib/api.js src/lib/i18n.js src/style.css`), among them: - `2d04678` "Say translated sentences for an unscriptable page and a non-JSON answer": the current chromium `readResponse` throws `Refusal("notPostulo")` when a 2xx answer is not a JSON object. The copy here still does ```js try { body = await response.json(); } catch { body = null; } if (response.ok) { return body; } ``` so a sign-in page, a reverse proxy's landing page or a captive portal answering `200 text/html` at `/api/v1/me` makes `whoAmI` resolve to `null`, and `options.js:61` (`me.scopes`) throws a `TypeError` whose engine-written message is what `explain()` shows, instead of a sentence from the catalogue. - `41f1e60` (an `Idempotency-Key` on every capture POST) and `b7c8e78` (addresses kept within Postulo's limits, `/captures/known` asked in chunks): request hardening the capture feature (#1) will need and would otherwise re-learn. - `2d04678`/`d5f30bd` add `Accept-Language` and a chosen-language catalogue; chromium's `i18n.js` now imports `./languages.js` and `./messages.js`, which this repository does not have, so `npm run shared:sync` is no longer a plain copy but a small migration (what #7 and #8 are for). ## Impact No exposure today: the only request the skeleton makes is `GET /me` on *Test*, and the worst outcome is an unhelpful error message. The cost is that the next feature is built on a copy of `api.js` that lacks fixes its source already has. ## Fix Re-pin: run `npm run shared:sync` against a postulo-chromium checkout at or after `b6c1009`, add `languages.js`/`messages.js` to `shared.json` (or land #7/#8 first), and reword any new catalogue key that speaks of a browser (the "no sentence speaks of a browser" test enforces it). Done when `shared.json`'s `ref` is at or after `2d04678` and a test in `test/` feeds `readResponse` a `200` response whose body is not JSON and asserts a `Refusal` with key `notPostulo` is thrown. ## Related #7 and #8 (open) are the features that bring `languages.js` and `messages.js`. postulo/postulo-firefox#8 (closed) was the same kind of stale pin for the Firefox build.
Sign in to join this conversation.
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#15
No description provided.