Skip to content

fix(claude-k8s): carry the source volume's items selector through propagation (BLO-18927) - #1368

Merged
allyblockcast[bot] merged 2 commits into
masterfrom
cto/blo-18927-preserve-secret-volume-items
Aug 15, 2026
Merged

fix(claude-k8s): carry the source volume's items selector through propagation (BLO-18927)#1368
allyblockcast[bot] merged 2 commits into
masterfrom
cto/blo-18927-preserve-secret-volume-items

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown

Carries the source volume's items: key selector through secret-volume propagation in the vendored claude_k8s adapter.

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Agent runs execute in Kubernetes Job pods, and the claude_k8s adapter templates every one of those pods from the paperclip server's own pod spec
  • Part of that templating replays the server's mounted secret volumes onto the agent pod, which is why BLO-18927 exists: it narrows what agent pods receive
  • getSelfPodInfo() captured only secretName/mountPath/defaultMode per volume, dropping the items: key selector, and the mount site then rebuilt the volume without one
  • A source mount that deliberately projects ONE key out of a multi-key Secret was therefore re-expanded into EVERY key of that Secret on the agent pod
  • This pull request preserves the selector at both the capture and mount halves, so a scoped projection stays scoped after propagation

Linked Issues or Issue Description

Refs BLO-18927 (AC-3, "items: selectors are preserved"). Related: BLO-22514.

No GitHub issue exists — Paperclip issues are not mirrored as numbered GH issues. Stating the problem in full:

vendor/paperclip-adapter-claude-k8s/src/server/k8s-client.ts discovers the server pod's secret volumes and records four fields per volume. V1SecretVolumeSource.items is not among them. job-manifest.ts then reconstructs each volume for the agent Job from those four fields, so the selector cannot be re-emitted even in principle.

Measured against the live cluster: paperclip-api mounts authbot-mcp-consumer-service-keys with items: [gbrain-plugin-service-key] — exactly one key. Agent pods receive all 7 keys of that Secret (figma, gbrain, gbrain-plugin, google-docs, grafana, linear, webflow). The agent pod ends up holding more key material than the container the mount was copied from, which inverts the intent of the mount.

  • I searched the GitHub PR list (open + recently closed) for similar PRs and confirmed this is not a duplicate. Searched items secret volume selector (0 results) against a positive control of vendor claude-k8s (44 results), so the zero is a real absence rather than a broken query.

What Changed

  • src/server/k8s-client.ts — added items?: k8s.V1KeyToPath[] to SelfPodSecretVolume and populated it in getSelfPodInfo(), copying the array ([...items]) rather than aliasing the cached pod spec.
  • src/server/job-manifest.ts — spread items back onto the reconstructed volume when the source had one.
  • PROVENANCE.md — updated the CI-enforced integrity hash and added the Local-modifications row.

Two deliberate choices worth reviewing rather than waving through:

  1. optional: true stays hardcoded at the mount site. The source's optional is not propagated. Carrying it through could turn a propagated Secret that is absent in the agent namespace into a hard Job failure — a behaviour change with availability blast radius, and not what this PR is for. The comment at the mount site records this as intentional.
  2. items is optional (items?:) rather than required-with-undefined. This mirrors V1SecretVolumeSource.items upstream and keeps the diff to the behaviour change; the required-with-undefined shape used by the neighbouring defaultMode would have forced items: undefined noise into four unrelated existing tests.

Verification

Run in vendor/paperclip-adapter-claude-k8s:

$ ./node_modules/.bin/tsc --noEmit     # exit 0, no output
$ ./node_modules/.bin/vitest run
 Test Files  13 passed (13)
      Tests  726 passed (726)

The integrity-hash procedure was validated before being trusted: on the pristine tree it reproduces the previously recorded 75eba541… exactly, which is what makes the new fcce97c7… meaningful rather than just different.

Five tests added — three on the capture half (newly possible; k8s-client.test.ts did not exist when this fix was first written), two on the mount half. All three guards are mutation-checked, so none of them passes for an unrelated reason:

mutation expected observed
drop the items spread at the mount site mount-half test fails ✅ 1 failed / 177 passed — preserves the source volume's items selector…
drop items at the capture site capture-half test fails ✅ 1 failed / 5 passed — carries the source volume's items selector through capture
alias vol.secret.items instead of copying it aliasing test fails ✅ 1 failed / 5 passed — copies the items array rather than aliasing the pod spec

All three mutations were reverted; the full suite is green on the committed tree.

Risks

Low, and strictly narrowing — but the scope limit is the important part.

  • This does not close BLO-22514, and should not be read as doing so. That issue needs an allowlist that drops server-only credentials (PAPERCLIP_AGENT_JWT_SECRET, DATABASE_URL, GITHUB_APP_PRIVATE_KEY) from the inherited env and volume set. This PR only stops a scoped projection from being widened; a volume that already propagates wholesale still propagates wholesale. PR feat(vendor): bring claude_k8s adapter in-tree and retire CLAUDE_K8S_REF (BLO-17980) #1092 deferred the allowlist deliberately because dropping the wrong key breaks every agent, and that reasoning is unchanged.
  • Direction of change is one-way. Agent pods can only ever see the same keys or fewer, never more. The only way this breaks an agent is if something was depending on a key it was never meant to receive — in which case the dependency is the bug, and it fails visibly as a missing file rather than silently.
  • Volumes with no selector are untouched, which is the common case and is covered by its own test asserting items stays undefined and optional stays true.
  • PROVENANCE.md is excluded from the integrity hash by design, so updating the table alongside the hash cannot itself perturb the value CI checks.

Model Used

claude-opus-5[1m] via Claude Code, extended thinking with tool use — running as the Paperclip CTO agent.

Checklist

  • I have included a thinking path that traces from project context to this change
  • I have specified the model used (with version and capability details)
  • I have checked ROADMAP.md and confirmed this PR does not duplicate planned core work
  • I have searched GitHub for duplicate or related PRs and linked them above
  • I have either (a) linked existing issues with Fixes: # / Closes # / Refs # OR (b) described the issue in-PR following the relevant issue template
  • I have run tests locally and they pass
  • I have added or updated tests where applicable
  • If this change affects the UI, I have included before/after screenshots — n/a, no UI surface
  • I have updated relevant documentation to reflect my changes — PROVENANCE.md hash + local-modifications row
  • I have considered and documented any risks above
  • All Paperclip CI gates are green — pending first run on this branch
  • Greptile is 5/5 with no open P2s, recommendations, or follow-ups — pending review
  • I will address all Greptile and reviewer comments before requesting merge

🤖 Generated with Claude Code

…pagation (BLO-18927)

getSelfPodInfo() captured only secretName/mountPath/defaultMode from each
inherited secret volume, and buildJobManifest() rebuilt the volume without a
key selector. A source mount projecting ONE key out of a multi-key Secret was
therefore re-expanded into EVERY key of that Secret on the agent Job pod.

Measured live: paperclip-api projects gbrain-plugin-service-key alone out of
authbot-mcp-consumer-service-keys, while agent pods received all 7 keys — the
agent held more key material than the container the mount was copied from.

optional: true stays hardcoded at the mount site by design, so a Secret absent
in the agent namespace still cannot hard-fail the Job.

Refs BLO-18927 AC-3. Does NOT close BLO-22514, which needs the env allowlist.

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-22514
🔗 Paperclip issue: BLO-18927

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 15, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: BLO-22514
🔗 Paperclip issue: BLO-18927

Co-Authored-By: Claude <noreply@anthropic.com>
@allyblockcast
allyblockcast Bot added this pull request to the merge queue Aug 15, 2026
Merged via the queue into master with commit 038abe3 Aug 15, 2026
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant