Skip to content

fix(0.4.0): G9 injection — Codex round-1 blockers#33

Merged
himerus merged 6 commits intomainfrom
fix/0.4.0-g9-injection-blockers
Apr 19, 2026
Merged

fix(0.4.0): G9 injection — Codex round-1 blockers#33
himerus merged 6 commits intomainfrom
fix/0.4.0-g9-injection-blockers

Conversation

@himerus
Copy link
Copy Markdown
Contributor

@himerus himerus commented Apr 19, 2026

Summary

Addresses four post-merge Codex adversarial-review blockers on PR #25 (G9 three-tier injection classifier). Patch-level bump targeted for the 0.4.0 line so rea can ship with the injection middleware behaving as advertised.

Closes the round-1 + round-2 Codex concerns on that classifier in a single follow-up PR:

  • [high] denyOnSuspicious defaulted to false on action: 'block' + flag unset, silently loosening 0.2.x injection_detection: block behavior for non-bst consumers upgrading to 0.3.0. Middleware now defaults to true in that case, restoring 0.2.x parity. The zod schema no longer applies a default for suspicious_blocks_writes, so absence is distinguishable from an explicit false. Consumers who want the looser warn-only posture must opt in via injection.suspicious_blocks_writes: false. bst-internal* profiles continue to pin true.
  • [high] The 7-phrase ASCII pattern library was trivially bypassed by Unicode whitespace (NBSP, en/em/ideographic space), zero-width joiners, and fullwidth compatibility characters. Inputs are now NFKC-normalized, zero-width-stripped, Unicode-whitespace-collapsed, and lowercased before literal matching. The phrase library was also expanded with two narrow persona-swap vectors (pretend you are, roleplay as). Broader candidates (act as a / act as an / pretend to be) were considered but dropped — at read tier a single literal match escalates to likely_injection, which would deny benign prose like "this proxy can act as a bridge." Pattern-set extensibility via policy is filed as G9.1 follow-up.
  • [medium] decodeBase64Strings was exported and tested but never wired into the middleware execution path — 28 lines of dead code advertised as a second-opinion base64 probe. It is now invoked after the primary scan; any phrase detected in a decoded whole-string payload is merged into base64DecodedMatches and triggers classification rule chore(ci)(deps): bump actions/setup-node from 4.0.3 to 6.3.0 #2 (likely_injection).
  • [low] On worker-bounded regex timeout, the audit record carried timing metadata under injection.regex_timeout but no verdict field under injection. A new verdict: 'error' value is emitted when a timeout produces no actionable signal, giving downstream audit consumers a stable record shape. A new InjectionMetadataSchema zod schema is added to the injection middleware module for internal test coverage; promoting it to a public package entrypoint is tracked as G9.2 follow-up (the module is not reachable through the current exports map).

Behavior change

Non-bst consumers with injection_detection: block will now block on suspicious classifications by default, restoring 0.2.x parity. This is a narrow tightening for consumers who upgraded to 0.3.0 without adding the new injection: block. To restore the 0.3.0 looser behavior, opt out explicitly:

injection:
  suspicious_blocks_writes: false

likely_injection continues to deny unconditionally in all configurations.

Follow-ups filed

  • G9.1 — policy-driven extensibility of the injection phrase library
  • G9.2 — promote InjectionMetadataSchema to a public entrypoint for downstream audit-consumer validation (Helix, etc.)

Test plan

  • pnpm lint — clean (including lint:regex safe-regex check)
  • pnpm type-check — strict, 0 errors
  • pnpm test — 440 passed / 1 skipped (53 injection-specific tests, 2 new regression guards against the dropped broad patterns)
  • pnpm build — clean tsc -p tsconfig.build.json
  • /codex-review round-1 blockers addressed in follow-up commit (narrowed persona-swap patterns, schema scope clarified)
  • Changeset: .changeset/fix-0.4.0-g9-injection.md (patch bump)
  • All commits DCO-signed
  • CI status checks green on PR
  • CODEOWNERS review on protected-path diff (src/gateway/middleware/)

Files touched

  • src/gateway/middleware/injection.tsnormalizeForMatch, decodeBase64Strings wiring, verdict: 'error', InjectionMetadataSchema, narrowed phrase library, denyOnSuspicious tri-state default
  • src/gateway/middleware/injection.test.ts — 4 new describe blocks covering all 4 findings + 2 regression guards
  • src/policy/loader.ts — drop default on suspicious_blocks_writes (make presence observable)
  • src/policy/loader.test.ts — updated expectations for absence-preservation
  • src/policy/types.tssuspicious_blocks_writes?: boolean (optional)
  • src/gateway/server.ts — conditionally omit suspiciousBlocksWrites when unset (preserves tri-state to middleware under exactOptionalPropertyTypes)
  • .changeset/fix-0.4.0-g9-injection.md — patch changeset

Jake Strawn added 6 commits April 19, 2026 00:07
Address four post-merge Codex findings on PR #25 (G9):
- denyOnSuspicious silently loosened 'block' action for non-bst consumers
- 7 ASCII pattern library bypassed by NBSP/zero-width/Unicode space
- decodeBase64Strings exported but never wired into middleware path
- regex-timeout records lacked stable verdict field

Audit entry: .rea/audit.jsonl hash 44281f39

Refs: #25
Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
…odex round-1)

Codex adversarial review of the G9 blocker-patch (commit c1c79b6) flagged
two follow-ups:

- [P1] act as a / act as an patterns were too broad. At read tier a single
  literal match escalates to likely_injection (always deny), so benign
  prose such as "this proxy can act as a bridge" or "the service can act
  as an intermediary" would be falsely denied. Dropped those phrases and
  also pretend to be. The remaining additions (pretend you are, roleplay
  as) retain second-person / explicit-roleplay framing which is rare in
  ordinary documentation. Added two regression guards in
  injection.test.ts asserting the dropped phrases do NOT match in benign
  contexts.
- [P3] InjectionMetadataSchema was advertised as "exported for external
  consumer validation" but the module is not in the published package's
  exports map (only ., ./policy, ./middleware, ./audit are public).
  Downgraded the docstring to flag it as internal-only today, with a G9.2
  follow-up to promote it to a public entrypoint. Updated the changeset
  body to match.

Quality gates: pnpm lint / type-check / test / build all green.
Injection suite now 53 tests (2 new regression guards).

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
…t in block mode

Replace hand-enumerated ZERO_WIDTH_RE with \p{Default_Ignorable_Code_Point}/gu
(Unicode property class, Node 22+) to cover all invisible bypass vectors including
soft hyphen, BIDI isolation controls, variation selector-16, and combining grapheme
joiner that the old regex missed.

In createInjectionMiddleware, the scanTimedOut branch now fails closed when
action === 'block': a timeout under block policy denies the request rather than
allowing it through with verdict:'error'.

Replace flaky best-effort timeout tests (wrapped in 'if (meta !== undefined)')
with deterministic tests that mock wrapRegex via vi.mock to always fire onTimeout,
unconditionally asserting warn-mode fail-open and block-mode fail-closed behavior.

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
…0.3.x suspicious default

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
…ault-behavior doc

Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
Signed-off-by: Jake Strawn <bandy.strawn@clarityhouse.press>
@himerus himerus marked this pull request as ready for review April 19, 2026 12:15
@himerus himerus merged commit a5cca2a into main Apr 19, 2026
8 checks passed
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