Skip to content

fix: input hardening + silent-failure hygiene (AGENTS.md doctrine)#39

Merged
lesquel merged 2 commits into
mainfrom
fix/hardening-hygiene-batch
May 17, 2026
Merged

fix: input hardening + silent-failure hygiene (AGENTS.md doctrine)#39
lesquel merged 2 commits into
mainfrom
fix/hardening-hygiene-batch

Conversation

@lesquel

@lesquel lesquel commented May 17, 2026

Copy link
Copy Markdown
Owner

Summary

Resolves the hardening + silent-failure hygiene batch of #33 (Tier-3). One coherent PR (defensive input hardening + AGENTS.md §"No silent failures" compliance), not micro-PRs.

A — /fs/glob pattern traversal

globFiles validated cwd but not the glob pattern. An absolute /etc/* (or ../../etc/*) made Bun.Glob yield real filenames disclosed via the response path field. Now rejects pattern.includes("..") || isAbsolute(pattern) before constructing the glob; plus a post-glob containment check with a proper resolved + "/" segment boundary. (Feature gated behind PILOT_ENABLE_GLOB_OPENER, off by default — still a real disclosure.)

B — input length caps

  • Codex validators: requireString gained maxLen; session_id/turn_id/tool_use_id ≤128, tool_name/cwd/model ≤512, prompt ≤50000 — a single field can't consume the 1 MiB body and fan to every SSE client/audit.
  • validatePromptBody: per-element parts validation; ≤200 caps on model.*/agent; reject empty modelID. providerID: "" is accepted — the dashboard legitimately sends it for "no provider override"; rejecting it (the original spec) regressed the real send-prompt flow. The contract is now main-permissive on providerID, stricter only on the meaningful modelID.

C — silent swallows made observable (AGENTS.md doctrine)

Per-item fix-vs-comment decision: audit/log.ts disk-write surfaced via the app.log fallback; banner/writer.ts returns a result so the composition root logs a failed global banner write; dotenv warns on an unreadable existing .env (absent stays silent); audit/rotation.ts logs + bails early (no partial shift); dashboard.ts warns if a required asset is missing; remaining provably-irrelevant cleanup catches got accurate justifying comments.

Review trail (this is the one that needed two passes)

  • First independent fresh-context review: REQUEST CHANGES — 2 blockers: (1) absolute-pattern bypass in glob, (2) the providerID rejection regressed the dashboard's real model-switch send flow.
  • Both fixed with RED→GREEN proof tests (the tests fail on the buggy code, pass after the fix); the contract was re-derived from main + the actual dashboard call path.
  • Focused fresh re-review: APPROVE WITH NITS — both blockers confirmed closed, B2 success-shape proven equivalent to main for valid bodies, no new regression. The one remaining nit (an inaccurate "cross-platform" comment) is fixed in this PR.

Testing

  • +~45 tests incl. adversarial traversal vectors + the exact dashboard regression payload
  • bun test 669/0 · bunx tsc --noEmit clean
  • No any/class/console.log-in-server-path introduced

Independent off main. Touches server/index.ts (banner-result wiring) — open #35 also touches server/index.ts (lifecycle); regions differ → 3-way mergeable; sequence either way.

Refs #33 (Tier-3). With #34–38 + this, all six #33 PR groups are delivered.

lesquel added 2 commits May 17, 2026 14:34
Second-wave audit (#33 Tier-3) batch. Verified per item; security/regression
blockers from the first adversarial review fixed and re-reviewed.

A — /fs/glob traversal: globFiles validated cwd but not the glob `pattern`.
  Reject pattern containing `..` OR isAbsolute(pattern) before constructing
  Bun.Glob (an absolute `/etc/*` otherwise disclosed real filenames via the
  response `path` field). Belt-and-suspenders: post-glob containment check
  with a proper `resolved + "/"` segment boundary.

B — input length caps:
  - codex validators: requireString gained maxLen; session_id/turn_id/
    tool_use_id <=128, tool_name/cwd/model <=512, prompt <=50000 — a single
    field can no longer consume the 1 MiB body and fan to all SSE clients.
  - sessions validatePromptBody: per-element `parts` validation (non-null
    object with non-empty string `type`); <=200 caps on model.*/agent;
    reject empty modelID. providerID '' is ACCEPTED (the dashboard sends ''
    for "no provider override" — rejecting it regressed the real send-prompt
    flow; matches main's permissive contract, stricter only on modelID).

C — make silent swallows observable per AGENTS.md §"No silent failures":
  audit/log.ts disk-write failure now surfaced via the app.log fallback;
  banner/writer.ts returns a WriteBannerResult so the composition root logs
  a failed GLOBAL banner write (project write stays best-effort); dotenv
  warns when an EXISTING .env is unreadable (absent stays silent);
  audit/rotation.ts logs and bails early instead of a partial shift;
  dashboard.ts warns once if a required asset (main.js/styles.css/sw.js)
  is missing; remaining provably-irrelevant cleanup catches got accurate
  justifying comments (server shutdown, bus debug-trace, text stream
  teardown, filesystem statSync).

+~45 tests. bun test 669/0, tsc clean. Two independent fresh-context
reviews: first found 2 blockers (absolute-pattern bypass; validator
regressed the dashboard) — both fixed with RED->GREEN proof tests;
focused re-review APPROVE, blockers confirmed closed, no new regression.

Refs #33
@lesquel lesquel merged commit c575c96 into main May 17, 2026
2 checks passed
@lesquel lesquel deleted the fix/hardening-hygiene-batch branch May 17, 2026 20:14
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