feat(context-guard): per-session context snapshots, zone resolver + setup skill - #1241
Conversation
…lity Approved plan for porting the machine-local plugin-audit skill into the marketplace as two swim-lane plugins: context-guard (statusline tee -> per-session context snapshots + zones SSOT) and plugin-quality (post-use component audit skill with zone-informed dispatch and a draft+confirm gh-issue sink). Brief locked by /planning:interview 2026-07-23 (15 branches). Plan passed a fresh-context plan-review (11 findings applied) and a devils-advocate stress-test (14 findings; 5 MUST-fix applied). Design gate satisfied by design/design-resolution.md (interview early-exit). Next: /work-items:decompose into the two lanes; context-guard merges first (seams-first). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xecution shape Owner override at execution start (2026-07-24): both lanes authored in parallel worktrees this session; merges stay serialized (lane A first, lane B rebases). Records the split-brain guard (freeze reader-contract floor early, re-run drift grep post-merge) and expected trivial shared-file conflicts at lane-B rebase. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n snapshots (#1228) Transparent statusline wrapper on the rate-limit-guard tee pattern: byte-for-byte pass-through (stdout + exit code), bounded stdin read, jq-missing visible degrade, atomic temp+rename with bounded Windows rename retry, standalone minimal-statusline mode. Writes captured_at + session_id + the stdin context_window object verbatim to ~/.claude/context-guard/context/<session_id>.json per refresh. session_id sanitized to [A-Za-z0-9_-] before filename use (tee skipped otherwise — path containment). Stale sibling snapshots pruned on write (14-day cutoff, far above the reader contract's 10-minute staleness window; .tmp.* in-flight files never touched). Test-first: 40 assertions, sandboxed HOME, all green; shellcheck clean. Statusline schema re-verified against the official doc this session (all five Brief fields verbatim; null caveats for used_percentage / current_usage confirmed). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…en contract (#1234) context-zone.sh <session_id> prints exactly one of smart / acceptable / dumb / unknown. Shipped judgment defaults smart <= 50 < acceptable <= 75 < dumb over used_percentage (no official auto-compaction threshold is documented — re-verified 2026-07-24 against the costs and statusline pages). zones.json ({smart,acceptable}_max_used_percentage) is the operator tuning path; malformed/inverted/mistyped files fall back to shipped defaults with a visible stderr notice. Fail-open to unknown: absent/stale (>10 min)/unparsable snapshot, null or missing or out-of-range used_percentage, null/missing current_usage (early-session and post-/compact statusline states), hostile session id, missing jq. Exit code always 0 — the word is the contract. Empirical band-vs-compaction check recorded in topic notes: no auto-compact event exists in this machine's transcript history (largest session ran to 308k tokens uncompacted on a 1M-class window), so the dumb band's ordering margin is declared (>=15 points under a >=90% trigger assumption) with zones.json as the correction path. Test-first: 40 assertions green, sandboxed HOME; shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…very, compaction rule (#1229) Consumer-facing contract on the rate-limit-guard model: per-session snapshot path pattern, file shape (context_window verbatim + upstream null states), 10-minute staleness rule, fail-open capability table, zones.json shape + direct-read rule for cache-isolated consumers, untrusted-field warning, idle-session note, invariants (fixed seam path outside ${CLAUDE_PLUGIN_DATA}; no shipped Monitor config; fixed staleness constant). Load-bearing additions: (1) session-id discovery via the ${CLAUDE_SESSION_ID} skill substitution (re-verified 2026-07-24) with the no-guess conservative fallback; (2) inline-floor ownership + byte-identity rule (path pattern, staleness value, default bands — lane B carries the drift check); (3) zone-is-NOT-a-compaction- indicator rule citing the verified absence of any documented auto-compact threshold (2026-07-23, re-checked 2026-07-24) and the empirical no-event transcript observation with declared margin. These floor values are now FROZEN for the plugin-quality consumer. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ring detection (#1236) check (read-only): jq, statusline wiring with FOUR states — the stale-path detection compares the wired path against the resolved ${CLAUDE_PLUGIN_ROOT} so exists-but-outdated cache paths are caught, not just missing files; live-session snapshot freshness via ${CLAUDE_SESSION_ID} (with the unexpanded-substitution fallback); zones.json state; prints the exact operator settings.json edit (wrapped + standalone variants) — never writes settings; dotfiles reminder surfaced, never run. apply: scoped to the one plugin-schema-owned machine file — seeds or converges ~/.claude/context-guard/zones.json, idempotent, preserves unrecognized keys, reports what it wrote. Statusline wiring stays print-only (owner-approved EXEC-SHAPE decision). skill-quality gate: PASS (0 errors); evals validate structurally against the bundled schema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…entry, security review (#1230) Plugin README (capability, bash+jq prerequisites with the Windows Git Bash requirement, wiring flow, reader-contract pointer, security posture) and 0.1.0 CHANGELOG. Marketplace catalog entry (category claude-code) and repo README catalog row. Plugin-acceptance security review record in MIGRATION-PLAYBOOK: no hooks, no MCP, no userConfig, no egress; writes only ~/.claude/context-guard/ under the operator-home carve-out (documented cross-plugin seam, deliberately outside CLAUDE_PLUGIN_DATA); scripts reviewed with sanitized untrusted input. claude plugin validate plugins/context-guard: exit 0. claude plugin validate --strict .: exit 0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 5m 27s —— View job PR Review:
|
|
Claude finished @kyle-sexton's task in 5m 0s —— View job Security Review — PR #1241
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e5fe998745
ℹ️ 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".
…catalog row order (#1230) Set the executable bit (worktree + index) on the four shebang scripts; rewrap the PLAN.md continuation line that markdownlint parsed as a plus-marker list item; move the repo README catalog row to match the marketplace.json entry order the plugin gate compares against. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 47s —— View job PR Review:
|
|
Claude finished @kyle-sexton's task in 3m 44s —— View job Security Review — PR #1241
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5d34d8e1ee
ℹ️ 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".
…1230) The catalog rows are generator-owned (scripts/generate-catalog.mjs derives them from each plugin.json description); the hand-written row carried an extra clause the gate rejected. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 49s —— View job PR Review:
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5fdd119699
ℹ️ 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".
|
Claude finished @kyle-sexton's task in 3m 14s —— View job Security Review — PR #1241
|
Review-driven hardening (codex + claude review + security review on PR #1241), test-first where behavioral: - context-zone.sh: strict ISO-8601 format gate on captured_at BEFORE any date parsing (GNU date -d accepts "now"/relative phrases, which would let a forged snapshot defeat the staleness check), and the snapshot's embedded session_id must equal the requested id (a copied/renamed snapshot must not answer for another session). Two new red-green test cases; 44/44 green. - statusline-tee.sh: prune re-checks each candidate's mtime immediately before unlink (a sibling's atomic refresh between scan and delete no longer loses the new snapshot; residual microsecond race documented as fail-open + self-healing); temp names widen entropy ($RANDOM$RANDOM) and the write runs under noclobber so a pre-planted symlink or existing path refuses instead of following; 1 MiB truncation boundary documented in the header. - setup SKILL.md: shell-syntax guard for the printed wiring (inline env assignments/pipes get the sh -c wrapped variant — argv-only wrapping broke such statuslines); apply's merge path FAILs with remediation when jq is absent and zones.json exists. - reader contract + README + security record: untrusted-data rule restated precisely (validate format before lenient parsing; never execute), and the Windows chmod no-op integrity boundary stated honestly — zones are routing hints, never security inputs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 33s —— View job PR Review:
|
| Finding | Source | Status |
|---|---|---|
captured_at ISO-8601 format gate before date -d |
All 4 reviews (primary finding) | ✅ Fixed — context-zone.sh:81 |
Test cases for non-ISO captured_at ("now", "yesterday", "1 second ago") |
Multiple reviews | ✅ Added — context-zone.test.sh:115-119 |
| Session ID equality check in jq (copied/renamed snapshot must not answer) | Codex P2 (2nd round) | ✅ Fixed — context-zone.sh:66 |
| Test for session_id mismatch | Codex P2 | ✅ Added — context-zone.test.sh:121-125 |
| Pruning TOCTOU: per-file mtime re-check before unlink | Codex P2 (2nd round) | ✅ Fixed — statusline-tee.sh:113-117 |
| 1 MiB truncation boundary not documented | Codex P2 (3rd round) | ✅ Documented — statusline-tee.sh:67-70 |
Temp file: $RANDOM entropy + pre-planted symlink race |
Security reviews | ✅ Fixed — $RANDOM$RANDOM + set -o noclobber — statusline-tee.sh:122-128 |
SKILL.md: inline env-assignment wiring broken (THEME=dark cmd) |
Multiple reviews (P2) | ✅ Fixed — sh -c variant printed for shell-syntax commands — SKILL.md:88-100 |
SKILL.md apply: no jq guard for merge path when file exists |
Multiple reviews (P2) | ✅ Fixed — explicit FAIL + remediation — SKILL.md:132-135 |
| Reader contract "untrusted data" contradicted the implementation | Multiple reviews | ✅ Fixed — restated precisely ("validate format before lenient parser; never execute") — reader-contract.md:63-73 |
Windows chmod 700 integrity gap undocumented |
Security reviews | ✅ Documented in reader-contract, README, and MIGRATION-PLAYBOOK.md |
The updated test harness now runs 44 assertions in context-zone.test.sh (up from 40), all green per the commit message.
Remaining Minor Item
File: docs/MIGRATION-PLAYBOOK.md:812
The Code Execution (1) bullet in the security review record still reads:
"snapshot values are only ever passed through
jq, never interpolated into a shell"
This is imprecise: $ts is extracted from the snapshot via jq -r and then passed to date -d "$ts". The actual risk is now fully mitigated by the ISO-8601 format gate at context-zone.sh:81, and the reader contract and README both use accurate language ("validate format before lenient parsing; never execute"). But the security record's ACCEPT justification still contains the inaccurate claim, which the prior security review flagged as "Finding 3 — IMPORTANT / CONFIRMED."
The Data Egress bullet was correctly updated in 163a3a1; only the Code Execution bullet was not. A one-word adjustment ("and snapshot values are validated to their documented format before any typed parser (e.g. date -d for the ISO-8601 timestamp) and never passed to a code executor") would bring the record into alignment with everything else.
Fix the remaining imprecision →
Not a blocker.
Nit: apply reset trigger still implicit
File: skills/setup/SKILL.md:129
Step 2 says "ONLY if the operator asked for a reset" but doesn't define what constitutes asking for a reset (e.g., argument reset, the word "reset" in the request). evals.json eval #4 exercises the preserve-unrecognized-keys path but not the reset path. The LLM is left to interpolate. Not a correctness bug for the current scope, but noting it for the apply eval gap.
What Remains Solid
- Fail-open contract — every precondition failure in
context-zone.shcorrectly routes tounknown; all 44 test assertions cover the full failure mode surface including the two new forged-timestamp and session-id-mismatch cases. - TOCTOU mitigation is correct: the per-file
find "$f" -maxdepth 0 -mmin +20160re-check runs inside the-exec sh -cloop, so a file atomically refreshed between scan and delete is now spared. set -o noclobber+$RANDOM$RANDOM— both are additive hardening. The noclobber guard is the meaningful one (refuses to follow a pre-planted symlink); the widened entropy is defense-in-depth.- Shell-syntax guard in SKILL.md is the right approach: detect env-assignment/pipe/
;/&&patterns and emit thesh -c '...'variant, not the argv-only form. The detection scope is realistic. - jq guard in
applynow clearly distinguishes the two paths: file absent (no jq needed → write template) vs. file present (jq required for merge → FAIL with remediation). This is the correct gate. - Reader contract language now accurately describes the security posture: format-gate
captured_atbefore any date parsing, requiresession_idto match, never pass to code executors. The Windows limitation is stated honestly with the practical consequence (zone is a routing hint, not a security input). - CLAUDE.md compliance remains complete:
${CLAUDE_PLUGIN_ROOT}anchoring, no hardcoded paths, nouserConfig, versioned0.1.0,disable-model-invocation: trueon the write-capable skill.
|
Claude finished @kyle-sexton's task in 3m 36s —— View job Security Review — PR #1241
|
|
Review + security-review findings triaged — classification and disposition (fixes in
Tests: 44/44 (zone) + 40/40 (tee), shellcheck clean. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 163a3a19e0
ℹ️ 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".
…-byte passthrough (#1230) The single bounded read capped the wrapped command's stdin at 1MiB, violating the byte-for-byte contract for oversized payloads (codex review finding). The read now loops 1MiB blocks until EOF with the per-block 5s timeout still bounding a stalled pipe; the documented boundary narrows to the stalled-pipe case only. Red-green test: a 1.5MB payload reaches the wrapped command byte-complete and still tees. 42/42 green; shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 17s —— View job PR Review:
|
| Finding | Fix |
|---|---|
captured_at non-ISO bypasses staleness check (date -d "now") |
ISO-8601 regex gate at context-zone.sh:81; red-green tests for "now", "yesterday", "1 second ago" |
Snapshot session_id not checked against requested id |
jq equality check at context-zone.sh:66; mismatch → unknown; test at line 122 |
| Inline env-assignment statuslines break direct argv wiring | Shell-syntax guard added at SKILL.md:88-100 |
apply merge path undefined when jq absent and zones.json exists |
Explicit FAIL + install remediation at SKILL.md:132-134 |
| Prune TOCTOU could delete a concurrently refreshed sibling | Per-file mtime re-check before unlink at statusline-tee.sh:121-124 |
$RANDOM temp-name entropy + symlink follow via pre-planted path |
$RANDOM$RANDOM widens to 30 bits; noclobber refuses pre-existing paths at statusline-tee.sh:129-138 |
| 1 MiB stdin cap silently truncated wrapped command's input | Loop-drain reads until EOF in 1 MiB blocks at statusline-tee.sh:73-82; Case 15b test for >1 MiB round-trip |
Reader contract wording contradicted date -d "$ts" |
Contract now says "validate format BEFORE handing to a lenient parser" and "never pass to anything that executes them (eval, sh -c)" — accurate at reader-contract.md:63-67 |
Windows chmod 700 scope limitation undocumented |
Reader contract, README, and playbook record (lines 826-831) now explicitly surface the Windows integrity caveat |
What's solid at HEAD
- Fail-open is airtight. Every precondition failure in
context-zone.shroutes tounknown; every tee failure returns 0.set +o pipefailis correctly scoped to the passthrough pipeline only. - Path containment is thorough.
[A-Za-z0-9_-]+$enforced on both write and read sides; session-id mismatch between embedded and requested id now also caught. - Test coverage is comprehensive. 40 tee assertions (including Case 15b for >1 MiB payload), 44 zone assertions (including
"now"/"yesterday"rejection and session-id impersonation). Themv-shim pattern for deterministic Windows locked-target simulation remains well-crafted. - Float-safe band comparison via
awkis correct;50.5is tested. - CLAUDE.md compliance is complete: repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented and justified, security review updated.
One P2 to address before merge (single-quote escaping in the sh -c template in SKILL.md); the stale claim in MIGRATION-PLAYBOOK is minor cleanup. Everything else is resolved.
|
Claude finished @kyle-sexton's task in 4m 15s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
Format gate at L81; test cases for "now", "yesterday", "1 second ago" → unknown ✅ |
| Snapshot session_id not cross-checked (copied/renamed snapshot answers for other session) | 163a3a1 |
jq gate at L66 requires .session_id == $sid ✅ |
Temp file pre-planted symlink race ($RANDOM 15-bit entropy + >"$tmp" follows symlink) |
163a3a1 |
set -o noclobber at L134 refuses pre-existing path; $RANDOM$RANDOM doubles entropy to ~30 bits ✅ |
Prune TOCTOU: concurrent sibling refresh deleted by racing rm |
163a3a1 |
Per-file mtime re-check before unlink at L123 ✅ |
Windows chmod 700 no-op integrity gap undocumented |
163a3a1 |
Disclosed in reader-contract, README, and Data Egress (5) in the playbook ✅ |
Inline env-assignment wiring template broken (THEME=dark cmd) |
163a3a1 |
sh -c variant printed for shell-syntax commands at SKILL.md L88–100 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop reads to EOF; L75–82 ✅ |
What is NOT a finding
session_idpath containment: validated to^[A-Za-z0-9_-]+$before filesystem use on both write (L94) and read (L52) sides.jqandawkvariable bindings:--arg ts "$ts",-v u=,-v s=,-v a=— all bind values as typed data, not as program text. Output is constrained to four known words.printf '%s' "$INPUT" | "$@"passthrough:$INPUTis piped as data;"$@"originates from the operator'ssettings.json, not from stdin. No injection surface.find -exec sh -c '...' _ {} +pruning: filenames produced byfindflow into"$f"as double-quoted positional parameters;-type f(usinglstat) excludes symlinks from the outer scan.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$target(does not follow it); the pre-planted symlink attack path at$targetis not viable.- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: true: correct on the write-capable setup skill.
Summary
No CRITICAL or IMPORTANT security vulnerabilities remain at HEAD 78e8c8c. Two SUGGESTION-level items persist: a stale inaccurate claim in the MIGRATION-PLAYBOOK security record (Finding 1, flagged 3× previously, one-line fix), and the single-quote escaping gap in the sh -c wiring template (Finding 2, flagged by Codex in the prior round, documentation note needed). Neither is an exploitable vulnerability. The 78e8c8c stdin-drain fix is clean.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 78e8c8c83a
ℹ️ 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".
…pply reset mode (#1230) Two codex follow-up findings: (1) the sh -c wrapped wiring variant corrupted original commands containing single quotes — the skill now prints the fully POSIX-escaped line ('\'' substitution) and verifies the round-trip, never a template with raw quotes; (2) bare apply on an existing valid zones.json was ambiguous between converge and preserve — now mode-explicit: bare apply is repair-only (never overwrites valid custom bands), `apply reset` is the explicit route to shipped defaults, both preserve unrecognized keys. Eval updated to pin the repair-only semantics. skill-quality gate: PASS. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 10s —— View job PR Review:
|
| Finding | Fixed in | Status |
|---|---|---|
captured_at ISO-8601 gate before date -d |
163a3a1 |
✅ |
| Session-id equality check (copied snapshot can't answer for another session) | 163a3a1 |
✅ |
SKILL.md sh -c template broken for inline env-assignment commands |
163a3a1 |
✅ |
SKILL.md apply no jq guard for merge-path when file present |
163a3a1 |
✅ |
| Prune TOCTOU: per-file mtime re-check before unlink | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + symlink follow via pre-planted path |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous; LLM left to interpret | 288d0dd |
✅ |
What Remains Solid
- Fail-open is airtight. Every precondition failure in
context-zone.shroutes tounknown; the ISO-8601 format gate at L81 and session-id equality check at L66 are both defensive-correct. The tee never surfaces failures to the operator's statusline pipeline. - Test harness is comprehensive. 40 tee assertions (including >1 MiB round-trip) + 44 zone assertions (including
"now"/"yesterday"→unknownand session-id mismatch →unknown). Themv-shim approach for deterministic Windows locked-target testing is well-crafted. - POSIX-escape round-trip verification — the instruction to "mentally unquote it back and confirm it reproduces the original command byte-for-byte" is the right guard for a skill that generates edits without running them.
- Apply mode semantics are now unambiguous — REPAIR-ONLY vs.
apply resetis deterministic, and eval ci: onboard to the CI platform (ci-workflows + standards) #4 validates the REPAIR-ONLY path against a concrete example with custom-but-valid bands. - CLAUDE.md compliance is complete — repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam carve-out documented,disable-model-invocation: trueon the write-capable skill.
|
Claude finished @kyle-sexton's task in 4m 9s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO bypass (date -d "now" → fresh zone) |
163a3a1 |
Format gate at context-zone.sh:81; test cases for "now", "yesterday", "1 second ago" → unknown ✅ |
Snapshot session_id not checked against requested id |
163a3a1 |
jq equality at context-zone.sh:66; mismatch → unknown; test at line 122 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134 (O_CREAT|O_EXCL refuses any pre-existing path including symlinks); $RANDOM$RANDOM at L129 for ~30-bit entropy ✅ |
| Prune TOCTOU deletes concurrently-refreshed sibling | 163a3a1 |
Per-file mtime re-check at statusline-tee.sh:123 before unlink ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract (L68–73), README, and MIGRATION-PLAYBOOK.md L826–831 ✅ |
| Inline env-assignment statuslines break argv wiring | 163a3a1 |
Shell-syntax guard at SKILL.md:88–100 ✅ |
sh -c template breaks on single quotes in wrapped command |
288d0ddb |
POSIX-escape instruction at SKILL.md:102–106 (replace ' with '\''; JSON-escape after; verify round-trip) ✅ |
| 1 MiB stdin cap truncated wrapped command input | 78e8c8c |
Drain loop to EOF in 1 MiB blocks at statusline-tee.sh:73–82 ✅ |
Finding 1 — SUGGESTION / CONFIRMED: Security record still contains a false claim
File: docs/MIGRATION-PLAYBOOK.md L812–813
The Code Execution (1) bullet still reads:
"snapshot values are only ever passed through
jq, never interpolated into a shell"
At HEAD, context-zone.sh L81–86 extracts $ts from .captured_at via jq -r, format-gates it to strict ISO-8601 UTC, then passes it to date -d "$ts". That's not "only ever through jq" — $ts reaches a typed date parser. The format gate makes this fully safe, but the ACCEPT justification states a false factual premise that any future security reviewer will notice (and every prior reviewer has).
The Data Egress (5) bullet at L830–831 was correctly updated in 163a3a1: "the resolver format-gates captured_at and requires the embedded session id to match, so forgery cannot ride a lenient parser." The Code Execution bullet should align. One-line fix:
"…sanitized to
[A-Za-z0-9_-]before filename use;captured_atis format-gated to strict ISO-8601 UTC before being passed todate -d; no snapshot value is passed toeval,sh -c, or any code executor."
This finding has been flagged in every prior review round. It is a documentation-accuracy issue in the ACCEPT justification, not an exploitable vulnerability.
Fix the Code Execution claim →
What is NOT a finding
session_idpath containment: validated to^[A-Za-z0-9_-]+$before any filesystem operation on both write (L94) and read (L52) sides; five hostile patterns exercised in tests.jqvariable binding (--arg ts "$ts"in tee,-v u=/-v s=/-v a=in awk): all bind values as typed data, not as program text.$tsin tee (L105): generated bydate -uinside the tee itself, not from stdin data;--arg ts "$ts"is safe.printf '%s' "$INPUT" | "$@"passthrough:$INPUTis piped as data;"$@"originates from operator'ssettings.json, not from stdin content — no injection surface.find -exec sh -c '...' _ {} +pruning: filenames flow into"$f"as double-quoted positional parameters;-type f(lstat) excludes symlinks from the outer scan;$diris$HOME+ fixed literal suffix.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetwith the new file rather than following it — a pre-planted symlink at the target path cannot redirect the write.- Stdin drain loop (
78e8c8c):INPUTreaches onlyjq(piped as stdin data) andprintf '%s' "$INPUT" | "$@"(piped as data); no injection surface regardless of payload size. - POSIX-escape instruction in SKILL.md: the
sh -ctemplate source is the operator's ownsettings.json(trusted); the POSIX-escape and JSON-escape instructions are correct; the verify-round-trip instruction is an appropriate correctness check. apply resetargument: theargument-hintfield is now"check | apply [reset]"and the skill logic is mode-explicit.applywithout argument is repair-only;apply resetoverwrites to shipped defaults. Both preserve unrecognized keys. No write path is ambiguous.- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
One SUGGESTION remains (stale Code Execution claim in MIGRATION-PLAYBOOK.md L812, flagged in every prior round). No CRITICAL or IMPORTANT vulnerabilities exist at HEAD 288d0ddb. The 288d0ddb commit (POSIX-escape instruction + apply reset mode) introduces no new attack surface.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9e26c47761
ℹ️ 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".
An overlapping older refresh could land its rename after a newer one, regressing the snapshot one refresh (codex finding). The rename loop now reads the target's captured_at first and skips when the target is newer (lexical ISO compare, re-checked per retry); the residual check-to-rename microsecond race is documented as accepted. Red-green test: a target with a newer captured_at survives an older write. 43/43 green; shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 2m 6s —— View job PR Review:
|
| Finding | Fixed in | Status |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
✅ |
Snapshot session_id not cross-checked |
163a3a1 |
✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
✅ |
apply merge path undefined when jq absent and zones.json exists |
163a3a1 |
✅ |
| Prune TOCTOU could delete a concurrently refreshed sibling | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous | 288d0dd |
✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
✅ |
What's Solid at HEAD
- Fail-open is airtight. ISO-8601 gate at
context-zone.sh:81, session-id equality check at L66, and every other precondition failure routes tounknown. No tee failure ever reaches the statusline pipeline. - Path containment is thorough.
[A-Za-z0-9_-]+$enforced on both write (L94) and read (L52) sides; five hostile patterns exercised in tests. - Test coverage is comprehensive. 40 tee assertions (Cases 1–17 including 15b and 16b) + 44 zone assertions covering every documented failure mode. The
mv-shim approach for deterministic Windows locked-target simulation remains well-crafted. - Security record is internally consistent. MIGRATION-PLAYBOOK.md lines 811–814 and 826–831 both accurately describe the mechanism; the ACCEPT verdict is factually sound.
- CLAUDE.md compliance is complete. Repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented,disable-model-invocation: trueon the write-capable setup skill.
|
Claude finished @kyle-sexton's task in 3m 8s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO bypass (date -d "now" → fresh zone) |
163a3a1 |
ISO-8601 gate at context-zone.sh:81 ✅ |
Snapshot session_id not checked (copied/renamed snapshot answers for other session) |
163a3a1 |
jq equality at context-zone.sh:66 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134 + $RANDOM$RANDOM at L129 ✅ |
| Prune TOCTOU deletes a concurrently-refreshed sibling | 163a3a1 |
Per-file mtime re-check before unlink at statusline-tee.sh:123 ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract, README, and MIGRATION-PLAYBOOK.md L826–831 ✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
Shell-syntax guard at SKILL.md L88–100 ✅ |
Single quotes in sh -c template break commands containing ' |
288d0dd |
POSIX-escape instruction at SKILL.md L102–106 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop to EOF at statusline-tee.sh:73–82 ✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
MIGRATION-PLAYBOOK.md:811–814 now accurate ✅ |
What is NOT a finding
existing_tscomparison injection risk:[[ "$existing_ts" > "$ts" ]]uses bash's[[string comparison — no command execution, no word splitting. Even a crafted string value cannot inject commands here. Safe.session_idpath containment: validated to^[A-Za-z0-9_-]+$before any filesystem operation on both write (L94) and read (L52) sides; five hostile patterns exercised in tests.jqvariable binding:--arg ts "$ts"in the tee and-v u=/-v s=/-v a=in awk bind values as typed data, not as program text.printf '%s' "$INPUT" | "$@"passthrough:"$@"originates from the operator'ssettings.json, not from stdin data. No injection surface.find -exec sh -c '...' _ {} +pruning: filenames flow into"$f"as double-quoted positional parameters;-type f(lstat) excludes symlinks from the outer scan.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetatomically — no symlink-follow on the destination side.- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
One SUGGESTION: the anti-regression check in cf5628a reads existing_ts from the target file without format-validating it, which converts the pre-existing Windows forged-snapshot timing race into a persistent snapshot-freeze path. This is a marginal worsening of the already-disclosed Windows chmod 700 limitation, not a new vulnerability class. All nine IMPORTANT findings from prior review rounds are confirmed resolved at HEAD.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: cf5628acb8
ℹ️ 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".
…tatusLine (#1230) A project or local settings scope shadows the user file; wiring the user settings while shadowed would apply cleanly and never run (codex finding). check now resolves the effective command across user, project, and local scopes, evaluates the wiring states against it, and prints the edit for the owning file — naming the shadow and the remove-the-override alternative explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 19s —— View job PR Review:
|
| Finding | Fixed in | Status |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
✅ |
Snapshot session_id not cross-checked |
163a3a1 |
✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
✅ |
apply merge path undefined when jq absent and zones.json exists |
163a3a1 |
✅ |
| Prune TOCTOU could delete a concurrently refreshed sibling | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous | 288d0dd |
✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
✅ |
Project-level statusLine shadow silently blocks the tee |
e7e2dd0 |
✅ |
What Remains Solid
- Fail-open is airtight. ISO-8601 gate at
context-zone.sh:81, session-id equality check at L66, and every other precondition failure routes tounknown. - Anti-regression rename is correct. ISO-8601 lexical comparison re-checked on every retry; fail-safe on absent/malformed target; temp file cleaned up on early return.
- Test coverage — 40 tee assertions (including >1 MiB round-trip, Cases 15b and 16b) + 44 zone assertions (including
"now"/"yesterday"→unknownand session-id mismatch →unknown). - Security record accurate. MIGRATION-PLAYBOOK.md Code Execution (1) now correctly states the ISO-8601 format gate on
captured_atbeforedate -d; Data Egress (5) discloses the Windowschmod 700limitation honestly. - CLAUDE.md compliance is complete. Repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented and justified,disable-model-invocation: trueon the write-capable skill.
|
Claude finished @kyle-sexton's task in 3m 18s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
Format gate at context-zone.sh:81 ✅ |
Snapshot session_id not checked against requested id |
163a3a1 |
jq equality at context-zone.sh:66 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134; $RANDOM$RANDOM at L129 ✅ |
| Prune TOCTOU deletes a concurrently refreshed sibling | 163a3a1 |
Per-file mtime re-check at statusline-tee.sh:123 ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract, README, and MIGRATION-PLAYBOOK.md L826–831 ✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
Shell-syntax guard at SKILL.md:94–106 ✅ |
Single quotes inside sh -c template break commands containing ' |
288d0dd |
POSIX-escape instruction at SKILL.md:108–112 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop to EOF at statusline-tee.sh:73–82 ✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
MIGRATION-PLAYBOOK.md:811–814 now accurate ✅ |
| Overlapping older same-session write can regress newer snapshot | cf5628a |
Anti-regression check at statusline-tee.sh:148–152 ✅ |
| Setup targets wrong scope when project/local statusLine shadows user file | e7e2dd0 |
Scope resolution at SKILL.md:30–37 ✅ |
What is NOT a finding
session_idpath containment: validated to^[A-Za-z0-9_-]+$before filesystem use on both write (L94) and read (L52) sides.$tsin tee (L105): generated bydate -uinside the tee, not from external data;--arg ts "$ts"to jq is safe.[[string comparison onexisting_ts: uses bash's[[— no command execution, no word-splitting; a crafted string value cannot inject shell commands here.printf '%s' "$INPUT" | "$@"passthrough:"$@"originates from the operator'ssettings.json;$INPUTis piped as data. No injection surface.find -exec sh -c '...' _ {} +pruning: filenames flow into"$f"as double-quoted positional parameters;-type f(lstat) excludes symlinks;$diris$HOME+ fixed literal suffix.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetatomically, not following it.- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
One SUGGESTION remains (Finding 1 — existing_ts not ISO-8601 validated before the anti-regression comparison in statusline-tee.sh:148, first raised in the cf5628a security review). No CRITICAL or IMPORTANT vulnerabilities exist at HEAD e7e2dd0. The e7e2dd0 commit (setup scope resolution) introduces no new attack surface.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7e2dd0ff1
ℹ️ 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".
…d race (#1230) captured_at is second-precision, so the regression guard's strict compare cannot order two same-second writers and the older may win that second (codex finding). Accepted by design, now stated in the guard comment: worst case is context data at most one second older on a coarse routing signal, superseded by the next refresh; serializing would add a cross-platform lock dependency (no flock on macOS) for no behavioral difference. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 11s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO bypass (date -d "now" → fresh zone) |
163a3a1 |
Format gate at context-zone.sh:81 ✅ |
Snapshot session_id not checked against requested id |
163a3a1 |
jq equality at context-zone.sh:66 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134; $RANDOM$RANDOM at L129 ✅ |
| Prune TOCTOU deletes a concurrently refreshed sibling | 163a3a1 |
Per-file mtime re-check at statusline-tee.sh:123 ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract.md:68–73, README, and MIGRATION-PLAYBOOK.md L826–831 ✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
Shell-syntax guard at SKILL.md:94–106 ✅ |
Single quotes inside sh -c template break commands containing ' |
288d0dd |
POSIX-escape instruction at SKILL.md:108–112 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop to EOF at statusline-tee.sh:73–82 ✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
MIGRATION-PLAYBOOK.md:811–814 now accurate ✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
Anti-regression check at statusline-tee.sh:154–158 ✅ |
| Setup targets wrong scope when project/local statusLine shadows user file | e7e2dd0 |
Scope resolution at SKILL.md:30–37 ✅ |
| Equal-second tie undocumented | f6a6c21 |
Guard comment now names both residual races at statusline-tee.sh:146–153 ✅ |
Finding 1 — SUGGESTION / PLAUSIBLE: existing_ts not ISO-8601 validated before anti-regression comparison
existing_ts=$(jq -r '.captured_at // empty' "$target" 2>/dev/null) || existing_ts=""
if [[ -n "$existing_ts" && "$existing_ts" > "$ts" ]]; thenexisting_ts is read from the on-disk snapshot's .captured_at via jq -r without ISO-8601 format validation before the lexical > comparison. The zone resolver applies this exact gate at context-zone.sh:81; the tee's anti-regression check does not.
On POSIX systems (Linux, macOS): not exploitable — chmod 700 is effective, the directory is owner-only.
On Windows (already-disclosed platform limitation, where chmod 700 is a no-op): an adversary who can write to the snapshot directory can set "captured_at": "2999-12-31T23:59:59Z". The string "2999..." sorts lexically later than any current timestamp, so every subsequent legitimate refresh sees "2999..." > "$ts" → removes its temp file and returns 0 without updating. The snapshot is permanently frozen. The consequence: the zone resolver's staleness check (age = now_epoch − snap_epoch, where snap_epoch is year 2999) produces an age far below −60, which fails ((age >= -60 && age <= 600)) → the resolver returns unknown. So the attack outcome is permanently degraded-to-fail-open, not a forged green zone.
The targeted variant (forged captured_at ~30 seconds in the future) passes both the format gate and the staleness check, enabling a short-window forged zone — but this requires the adversary to continuously refresh the forged timestamp (every 30–60 seconds) to stay within the staleness window, which is comparable to the pre-cf5628a race difficulty.
Independent one-line fix (mirrors context-zone.sh:81):
existing_ts=$(jq -r '.captured_at // empty' "$target" 2>/dev/null) || existing_ts=""
[[ "$existing_ts" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] || existing_ts=""This eliminates the far-future freeze path (a non-ISO or impossible-format value forces existing_ts="", causing the anti-regression check to skip and the rename to proceed, overwriting the forged file). It does not fully close the short-window forged-zone variant, which is bounded by the Windows chmod gap root cause already disclosed.
This finding was first raised in the cf5628a security review and remains open at HEAD.
What is NOT a finding
[[string comparison onexisting_ts: uses bash's[[— no command execution, no word-splitting; a crafted string cannot inject shell commands.$tsin the tee (L105): generated bydate -uinside the tee, not from external data;--arg ts "$ts"to jq is safe.- Session-id path containment: validated to
^[A-Za-z0-9_-]+$before any filesystem operation on both write (L94) and read (L52) sides. jqandawkvariable bindings (--arg ts "$ts",-v u=,-v s=,-v a=): bind values as typed data, not program text; awk output constrained to three known words.printf '%s' "$INPUT" | "$@"passthrough:"$@"originates from operator'ssettings.json, not from stdin data. No injection surface.find -exec sh -c '...' _ {} +pruning: filenames flow into"$f"as double-quoted positional parameters;-type fuseslstat, excluding symlinks;$diris$HOME+ fixed literal suffix.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetatomically — a pre-planted symlink at the destination cannot redirect the write.rm -f "$tmp"in noclobber failure path: removes the symlink itself if$tmpwas a pre-planted symlink; if absent, is a silent no-op.- Stdin drain loop:
INPUTflows only intojq(as piped stdin data) andprintf '%s' "$INPUT" | "$@"(as piped data). No injection surface regardless of payload size. - MIGRATION-PLAYBOOK.md Code Execution claim: accurately states the mechanism at L811–814; Data Egress (5) at L829–831 correctly discloses the Windows platform limitation.
- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
One SUGGESTION remains (existing_ts not ISO-8601 validated before the anti-regression comparison in statusline-tee.sh:154, introduced in cf5628a and still open at HEAD). The far-future freeze path it enables is Windows-only, fail-open in outcome (unknown rather than a forged green zone), and the root cause is the existing disclosed chmod 700 platform limitation. No CRITICAL or IMPORTANT vulnerabilities exist at HEAD f6a6c21. The HEAD commit (comment-only) introduces no new attack surface.
|
Claude finished @kyle-sexton's task in 1m 59s —— View job PR Review:
|
| Finding | Fixed in | Status |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
✅ |
Snapshot session_id not cross-checked |
163a3a1 |
✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
✅ |
apply merge path undefined when jq absent and zones.json exists |
163a3a1 |
✅ |
| Prune TOCTOU could delete a concurrently refreshed sibling | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous | 288d0dd |
✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
✅ |
Project-level statusLine shadow silently blocks the tee |
e7e2dd0 |
✅ |
| Equal-second same-session tie undocumented | f6a6c21 |
✅ |
existing_ts not ISO-8601 validated before anti-regression comparison |
— | SUGGESTION (open, not a blocker) |
What Remains Solid at HEAD
- Fail-open is airtight. ISO-8601 gate at
context-zone.sh:81, session-id equality check at L66, every other precondition failure routes tounknown. - Race acceptance reasoning is sound. The equal-second and check-to-rename races are correctly modeled as a bounded, self-healing worst case: coarse 25-point zone bands and the next refresh make the practical exposure negligible; the cross-platform lock cost is real.
- Test coverage remains comprehensive. 40 tee assertions (Cases 1–17, including >1 MiB round-trip in 15b and anti-regression in 16b) + 44 zone assertions.
- Security record and reader contract are accurate. MIGRATION-PLAYBOOK.md lines 811–814 and 826–831 both correctly describe the mechanism; Windows scope limitation is honestly disclosed.
- CLAUDE.md compliance is complete. Repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented,disable-model-invocation: trueon the write-capable setup skill.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f6a6c21099
ℹ️ 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".
…vel (#1230) A jq merge reserializes the document, so the byte-for-byte preservation claim was unachievable (codex finding). The contract now states value-level preservation (same keys, same JSON values; formatting and escape spellings may normalize) and requires consumers to parse the file as JSON, never depend on raw bytes. Eval aligned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 3s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO bypass (date -d "now" → fresh zone) |
163a3a1 |
Format gate at context-zone.sh:81 ✅ |
Snapshot session_id not checked against requested id |
163a3a1 |
jq equality at context-zone.sh:66 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134; $RANDOM$RANDOM at L129 ✅ |
| Prune TOCTOU deletes a concurrently refreshed sibling | 163a3a1 |
Per-file mtime re-check at statusline-tee.sh:123 ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract.md:68–73, README, and MIGRATION-PLAYBOOK.md L826–831 ✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
Shell-syntax guard at SKILL.md:94–106 ✅ |
Single quotes in sh -c template break commands containing ' |
288d0dd |
POSIX-escape instruction at SKILL.md:108–112 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop to EOF at statusline-tee.sh:73–82 ✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
MIGRATION-PLAYBOOK.md:811–814 now accurate ✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
Anti-regression check at statusline-tee.sh:154 ✅ |
| Setup targets wrong scope when project/local statusLine shadows user file | e7e2dd0 |
Scope resolution at SKILL.md:30–37 ✅ |
Finding 1 — SUGGESTION / PLAUSIBLE: existing_ts not ISO-8601 validated before anti-regression comparison (carried forward from cf5628a)
existing_ts=$(jq -r '.captured_at // empty' "$target" 2>/dev/null) || existing_ts=""
if [[ -n "$existing_ts" && "$existing_ts" > "$ts" ]]; thenexisting_ts is read from the on-disk snapshot's .captured_at via jq -r without ISO-8601 format validation before the lexical > comparison. The zone resolver applies this exact gate at context-zone.sh:81; the tee's anti-regression check does not.
On POSIX systems (Linux, macOS): not exploitable — chmod 700 is effective.
On Windows (the already-disclosed platform limitation, chmod 700 is a no-op): a co-user can write a snapshot with "captured_at": "2999-12-31T23:59:59Z" once. The string "2999..." sorts lexically later than any current timestamp, so every subsequent legitimate refresh sees "2999..." > "$ts" → removes its temp file and returns 0 without updating. The session's snapshot is permanently frozen. The zone resolver correctly rejects a far-future timestamp (age << −60 → fails the staleness check), so the consequence is permanent fail-open unknown, not a forged green zone.
The targeted variant (a captured_at ~30 seconds in the future) passes the staleness check but requires the adversary to continuously refresh the forged timestamp to sustain it, which is comparable to the difficulty of the pre-cf5628a race.
The independent one-line fix (mirrors context-zone.sh:81):
existing_ts=$(jq -r '.captured_at // empty' "$target" 2>/dev/null) || existing_ts=""
[[ "$existing_ts" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z$ ]] || existing_ts=""This eliminates the far-future freeze path; a non-ISO or out-of-format value causes the check to skip and the rename to proceed, overwriting the forged file. It does not close the short-window sustained-refresh variant, which is bounded by the Windows chmod 700 root cause already disclosed.
This finding was first raised in the cf5628a security review and remains open at HEAD b96ab6b.
Fix: add ISO-8601 format gate on existing_ts →
What is NOT a finding
session_idpath containment: validated to^[A-Za-z0-9_-]+$before any filesystem operation on both write (L94) and read (L52) sides.$tsin the tee (L105): generated bydate -uinside the tee itself, not from external data;--arg ts "$ts"to jq is safe.[[string comparison onexisting_ts: uses bash's[[— no command execution, no word-splitting; a crafted string cannot inject shell commands.rm -f "$tmp"in noclobber failure path: removes the symlink itself on Linux if$tmpwas pre-planted; if absent, silent no-op. Cannot write through it.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetatomically — a pre-planted symlink at the destination cannot redirect the write.find -exec sh -c '...' _ {} +pruning: filenames flow into"$f"as double-quoted positional parameters;-type f(lstat) excludes symlinks from the outer scan;$diris$HOME+ fixed literal suffix.- awk variable binding (
-v u=,-v s=,-v a=): binds values as typed strings, not as awk program text; output is constrained to three hardcoded words. printf '%s' "$INPUT" | "$@"passthrough:"$@"originates from operator'ssettings.json, not from stdin data. No injection surface.- Stdin drain loop:
INPUTflows only intojq(piped as stdin data) andprintf '%s' "$INPUT" | "$@"(piped as data). No injection surface. - HEAD commit (
b96ab6b) — documentation-only: changesSKILL.mdandevals.jsonto clarify thatzones.jsonpreservation is semantic (value-level, not lexical). No code logic altered; no new attack surface. - MIGRATION-PLAYBOOK.md Code Execution claim: accurately states the mechanism at L811–814.
- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
One SUGGESTION remains (existing_ts not ISO-8601 validated before the anti-regression comparison in statusline-tee.sh:154, introduced in cf5628a and carried forward through all subsequent reviews). The far-future freeze it enables on Windows is fail-open in outcome (unknown rather than a forged green zone) and shares its root cause with the already-disclosed Windows chmod 700 platform limitation. No CRITICAL or IMPORTANT vulnerabilities exist at HEAD b96ab6b. The HEAD commit (semantic preservation documentation fix) introduces no new attack surface.
|
Claude finished @kyle-sexton's task in 2m 30s —— View job PR Review:
|
| Finding | Fixed in | Status |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
✅ |
Snapshot session_id not cross-checked |
163a3a1 |
✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
✅ |
apply merge path undefined when jq absent and zones.json exists |
163a3a1 |
✅ |
| Prune TOCTOU could delete a concurrently refreshed sibling | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous | 288d0dd |
✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
✅ |
Project-level statusLine shadow silently blocks the tee |
e7e2dd0 |
✅ |
| Equal-second same-session tie undocumented | f6a6c21 |
✅ |
zones.json byte-level preservation unachievable via jq merge |
b96ab6b |
✅ |
existing_ts not ISO-8601 validated before anti-regression comparison |
— | SUGGESTION (open, not a blocker) |
What's Solid at HEAD
- Fail-open is airtight. ISO-8601 gate at
context-zone.sh:81, session-id equality check at L66, and every other precondition failure routes tounknown. The tee never surfaces failures to the operator's statusline pipeline. - Semantic preservation contract is now technically accurate.
jqdeserializes and reserializes; the old "byte-for-byte" claim was wrong about what the code actually does. The new language correctly describes value identity with normalization of lexical representation. - Zones.json consumer guidance is correct.
reader-contract.md:136already said "The byte-identity rule covers the inlined defaults only" (not the unrecognized keys), consistent with the updated SKILL.md language. - Test coverage — 40 tee assertions (including >1 MiB round-trip, Cases 15b and 16b) + 44 zone assertions (including
"now"/"yesterday"→unknownand session-id mismatch →unknown). - Security record and reader contract are accurate. MIGRATION-PLAYBOOK.md lines 811–814 and 826–831 both correctly describe the mechanism; Windows scope limitation is honestly disclosed.
- CLAUDE.md compliance is complete. Repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented and justified,disable-model-invocation: trueon the write-capable setup skill.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b96ab6b707
ℹ️ 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".
…ling (#1230) A valid-but-far-future captured_at in the target (clock correction, tampering) made the regression guard suppress every refresh until that date, wedging the session in conservative mode (codex finding). The guard now honors a newer target only within a ~5-minute plausibility ceiling of the current write's timestamp; anything beyond is replaced. Ceiling-computation failure disables the guard (fail toward freshness). Red-green test: a 9999-12-31 target is replaced by the next refresh. 44/44 green; shellcheck clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @kyle-sexton's task in 3m 35s —— View job PR Review:
|
existing_ts |
Condition 4 (! existing_ts > ceiling) |
Outcome |
|---|---|---|
| Normal concurrent refresh (seconds ahead) | ceiling is 5 min → existing ≤ ceiling → true | Honored ✅ |
| 2-minute-ahead (Case 16b) | within 5-min window → true | Honored ✅ |
"9999-12-31T23:59:59Z" (Case 16c) |
9999 > ceiling → false | Replaced ✅ |
Any non-ISO string starting with letters (e.g. "now") |
letter > "2026..." → false |
Replaced ✅ |
guard_ceiling empty (date fails) |
[[ -n "" ]] → false → short-circuits |
Always writes (fail toward freshness) ✅ |
The ceiling approach is strictly superior to the suggested ISO-8601 format gate: a pure format gate would pass "9999-12-31T23:59:59Z" (valid ISO-8601, just implausible), leaving the far-future freeze unaddressed. The plausibility ceiling closes both non-ISO strings and valid-but-implausible ISO dates in a single check.
Test Case 16c (statusline-tee.test.sh:313–322) seeds sess-42.json with "9999-12-31T23:59:59Z", runs the tee, and asserts the written snapshot has used_percentage: 8 (the fresh tee value), confirming the far-future file was overwritten. This is the correct red-green observable form.
Complete Finding Resolution Across All 17 Findings
| Finding | Fixed in | Status |
|---|---|---|
captured_at non-ISO date -d bypass ("now" → fresh zone) |
163a3a1 |
✅ |
Snapshot session_id not cross-checked |
163a3a1 |
✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
✅ |
apply merge path undefined when jq absent and zones.json exists |
163a3a1 |
✅ |
| Prune TOCTOU could delete a concurrently refreshed sibling | 163a3a1 |
✅ |
Temp-file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
✅ |
Reader contract "untrusted data" wording contradicted date -d "$ts" |
163a3a1 |
✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
✅ |
| 1 MiB stdin cap silently truncated wrapped command's input | 78e8c8c |
✅ |
Single quotes inside sh -c '<original command>' unescaped |
288d0dd |
✅ |
| Apply reset mode trigger ambiguous | 288d0dd |
✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
✅ |
Project-level statusLine shadow silently blocks the tee |
e7e2dd0 |
✅ |
| Equal-second same-session tie undocumented | f6a6c21 |
✅ |
zones.json byte-level preservation unachievable via jq merge |
b96ab6b |
✅ |
Implausibly-future existing_ts permanently suppresses tee writes |
ef0bf085 |
✅ |
What's Solid at HEAD
- Fail-open is airtight. ISO-8601 gate at
context-zone.sh:81, session-id equality check at L66, and every other precondition failure routes tounknown. The tee never surfaces failures to the statusline pipeline. - Anti-regression guard is now complete. Plausibility ceiling (
now + 5 min) closes the far-future freeze path;guard_ceilingcomputation failure disables the guard and fails toward freshness; Cases 16b and 16c validate both the normal-race and the implausible-future paths independently. - Test coverage is comprehensive. 44 tee assertions across 17 cases (including Case 15b for >1 MiB round-trip, Case 16b for same-session non-regression, Case 16c for plausibility ceiling) + 44 zone assertions (including
"now"/"yesterday"→unknownand session-id mismatch →unknown). - Security record is accurate. MIGRATION-PLAYBOOK.md lines 811–814 and 826–831 correctly describe the mechanism; Windows
chmod 700limitation is honestly disclosed with the routing-hints-only consequence. - CLAUDE.md compliance is complete. Repo-agnostic paths, no
userConfig, versioned0.1.0, cross-plugin seam documented and justified,disable-model-invocation: trueon the write-capable setup skill.
No open findings remain. All 17 are confirmed resolved at HEAD ef0bf085.
|
Claude finished @kyle-sexton's task in 3m 39s —— View job Security Review — PR #1241
|
| Finding | Fixed in | Verification |
|---|---|---|
captured_at non-ISO bypass (date -d "now" → fresh zone) |
163a3a1 |
Format gate at context-zone.sh:81 ✅ |
Snapshot session_id not checked against requested id |
163a3a1 |
jq equality at context-zone.sh:66 ✅ |
Temp file $RANDOM entropy + pre-planted symlink race |
163a3a1 |
set -o noclobber at statusline-tee.sh:134; $RANDOM$RANDOM at L129 ✅ |
| Prune TOCTOU deletes a concurrently refreshed sibling | 163a3a1 |
Per-file mtime re-check before unlink at statusline-tee.sh:123 ✅ |
Windows chmod 700 scope limitation undocumented |
163a3a1 |
Disclosed in reader-contract.md:68–73, README, and MIGRATION-PLAYBOOK.md ✅ |
| Inline env-assignment statuslines break argv wiring template | 163a3a1 |
Shell-syntax guard at SKILL.md:94–106 ✅ |
Single quotes inside sh -c template break commands containing ' |
288d0dd |
POSIX-escape instruction at SKILL.md:108–112 ✅ |
| 1 MiB stdin cap silently truncated wrapped command input | 78e8c8c |
Drain loop to EOF at statusline-tee.sh:73–82 ✅ |
| Security record Code Execution claim factually wrong | 9e26c47 |
MIGRATION-PLAYBOOK.md:811–814 accurate ✅ |
| Overlapping older same-session write can regress a newer snapshot | cf5628a |
Anti-regression check at statusline-tee.sh:165 ✅ |
| Setup targets wrong scope when project/local statusLine shadows user file | e7e2dd0 |
Scope resolution at SKILL.md:30–37 ✅ |
Far-future captured_at permanently suppresses tee writes (freeze DoS) |
ef0bf085 |
Plausibility ceiling at statusline-tee.sh:140–166 ✅ |
ef0bf085 — Plausibility ceiling: no new attack surface
The HEAD commit adds two things:
1. guard_ceiling computation (statusline-tee.sh:147–148):
guard_ceiling=$(date -u -d '+5 minutes' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null ||
date -u -v '+5M' '+%Y-%m-%dT%H:%M:%SZ' 2>/dev/null) || guard_ceiling=""Both date arguments are fixed literals — no user or snapshot data involved. The || guard_ceiling="" makes any date-command failure disable the guard entirely, failing toward freshness. No injection surface.
2. Updated anti-regression condition (statusline-tee.sh:165–166):
if [[ -n "$existing_ts" && -n "$guard_ceiling" && "$existing_ts" > "$ts" &&
! "$existing_ts" > "$guard_ceiling" ]]; thenThe added ! "$existing_ts" > "$guard_ceiling" clause (meaning: existing_ts ≤ guard_ceiling) constrains the guard to only honor timestamps in the range (ts, ts+5min]. Traced through the attack paths that were previously concerning:
existing_ts value |
> ts |
! > guard_ceiling |
Guard fires? | Outcome |
|---|---|---|---|---|
"2999-12-31T23:59:59Z" (far-future) |
TRUE | FALSE | No | Rename proceeds; forged file replaced ✓ |
"now" (non-ISO, sorts after '2') |
TRUE | FALSE | No | Rename proceeds ✓ |
"abc" (non-ISO, 'a' > '2') |
TRUE | FALSE | No | Rename proceeds ✓ |
"2026-07-24T07:07:00Z" (2 min ahead, within ceiling) |
TRUE | TRUE | Yes | Guard fires (intended: newer snapshot exists) |
"1970-01-01T00:00:00Z" (old) |
FALSE | — | No | Rename proceeds ✓ |
The guard_ceiling acts as an escape hatch for every value that could cause a permanent freeze. The POSIX [[ string comparison with ! is syntactically correct (! negates the entire binary expression inside [[ ]]).
Short-window forged snapshot (residual, previously noted): An adversary on Windows who writes captured_at ≈ 30 seconds ahead can still trigger the guard and suppress a single refresh cycle, while also passing the zone resolver's staleness check (age ≈ −30 ∈ [−60, 600]). However: sustaining this requires refreshing the forged file every ~30–60 seconds; when the adversary stops, the legitimate tee immediately recovers on the next refresh. The zone word is a routing hint; the reader contract explicitly prohibits attaching security or egress decisions to it. This remains within the scope of the already-disclosed Windows chmod 700 platform limitation, not a new vulnerability class.
Test case 16c exercises the far-future freeze path correctly: seeds the snapshot with "captured_at":"9999-12-31T23:59:59Z" and asserts the legitimate write supersedes it.
What is NOT a finding
guard_ceilingdate arguments: both are fixed literals ('+5 minutes','+5M'), not influenced by snapshot data or user input.[[ ]]string comparisons onexisting_ts: uses bash's[[— no command execution, no word-splitting; a crafted string value cannot inject shell commands.guard_ceilingcomputed once, used across retries: the retry loop runs for at most 0.3 s total (3 × 0.1 s sleep); the 5-minute ceiling does not go stale.session_idpath containment: validated to^[A-Za-z0-9_-]+$before any filesystem operation on both write (L94) and read (L52) sides.jqandawkvariable bindings:--arg ts "$ts",-v u=,-v s=,-v a=all bind values as typed data, not program text.printf '%s' "$INPUT" | "$@"passthrough:"$@"originates from the operator'ssettings.json, not from stdin data.mv -f "$tmp" "$target"rename: on Linux/macOSrename(2)replaces a symlink at$targetatomically; a pre-planted symlink at the destination cannot redirect the write.- No hooks, no MCP, no
eval, no outbound network: confirmed clean at HEAD. disable-model-invocation: trueon setup skill: correct — prevents ambient invocation of theapplywrite path.
Summary
No CRITICAL or IMPORTANT security vulnerabilities exist at HEAD ef0bf085. The HEAD commit closes the one SUGGESTION that was carried through five prior review rounds: the plausibility ceiling (now + 5 minutes) prevents any captured_at value — ISO-8601 or otherwise — from permanently suppressing tee writes. Failure of the ceiling computation disables the guard entirely, failing toward freshness. All 9 previously IMPORTANT findings are confirmed resolved.
… config surface (#1243) ## Summary Lane B of the `plugin-audit-port` topic (contract: `docs/topics/plugin-audit-port/PLAN.md`; lane A merged as #1241 — this lane consumes its frozen reader contract). Adds the **`plugin-quality`** plugin — the machine-local `plugin-audit` skill ported, generalized, and improved: - **`/plugin-quality:audit <plugin>[:<component>]`** — six-step post-use behavioral audit: main-thread evidence capture into a compaction-proof packet (`${CLAUDE_PLUGIN_DATA}/evidence/<session_id>/<target-slug>/<run-nonce>/`, deterministic resume rule, 30-day retention) → map+ground in the fresh named `auditor` subagent with per-topic fresh-docs verification → blindspot + candidates → interactive contract lock (written INTO the packet) → presence-gated review seams with stated fallbacks → sink emit. - **Context-gate** over context-guard snapshots (soft dependency): per-zone decision table (steps 2–3 always subagent; dumb row = summary+pointer, mandatory dispatch, immediate flush artifacts; unknown = dumb row + visible notice), re-evaluated at steps 2 and 5; zones.json read directly via jq; inlined floor values byte-identical to the merged reader contract (drift-grepped post-rebase). - **Sink ladder** (tracked config → registration inference → ask+persist → local markdown) with the unconditional draft+confirm egress gate surfacing the ACTING `gh` account; verb-contract deviation recorded at the coupling site. - **Auditor agent** — fresh-context, tools named honestly (Bash for validate/config probes; Write scoped to the evidence packet — gap surfaced by the dumb-zone smoke); standing untrusted-content posture in both hub and agent, backed by a prompt-injection anti-pattern eval. - **Reference corpus** ported + generalized (recurring-concerns + 5 component-type lenses); config surface `.claude/plugin-quality.md` per the config-cascade convention (per-key override declared, implementers row added); setup skill (check with per-layer provenance / apply scoped to the tracked config). ## Sanity-check evidence (per PLAN phases B1–B6) - Drift check (post-rebase, vs MERGED main): snapshot path pattern, 10-minute staleness value, and 50/75 band line grep-match `plugins/context-guard/reference/reader-contract.md` byte-identically. - `grep 'context: fork'` in the audit SKILL.md: no hits. `gh issue create` appears with the confirm gate in the same section. - Forbidden-strings grep (machine paths, consumer names, dead inbox refs) across the plugin tree: empty (re-run post-rebase). - `claude plugin validate plugins/plugin-quality` exit 0; `claude plugin validate --strict .` exit 0 (post-rebase). - `skill-quality:check` PASS (0 errors) on both skills; evals validate against the bundled schema. - Compat reconciliation recorded in `reference/config.md`: markdown item schema byte-matches the live handoff inbox contract fixture; `work-items` local-markdown adapter storage documented as divergent-by-design (seam-CLI-only emission). - **Interactive smokes (headless, recorded observables):** - *Zero-config clean repo*: conservative notice printed verbatim once; unknown at steps 2 AND 5; packet with all five flush artifacts; rung 1 miss → rung 2 HIT (registration inference proposed `melodic-software/claude-code-plugins`) → confirm declined → rung 4 local item; no `gh issue create`. - *Rung-3/4 (unregistered `--plugin-dir` target)*: rungs 1–2 fail correctly, rung 4 local item written; `${CLAUDE_PLUGIN_DATA}` resolves to `…/data/plugin-quality-inline/` under `--plugin-dir` (identity difference vs installed form noted per stress-test #12). - *Falsifiable dumb-zone*: hand-crafted fresh snapshot (`used_percentage` 90) for the smoke session id → step 2 resolved **dumb** with the conservative notice ABSENT (gate did not fall through), flush artifacts present at every step boundary (evidence/findings/contract/review/item), steps 3–4 ran summary+pointer; when the snapshot honestly aged past 10 minutes mid-run, step 5 re-evaluated to unknown with the notice — correct re-evaluation observed live. ## Related - #1241 — lane A (context-guard), merged; this lane inlines its frozen reader-contract floor. - #1233 — operator cutover (post-merge, HITL; deliberately left open). - Topic contract: `docs/topics/plugin-audit-port/PLAN.md`. Closes #1237 Closes #1231 Closes #1232 Closes #1238 Closes #1239 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

Summary
Lane A of the
plugin-audit-porttopic (contract:docs/topics/plugin-audit-port/PLAN.md, locked at40a64b5c; parallel-authorship scope change recorded at77e0a5f6). Adds thecontext-guardplugin — per-session context-window observability on therate-limit-guardtee pattern:scripts/statusline-tee.sh): transparent byte-for-byte wrapper; writescaptured_at+session_id+ the verbatimcontext_windowobject to~/.claude/context-guard/context/<session_id>.jsonper refresh. Atomic temp+rename with Windows retry, jq-missing visible degrade, standalone mode, session-id sanitization to[A-Za-z0-9_-](path containment), 14-day sibling pruning that never touches.tmp.*files.scripts/context-zone.sh): prints exactly one ofsmart/acceptable/dumb/unknown; shipped judgment bands smart ≤ 50 < acceptable ≤ 75 < dumb with~/.claude/context-guard/zones.jsonoverride; fail-open tounknownon absent/stale/unparsable snapshots, null/out-of-range fields, and missing jq.reference/reader-contract.md): inline-floor ownership + byte-identity rule, 10-minute staleness,${CLAUDE_SESSION_ID}session-id discovery + conservative fallback, zone-is-NOT-a-compaction-indicator rule.check(read-only; stale-wiring detection compares the wired path against the resolved${CLAUDE_PLUGIN_ROOT}, catching exists-but-outdated cache paths) +applyscoped to seeding/refreshingzones.jsononly. Evals included.Sanity-check evidence (per PLAN phases A1–A5)
statusline-tee.test.sh— 40 assertions, exit 0 (sandboxed HOME); manual printf smoke echoes exact bytes, exit 0, snapshot passesjq -e '.captured_at and .session_id and .context_window'. Shellcheck clean.context-zone.test.sh— 40 assertions, exit 0. No documented auto-compaction threshold (re-verified 2026-07-24, costs + statusline pages); empirical transcript archaeology found no auto-compact event on the producing machine (largest session 308k tokens uncompacted, 1M-class window) — margin declared in topic notes + contract.skill-quality:checkPASS (0 errors); evals validate against the bundled schema.claude plugin validate plugins/context-guardexit 0;claude plugin validate --strict .exit 0. Clean-repo--plugin-dirheadless smoke:/context-guard:setup checkran end-to-end in a non-source repo — jq PASS, wrapper-missing correctly detected with the wrapped operator edit printed (user's existing command preserved), snapshot-absent correctly attributed, zones-absent reported as valid zero-config state.Security review
Plugin-acceptance record appended to
docs/MIGRATION-PLAYBOOK.md: no hooks, no MCP, no userConfig, no egress; writes only~/.claude/context-guard/(operator-home carve-out, documented cross-plugin seam).Related
Closes #1228
Closes #1234
Closes #1229
Closes #1236
Closes #1230
🤖 Generated with Claude Code