Seven fixes from a Claude Code 2.1.220 / Opus 5 payload audit - #1
Seven fixes from a Claude Code 2.1.220 / Opus 5 payload audit#1elhoim wants to merge 15 commits into
Conversation
Claude Code 2.1.218 changed skills declaring `context: fork` to dispatch as detached background agents by default, opt-out via `background: false`. Research, Council, Ideate, Evals, SystemsThinking, RootCauseAnalysis and Knowledge all declare `context: fork` and their workflows return results inline to the calling thread. Under the new default their output arrives as a task notification instead, so the invoking turn sees nothing. Pin the previous behaviour explicitly rather than inheriting a default that changed underneath the skill.
CREDENTIAL_PATHS guards ~/.ssh keys, ~/.aws/credentials, GPG keyrings and
.env files, but procfs reaches the same secrets by another route: a
process's environ holds its API keys and tokens, and mem/maps expose its
address space.
Because `cat` is in SEARCH_TOOLS, classifyCommand() took the
read-only-command allow branch for `cat /proc/self/environ` and returned
`allow` — printing every secret in the environment with no prompt.
Add a procfs entry to CREDENTIAL_PATHS so those reads return `neutral`
and defer to the native permission prompt. Covers literal pids, `self`
and the shell forms ($$, $PID, ${PID}) that reach the same file.
Ordinary procfs reads (cpuinfo, meminfo, loadavg, /proc/sys/*) are
unaffected and still auto-allow.
The nesting default has moved twice in three releases: 5 until 2.1.217, 1 in 2.1.217, and 3 from 2.1.219. Guidance stating that subagents cannot spawn subagents steers away from a pattern the platform now enables by default. Record the current depth-3 default with the flag to flatten it, and add the two per-session ceilings (200 subagent spawns, 200 WebSearch calls) that a long fan-out session can actually reach — a denied spawn late in a session is usually the session budget rather than the concurrency cap.
Claude Opus 5 shipped 2026-07-24 and is the default Opus model. The
registry still pinned claude-opus-4-8 — the exact drift this file exists
to prevent, and the one its own header cites as the cautionary example.
Applied via `UpdateModels.ts --apply opus claude-opus-5`. Blast radius is
small: consumers that take the tier ALIAS were already auto-resolving, so
only pinnedModelForEffort() and the drift scanner change behaviour.
Left alone deliberately: the dated probe records in the architecture doc
and ALGORITHM/changelog.md are history and should keep naming the model
that actually ran, and cost-aggregator's includes("opus") fallback already
returns correct pricing since Opus 5 holds Opus 4.8's $5/$25 rate.
… scan Safety.hook.ts documents the intended wiring at isAttackerWritableSource: "the settings.json PostToolUse matcher only routes WebFetch/WebSearch + the qualifying mcp__ names here", with the in-code gate as the defensive backstop. The matcher only ever carried the WebFetch/WebSearch half. The ToolSearch and mcp mail/drive/calendar branches of that predicate were unreachable, so third-party-authored text arriving over those tools was never given the data-not-instructions framing or the injection-shape scan. Merge the two matcher entries into one alternation covering all four classes. The in-code gate still does the precise filtering, so unrelated MCP servers cost one neutral passthrough and nothing more. Also add dropbox to the gate: file content pulled from a sync service is attacker-writable for the same reason drive already is (shared folders, file requests).
The row that teaches how to resolve the Algorithm version named 8.1.0 while ALGORITHM/LATEST reads 8.4.0 — three versions of drift in the one place a reader looks up the answer. Line 26 of the same file was already correct, so this was a single missed edit rather than a systemic lag.
Images are downscaled to ~1568px on the long edge before the model sees them. On a 2000px-wide page a 40px logo arrives as a smudge, and cropping the delivered screenshot cannot recover it — the detail was destroyed upstream. That is the failure mode behind the appearance-vs-existence rule: a screenshot proves an element exists, not what it looks like. Zoom.ts takes absolute coordinates in the ORIGINAL image, crops from the full-resolution source, and magnifies that region to the image budget, so the pixels spent land on the thing under test. Nearest-neighbour on the upscale, so it magnifies without inventing detail. It never downscales: a region already past the budget is passed through at native size. Prefers `magick` (the binary Capture.sh and VerifyImageProbe.ts already use) and falls back to `ffmpeg` so it works on hosts without ImageMagick. Out-of-bounds, zero-area and negative-origin crops exit 2 with the source dimensions in the message rather than emitting a misleading image.
|
Superseded — split by category and sent upstream to danielmiessler/LifeOS: danielmiessler#1654 (security), danielmiessler#1655 (docs), danielmiessler#1656 (platform drift), danielmiessler#1657 (Interceptor zoom). The delegation wording was corrected before resubmission per the note on danielmiessler#1569. |
|
Full index of the upstream PRs this work became — six in total against danielmiessler/LifeOS. Split out of this PR (the original seven commits, regrouped by category):
Follow-on work from the same session, not part of this PR's commits:
Two corrections were made before resubmission: the delegation wording was made neutral on over-cap spawn behaviour per the note on danielmiessler#1569, and Zoom.ts's |
Seven fixes found by auditing the install payload against Claude Code 2.1.200–2.1.220 and the July 2026 Claude API release notes. Each was developed on its own branch and merged here; every one is verified by execution rather than inspection.
Critical
context: forkskills were silently detached. 2.1.218 changed skills declaringcontext: forkto dispatch as background agents by default. Seven skills declare it and all of them return results inline to the caller, so their output was arriving as a task notification instead of in the invoking turn. Pinsbackground: falseon each.cat /proc/self/environauto-allowed.CREDENTIAL_PATHSguards~/.ssh,~/.aws/credentials, GPG keyrings and.env, but procfs reaches the same secrets by another route. Becausecatis inSEARCH_TOOLS,classifyCommand()took the read-only-command allow branch and printed the whole environment with no prompt. Adds a procfs entry so those reads defer to the permission prompt.Fixes
MCP and ToolSearch results were never injection-scanned.
Safety.hook.tsdocuments the intended wiring — "the matcher only routes WebFetch/WebSearch + the qualifying mcp__ names here" — but the matcher only ever carried the WebFetch/WebSearch half, leaving theToolSearchand mail/drive/calendar branches ofisAttackerWritableSourceunreachable. Merges the matcher into one alternation covering all four classes, and addsdropboxto the gate for the same reasondriveis already on it.CURRENT.opusstill pinnedclaude-opus-4-8. Opus 5 shipped 2026-07-24. This is the driftmodels.tsexists to prevent and the exact case its own header cites. Applied viaUpdateModels.ts --apply.Subagent nesting guidance was inverted. The default moved 5 → 1 → 3 across 2.1.217–2.1.219; the docs still said subagents cannot nest. Also records the two per-session ceilings (200 spawns, 200 web searches) that a long fan-out actually reaches.
Stale version pointer. The row that teaches how to resolve the Algorithm version named 8.1.0 against a LATEST of 8.4.0.
Feature
Interceptor/Tools/Zoom.ts— measured zoom for appearance verification. Images are downscaled to ~1568px on the long edge before a model sees them, so on a 2000px page a 40px logo arrives as a smudge, and cropping the delivered screenshot cannot recover it. Zoom crops from the full-resolution original and magnifies the region to the budget. Nearest-neighbour, so it magnifies without inventing detail; never downscales; prefersmagickand falls back toffmpeg.Verification
classifyCommand()— 8 procfs/credential shapes nowneutral, 8 ordinary reads (/proc/cpuinfo,/proc/meminfo,/proc/sys/*,README.md) stillallow. No regression.WebFetch/WebSearch/ToolSearch/Gmail/Drive/Calendar/Dropbox, skipsBash/Read/Edit/Agent/Indeed/Kiwi. Matcher and in-code gate agree on every case.backgroundlands inside the frontmatter block in each.pinnedModelForEffort("high")→claude-opus-5;isCurrent("claude-opus-4-8")→ false.Not included, deliberately
sandbox.network.strictAllowlist— nosandboxblock is configured, so it would be inert; enabling sandboxing needs a real host allowlist and is a posture decision, not a bug fix.SubagentStophook — looked like a gap, isn't.AgentInvocation.hook.tsrecords that the native payload wrote"unknown"for 5,844 of 5,846 events, which is why lifecycle tracking lives onPreToolUse/PostToolUseof the Agent tool. Wiring it would re-add a known-bad source.effortits primary control and rejectsthinking: disabledabovehigh, so the uniform-highmapping is worth revisiting — but it is a stated directive inmodels.ts, not an oversight, so it stays for the owner to decide.parameter-schema.md,target-types.md,eval-guide.md) still describing abolished modes — deletions, left for the owner.