Skip to content

fix(security): audit fixes for secret redaction, error sanitization and HTTP hardening#14

Draft
JulienJBO wants to merge 6 commits into
mainfrom
codex/audit-reference-redaction
Draft

fix(security): audit fixes for secret redaction, error sanitization and HTTP hardening#14
JulienJBO wants to merge 6 commits into
mainfrom
codex/audit-reference-redaction

Conversation

@JulienJBO
Copy link
Copy Markdown
Contributor

@JulienJBO JulienJBO commented May 19, 2026

Résumé

Suite d'un audit ciblé de la surface sensible du serveur (rédaction de secrets, exécution op, transport HTTP, client Connect). Six correctifs atomiques sur codex/audit-reference-redaction, classés par sévérité décroissante.

# Commit Domaine Sévérité
1 bb7d7ee Redaction des références op:// case-insensitive dans l'audit Moyen
2 9b4a1cf Drop de Error.cause non rédigé dans normalizeError Moyen
3 09aa1d1 Flag i ajouté à redactExactValue pour matcher les casses mixtes Moyen
4 ad8ce51 sanitizeAuditValue traverse non-enumerable + Symbol keys via Reflect.ownKeys Moyen
5 e8f4341 decodeURIComponent try/catch + .normalize("NFC") sur les références Connect Faible
6 b0d8048 Validation du header Host (defense-in-depth DNS rebinding) Faible

Détail des bugs

1. Error.cause exfiltrable (9b4a1cf)

normalizeError propageait { cause: error } intact. Un secret présent dans la cause d'origine (URL fetch avec credentials, payload SDK 1Password contenant un token, etc.) traversait la rédaction. La cause est désormais détachée lors de la normalisation. Test dans errors.test.ts.

2. redactExactValue case-insensitive (09aa1d1)

La regex était construite sans flag i, contrairement à redactAuthText juste en dessous. Si secretRedactionValues contient "MyAPIKey" et que op produit myapikey (normalisation URL, message DNS), la valeur fuit. Flag passé à "gi".

3. sanitizeAuditValue superficielle (ad8ce51)

Utilisait Object.entries, qui ignore les propriétés non-énumérables et les clés Symbol. Plusieurs SDK (AWS, Octokit, undici) stockent des credentials sur ce type de propriétés. Itération désormais via Reflect.ownKeys + descripteur de propriété, avec gestion des getters qui throw.

4. Parsing Connect (e8f4341)

  • decodeURIComponent jetait un URIError brut sur les escapes malformés (%E0 sans byte de continuation). Enveloppé dans un try/catch qui renvoie une Error typée cohérente avec le reste du parseur.
  • equalsIdentifier ne normalisait pas l'Unicode. Un label café (NFC, U+00E9) ne matchait pas une requête café (NFD, e + combining acute). .normalize("NFC") ajouté des deux côtés.

5. Validation du Host header (b0d8048)

La défense contre DNS rebinding reposait uniquement sur Origin, qui n'est pas envoyé par les clients non-navigateur. Un processus local malveillant via DNS rebinding pouvait atteindre le socket sans déclencher le check. Ajout de assertHostAllowed qui valide le header Host contre l'adresse d'écoute (et les origines configurées). Test forcé via node:http request car fetch interdit de fixer Host.

Validation

  • npm run lint
  • npm test ✓ (147 tests, dont 6 nouveaux pour les correctifs)
  • npx tsc --noEmit

Hors scope

  • Le projet dépend de @1password/sdk@0.4.1-beta.1 (beta) — surveiller les release notes pour des nouvelles propriétés d'erreur non rédigées.
  • Connect host limité à localhost/127.0.0.1/::1 par config — si cette limite saute, refaire un audit SSRF/TLS complet.

JulienJBO added 6 commits May 19, 2026 22:09
normalizeError() previously wrapped non-Error values via `new Error(msg, {
cause: error })`. Node ≥ 16 prints the cause chain verbatim through
util.inspect / Error.toString, which can resurface unsanitized payloads
(1Password tokens, Connect URLs with credentials) downstream.

The wrapper now keeps only the redacted message. The top-level fatal
handler in index.ts is routed through errorMessage() for the same reason.
redactExactValue used a /g regex without the `i` flag, so a configured
secretRedactionValues entry like `MyAPIKey` would survive in `op` output
that happens to lowercase it (URL hosts, hex tokens, error messages).
Aligns with the /gi pattern already used by redactAuthText for
OP_SESSION / OP_SERVICE_ACCOUNT_TOKEN.
sanitizeAuditValue walked objects via Object.entries, which skips Symbol
keys and non-enumerable properties. Some SDKs (AWS, Octokit, undici
errors) hide credentials on non-enumerable property descriptors; those
would slip through audit redaction if such an object reached metadata or
error payloads.

Rewritten to traverse Reflect.ownKeys with explicit descriptor reads.
The function is now exported for direct testing.
Two related issues in parseSecretReference / equalsIdentifier:

* decodeURIComponent on a malformed escape (e.g. `%E0` without a
  continuation byte) raised a raw URIError that escaped the wrapper.
  Now caught and re-thrown as a typed Invalid secret reference error,
  consistent with the rest of the parser.

* Field title matching compared lowercase trims directly, so a stored
  NFC label (`café` = U+00E9) failed to match an NFD query (`café` =
  e + U+0301). macOS keyboards routinely emit NFD for accented Latin
  characters; both forms now normalize to NFC before comparison.
The DNS rebinding protection only relied on the Origin header, which
non-browser clients can omit entirely (assertOriginAllowed returns true
when Origin is absent). A local process reaching 127.0.0.1 via a rebound
DNS name would therefore bypass the check.

Adds an assertHostAllowed step that whitelists the configured listen
address (with localhost / 127.0.0.1 / [::1] aliases when bound to a
local host) plus any hosts explicitly added through httpAllowedOrigins.
Requests with a mismatched or missing Host header return 403.
@JulienJBO JulienJBO changed the title fix(security): redact secret references case-insensitively fix(security): audit fixes for secret redaction, error sanitization and HTTP hardening May 19, 2026
@JulienJBO
Copy link
Copy Markdown
Contributor Author

@codex

Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: b0d804899b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/http-server.ts
hosts.add(host);
}
} else {
hosts.add(`${config.httpHost}:${port}`.toLowerCase());
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Handle wildcard bind addresses when validating Host header

allowedHosts() adds only ${config.httpHost}:${port} for non-local binds, so with common deployment settings like --http-host=0.0.0.0 (or ::) every request is denied: clients send a concrete Host (service DNS/IP), never the wildcard bind address itself. In that mode the new Host check returns 403 for valid non-browser traffic unless operators repurpose httpAllowedOrigins as a Host allowlist, which breaks expected HTTP transport behavior.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant