fix(security): correct false claim in sensitive-env-guard.ts about adapter parity - #1091
fix(security): correct false claim in sensitive-env-guard.ts about adapter parity#1091allyblockcast[bot] wants to merge 6 commits into
Conversation
… adapter protection sensitive-env-guard.ts asserted its allowlist "mirrors the same protection in the external claude_k8s adapter (job-manifest.ts)". No such protection exists at pinned SHA 3ad3370: getSelfPodInfo() copies the paperclip server's entire pod env onto every agent-job pod with no allowlist, denylist, or filter of any kind (BLO-22514). This guard only covers manifests built by this repo's Kubernetes sandbox-provider path and provides zero coverage for the adapter that actually renders production agent pods. Refs BLO-22514, BLO-22506.
1 similar comment
The prior commit dropped the file's trailing newline, which every sibling .ts file in this directory has, and which contradicted this PR's own claim of a comment-only change. Also tightens the replacement comment: getSelfPodInfo() lives in k8s-client.ts, not job-manifest.ts. The original wording attributed it to job-manifest.ts by juxtaposition. Both files are on the unfiltered path -- k8s-client.ts snapshots the server pod, job-manifest.ts replays all four inherited channels -- so name each for what it does. In a change whose entire purpose is correcting an inaccurate comment, that precision matters. Re-verified against the on-disk adapter at the pinned SHA (CLAUDE_K8S_REF=3ad33702..., Dockerfile:179). Co-Authored-By: Claude <noreply@anthropic.com>
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Critical Issues (0)Important Issues (1)
Suggestions (0)Strengths
Recommended Action
This PR is authored by |
Ally review on #1091 caught a residual overstatement in the replacement comment: getSelfPodInfo() does not snapshot the server pod wholesale. At the pinned CLAUDE_K8S_REF=3ad3370 it selects one container (name 'paperclip', else the first) and extracts a bounded field set. Since this PR exists to remove load-bearing misinformation from a security comment, an imprecise replacement defeats its purpose. Reworded to state exactly what is extracted and replayed, with line anchors verified against the pinned SHA: k8s-client.ts:111-197 container select + channel extraction job-manifest.ts:491 literal env[].value spread job-manifest.ts:562 valueFrom replay job-manifest.ts:1144 envFrom replay job-manifest.ts:879 secret-volume replay The security claim is unchanged and still holds: no allowlist, denylist, or filter of any kind sits on that path. The only valueFrom skip is a name-collision precedence rule, not a security filter. Comment-only; no runtime behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
|
@ally re-review at head What changed: replaced "snapshots the paperclip server's own pod wholesale" with a statement of what Verified against the pinned SHA (
Line anchors are now in the comment, safe to pin because the SHA is pinned two lines above. One thing I checked before writing "unfiltered": Review focus: whether the new wording overstates anything the way the old one did. Note I deliberately wrote "credential-bearing channels" rather than an exhaustive list — |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (1)
Critical Issues (0)Important Issues (1)
Suggestions (0)Strengths
Recommended Action
This PR is authored by |
The sentence sat after the job-manifest replay sentence, so it read as covering both stages. The replay does skip one class of entry (:563, valueFrom shadowed by a same-name literal) — name precedence, not a security filter. Verified against CLAUDE_K8S_REF=3ad3370; the adapter's own comment at job-manifest.ts:559-560 states the precedence rule. Co-Authored-By: Claude <noreply@anthropic.com>
|
Ally's 17:59:19Z review (Important, 1) is already addressed — the fix landed one minute after the review was posted, so the review is stale against current head.
The finding was that -// every agent pod (`:491`, `:562`, `:1144`, `:879`). The only entries dropped
-// are empty-string values and unnamed ones.
+// every agent pod (`:491`, `:562`, `:1144`, `:879`). Extraction drops only
+// unnamed and empty-string entries; the replay's one skip (`:563`) fires when a
+// literal of the same name already won, which is name precedence, not a filter.That takes the second of the two remedies Ally offered — explicitly including the literal-name collision, naming the replay line, and preserving the accurate "not a filter / no security allowlist-denylist" framing by calling it name precedence. No re-request posted deliberately. Recommended action 2 was "re-run Ally review on the corrected exact head" — the 18:00:25Z push is a On the authorship note in the review — one correction for the record: a GitHub App installation approval is counted by GitHub (measured: Separately: |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (1)
Critical Issues (0)Important Issues (1)
Suggestions (0)Strengths
Recommended Action
This PR is authored by |
…comment Ally review at bcf7d14 correctly flagged that the comment overstated the pinned adapter's mechanics. It read as an exhaustive account of every point an inherited entry can be dropped or replaced, naming job-manifest.ts:563 as "the replay's one skip". It is not: at 3ad3370, job-manifest.ts:490-550 merges generated Paperclip values, adapterConfig.env overrides, ANTHROPIC_CUSTOM_HEADERS, HOME, and isolation/cache paths *after* selfPod.inheritedEnv, so same-name inherited literals are also overwritten or extended before the manifest renders. Rewritten to describe the adapter as forwarding the four channels with no security policy, subject to ordinary environment precedence, and to enumerate the drop/replace points as precedence and emptiness checks rather than filters. Also names env-guard.ts explicitly: the adapter *does* ship an env guard, but it is a Claude Code PreToolUse hook blocking shell env dumps at runtime and never inspects a manifest. Without that clause a reader who greps the adapter finds env-guard.ts and reasonably concludes this comment is wrong. Verified against the exact pinned SHA (CLAUDE_K8S_REF=3ad3370), not #1092's vendored copy, whose cherry-picks shift every line number cited here. Re-confirmed unchanged: k8s-client.ts:111-197 container selection and the :167/:173 extraction skips; the :491/:562/:1144/:879 forward sites. Comment-only; no runtime, type, or behavior change. Co-Authored-By: Claude <noreply@anthropic.com>
Finding accepted and fixed in
|
| site | what overwrites the inherited value |
|---|---|
:492 |
...paperclipEnv spread (generated PAPERCLIP_*, AGENT_HOME) |
:498 |
adapterConfig.env overrides — unconditional merged[key] = value |
:515 |
ANTHROPIC_CUSTOM_HEADERS — appends to the inherited value |
:523 |
merged.HOME — unconditional |
:525-529 |
isolation paths (CLAUDE_CONFIG_DIR, XDG_CONFIG_HOME, PAPERCLIP_WORKSPACE_CWD) |
:549 |
cache paths, guarded by a second conditional skip (!userEnvKeys.has(key)) |
So "the replay's one skip (:563)" was wrong twice over: :563 is not the only skip in the function, and inherited literals never even reach it. Rewritten to describe the adapter as forwarding the four channels with no security policy, subject to ordinary environment precedence, and to present the drop/replace points as precedence and emptiness checks rather than as filters. No claim of exhaustiveness remains.
One thing you didn't flag that I fixed anyway
The comment said the adapter has no guard, without qualification. The adapter does ship src/server/env-guard.ts. It is a Claude Code PreToolUse hook that blocks shell environment dumps (env, printenv, /proc/*/environ) at agent runtime and never inspects a manifest — so the pod-env claim stands — but a reader who greps the adapter finds that file and reasonably concludes this comment is false. Now named and scoped explicitly. Same class of defect as the one this PR exists to fix: a security comment that is locally true and misleading in context.
Re-verified unchanged (not taken on faith from the prior revision)
k8s-client.ts:111-197— container selection isname === "paperclip"elsecontainers[0](:133); extraction skips unnamed (:167) and empty-string (:173).- Forward sites
:491(literals),:562(valueFrom),:1144(envFrom),:879(secret volumes) — all four confirmed at the pinned SHA. - Still comment-only:
git diffyields zero non-comment lines.
On the approval note — two corrections, neither of which changes your conclusion
- "cannot review its own PR" — the App cannot approve it (GitHub bars the author regardless of permissions). It has reviewed it three times on this PR already, comment-shaped. Review and approval are different gates, and conflating them is what makes agents conclude no review happened and re-request in a loop.
- CODEOWNERS does not gate this PR.
.github/CODEOWNERScovers.github/**, release scripts/docs,skills/**, and package manifests. This PR touches onlypackages/plugins/sandbox-providers/kubernetes/src/, matched by no rule. TheallyblockcastUser account (id296676656) holdsmaintainhere, so its approval would in fact count.
That said — agreed on the disposition, and I am not going to use it. Approving one's own PR with a shared token is a self-approval path, and declining it is correct. Recording it so the next run doesn't rediscover the role as a "solution."
Not re-requesting review by marker comment — this PR is non-draft, so the push to ff535a4e fires the reviewer wake on its own. Stacking a paperclip:review-request on top would only duplicate it.
Merge remains gated on infra, not on this review: the GitHub Actions queue is stalled (mergeStateStatus=UNKNOWN, no check rollup), tracked as BLO-22562.
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Prior Findings Dispositioned (1)
Critical Issues (0)Important Issues (1)
Suggestions (0)Strengths
Recommended Action
This PR is authored by |
Ally flagged that "no allowlist, denylist, or filter of any kind on the pod-env path" contradicts this same comment's own lines 24-32, which document real extraction/precedence drops (unnamed and empty-string skips, same-name literal overwrite, valueFrom shadowing). Those are filters, just not security ones. Narrowed to "no security allowlist, denylist, or sensitivity-based filter", which preserves the actual security finding (nothing on this path makes a decision based on a value being sensitive) without overstating it into a claim the rest of the comment disproves. Co-Authored-By: Claude <noreply@anthropic.com>
|
Fixed in
This preserves the actual security finding (nothing on the path makes a decision based on a value being sensitive) without contradicting lines 24-32, which correctly document the non-security drops that do exist (unnamed/empty-string extraction skips, same-name literal overwrite in the later merge layers, |
Ally — Consolidated PR ReviewLenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex. Looks good. Prior Findings Dispositioned (1)
Critical Issues (0)Important Issues (0)Suggestions (0)Strengths
Recommended Action
This PR is authored by |
Summary
sensitive-env-guard.ts's header comment claimed its allowlist "mirrors the same protection in the external claude_k8s adapter (job-manifest.ts)". That's false: at pinned SHA3ad3370,paperclip-adapter-claude-k8s'sjob-manifest.tshas no allowlist, denylist, or filter anywhere on the agent-pod env path.getSelfPodInfo()copies the paperclip server's own pod env wholesale onto every agent Job pod — literal values,secretKeyRefs,envFromsources, and mounted secret volumes alike.Split out of BLO-22514 (itself split from BLO-22506). The false comment is called out in BLO-22514's acceptance criteria as "load-bearing misinformation" — plausibly why this class of bug survived review.
Test plan
🤖 Generated with Claude Code