Skip to content

security(server): record the PEN-2630 (b1) contents audit for prometheus/tempo/linear - #1551

Merged
kkroo merged 2 commits into
masterfrom
security/PEN-2630-seed-coverage-audit
Aug 30, 2026
Merged

security(server): record the PEN-2630 (b1) contents audit for prometheus/tempo/linear#1551
kkroo merged 2 commits into
masterfrom
security/PEN-2630-seed-coverage-audit

Conversation

@allyblockcast

@allyblockcast allyblockcast Bot commented Aug 30, 2026

Copy link
Copy Markdown

Thinking Path

  • Paperclip is the open source app people use to manage AI agents for work
  • Every agent Job pod gets a shared .mcp.json, seeded by the StatefulSet init script, which grants it a fixed set of MCP upstreams
  • PR security(server): enumerate scrub coverage for the shared agent MCP seed, and pin the per-agent override axis it cannot enumerate (PEN-2370) #1530 enumerated those upstreams and forced each into gateway-scrubbed / stdio-not-proxied / unscrubbed, and was careful about what the last one claims: it is a statement about the transport, not a finding about the contents
  • So prometheus, tempo and linear sat with the rationale "unassessed" — nobody had looked at what their response bodies actually hold, or at who is allowed to call them
  • PEN-2630 is PEN-2370 criterion (b1) applied to exactly those three, and it is deliberately an audit, not a scrub: routing them through a proxy before knowing what they disclose would have been guessing
  • This pull request records the audit's outcome in SEED_COVERAGE and points each upstream at the child row that owns its follow-up
  • The benefit is that the CI table stops saying "unassessed" about three upstreams that have now been assessed, and two real findings get owners instead of living in a comment

Linked Issues or Issue Description

What Changed

  • prometheus rationale: "unassessed" → ASSESSED disclosure, ticket repointed PEN-2630PEN-2735
  • linear rationale: "unassessed" → ASSESSED disclosure on the grant axis, ticket repointed PEN-2630PEN-2736
  • tempo rationale: "unassessed" → ASSESSED no-disclosure on contents, transport still unscrubbed and unenforced, ticket repointed PEN-2630PEN-2737
  • Doc comment above SEED_COVERAGE: replaced "the audit is still open" with the outcome, and added the caveat that an ASSESSED no-disclosure rationale is not grounds to reclassify an entry out of unscrubbed

No kind and no URL changed. The diff is rationale strings, ticket ids, and one comment.

The findings, in brief

prometheus — PEN-2370's own material by a second route. PEN-2370 names the control: "after any remediation, go looking for the same material by another route." Chain, each link read from source at the deployed version:

  1. discovery/kubernetes/kubernetes.go:793-797 — kubernetes SD copies every annotation value into __meta_kubernetes_<role>_annotation_<name>
  2. web/api/v1/api.go:999/:1023discoveredLabels is on both Target and DroppedTarget (populated :1145/:1184), and is pre-relabel, so this estate's narrow relabel_configs do not constrain it; dropped targets means deliberately-unscraped workloads still return their full SD label set
  3. prometheus-mcp-server server.py:642-659get_targets returns both arrays verbatim, no projection
  4. monitoring/prometheus-configmap.yaml — 36 of 75 jobs use kubernetes_sd_configs, spanning 12 namespaces incl. penstock and paperclip

kubectl.kubernetes.io/last-applied-configuration embeds the applied spec including inline literal container variable values — the field class #1435 scrubs on the k8s-ro route, reached here on a route that scrubber never sees.

linear — the grant axis, which no response scrubber reaches. The seed entry carries no credential (unlike gbrain, it mints nothing). The endpoint is unauthenticated, matched by no ingress policy, and fronts a workspace-wide Linear credential exposing create_issue, update_issue, add_comment, archive_project. Any pod acts as the Linear identity, unattributably. Same family as PEN-2620 door #7.

tempo — clean, demonstrated not assumed. headersToSpanAttributes, capture_headers, CAPTURE_REQUEST_HEADERS, http.request.header.* and OTEL_INSTRUMENTATION_* are absent estate-wide; instrumentation-pg has no enhancedDatabaseReporting; there is no manual setAttribute in this repo; the tracer is unwired in-cluster. Residual: OTel default url.full includes query strings.

Verification

  • No kind and no URL changed, so every topology assertion in this suite is untouched by construction.
  • Parsed the edited SEED_COVERAGE table and checked it against the two assertions this suite makes on the fields I changed: all four unscrubbed entries match /^(PEN|BLO)-\d+$/ and carry a rationale over the 20-character floor. All 7 entries present, kinds unchanged.
  • tsc --noEmit on the file is clean (only module-resolution errors for vitest / node:, which are absent from the sparse clone I worked in).
  • Not run: the vitest suite itself. It needs a full checkout plus install, which I did not have; CI covers it here. Flagging rather than checking the box.

Method — this matters as much as the result

Per PEN-2370's inherited constraints: no live endpoint was probed and no credential value was retrieved or quoted. Everything above comes from committed manifests (Blockcast/onprem-k8s, Blockcast/paperclip) and from vendor source at the deployed versions. A verification that reproduces the harm is not diligence.

Risks

Low risk to the build. The change is data and prose in a test file; no production code path, no schema, no manifest, no topology.

The real risk is a misreading, which the new doc comment is written to prevent: an ASSESSED no-disclosure rationale could tempt someone to reclassify tempo out of unscrubbed. That would be wrong — the axes are independent, nothing scrubs any of these three, and a contents verdict is a dated measurement. Tempo's rests entirely on sender-side configuration that any in-cluster pod can change by pushing spans directly to an unauthenticated OTLP port, which is precisely why PEN-2737 exists.

Second risk, named honestly: the prometheus finding is filed on the mechanism, not on a confirmed captured secret. Whether a credential value is presently reachable needs some scraped workload using an inline literal rather than a secretKeyRef; in the manifests I read, secrets are consistently secretKeyRef or file mounts. I did not enumerate all twelve namespaces and deliberately did not probe to find out. Per criterion (b2) that argues for fixing it rather than closing it — a channel this broad cannot be declared safe by having checked part of it — but a reader should not take PEN-2735 as "a secret was found".

Model Used

Claude Opus 4.5 (claude-opus-4-5), extended thinking, running as the Paperclip CTO agent via Claude Code with tool use (Bash, file edits, GitHub + Paperclip MCP). Two subagents were dispatched for the manifest sweeps of the Prometheus scrape config and the Tempo/OTel instrumentation; their findings were re-verified against vendor source before being written up here.

Checklist

@allyblockcast

allyblockcast Bot commented Aug 30, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: PEN-2736
🔗 Paperclip issue: PEN-2428
🔗 Paperclip issue: PEN-2737
🔗 Paperclip issue: PEN-2620
🔗 Paperclip issue: PEN-2735
🔗 Paperclip issue: PEN-2370
🔗 Paperclip issue: PEN-2630

1 similar comment
@allyblockcast

allyblockcast Bot commented Aug 30, 2026

Copy link
Copy Markdown
Author

🔗 Paperclip issue: PEN-2736
🔗 Paperclip issue: PEN-2428
🔗 Paperclip issue: PEN-2737
🔗 Paperclip issue: PEN-2620
🔗 Paperclip issue: PEN-2735
🔗 Paperclip issue: PEN-2370
🔗 Paperclip issue: PEN-2630

@allyblockcast

allyblockcast Bot commented Aug 30, 2026

Copy link
Copy Markdown
Author

Hey @allyblockcast[bot]! Before this PR can be reviewed, a few things need attention:

Missing or incomplete:

  • Missing section: ## Thinking Path
  • Missing section: ## What Changed
  • Missing section: ## Risks
  • Missing section: ## Model Used
  • Add the dedup-search checkbox to your PR description and check it once you have searched the GitHub PR list for similar PRs. See the PR template at .github/PULL_REQUEST_TEMPLATE.md and CONTRIBUTING.md → "Before You Start: Search First".

Once updated, push a new commit and these checks will re-run automatically.

— commitperclip

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types; degraded: skill unavailable) + gstack/review (degraded: no nested runtime) + native-codex.
Reviewed head: ed7e315

Critical Issues (0)

Important Issues (0)

Suggestions (1)

  • [native-codex] server/src/__tests__/mcp-seed-scrub-coverage.test.ts:119 — Keep the audit outcome and its owning follow-up tickets synchronized with durable issue links or a checked existence assertion if this table is expected to prevent orphaned tracking references; the current test only validates ticket-string shape.

Strengths

  • The patch preserves the independent transport classification: tempo remains unscrubbed despite the dated no-disclosure contents assessment.
  • The new rationales distinguish demonstrated disclosure mechanisms from unassessed body contents and preserve the no-live-probing constraint.
  • No executable assertions, URLs, or topology classifications changed.

Recommended Action

  1. Address the failing PR metadata quality gate by completing the required description sections and dedup-search checkbox.
  2. Consider the tracking-ticket integrity suggestion opportunistically.

@kkroo kkroo left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Independent approval at exact head ed7e315. Scope is the audit-rationale update; no topology or executable behavior changes. Review metadata check remains a separate required gate.

…eus/tempo/linear

PR #1530 classified these three upstreams as `unscrubbed` and said so honestly:
that is a statement about the transport, not a finding about the contents. Their
rationales read "unassessed" because nobody had looked. PEN-2630 looked.

Outcome, one owning row each:

- prometheus -> PEN-2735, DISCLOSURE. get_targets returns Prometheus's
  activeTargets/droppedTargets verbatim (server.py:642-659, no projection), and
  those carry discoveredLabels (prometheus v3.2.1 api.go:999/:1023, populated
  :1145/:1184). discoveredLabels is pre-relabel, so kubernetes_sd copies every
  annotation value into it (discovery/kubernetes/kubernetes.go:793-797) across
  the 36 SD jobs in this estate, spanning 12 namespaces including penstock and
  paperclip. The kubectl last-applied-configuration annotation embeds inline
  container variable values -- PEN-2370's own material, reached by a route no
  scrubber sits on.

- linear -> PEN-2736, DISCLOSURE on the grant axis, which no response scrubber
  reaches. The seed entry carries no credential; the endpoint is unauthenticated,
  matched by no ingress policy, and fronts a privileged workspace-wide Linear
  credential with write tools. Same family as PEN-2620 door #7.

- tempo -> PEN-2737, NO-DISCLOSURE on contents, demonstrated rather than assumed:
  no instrumentation in this estate captures headers or bodies. But nothing
  enforces that, so the row tracks making it an invariant.

Every link was read from committed manifests and vendor source. No live endpoint
was probed and no credential value was retrieved, per PEN-2370's constraint that
a verification which reproduces the harm is not diligence.

Also pins the reading that matters most for the next person: an ASSESSED
no-disclosure rationale is not grounds to reclassify an entry out of
`unscrubbed`. The axes are independent, and a contents verdict is a dated
measurement -- tempo's rests on sender configuration any pod can change.

No `kind` and no URL changed, so the topology assertions are untouched.

Refs PEN-2630, PEN-2735, PEN-2736, PEN-2737

Signed-off-by: Cto <cto@paperclip.blockcast.net>
@kkroo
kkroo force-pushed the security/PEN-2630-seed-coverage-audit branch from ed7e315 to 7662ded Compare August 30, 2026 07:03

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types; degraded: skill unavailable) + gstack/review (degraded: nested runtime unavailable) + native-codex.
Reviewed head: 7662ded

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The audit outcome preserves the transport/content distinction: all three direct endpoints remain unscrubbed, including Tempo's dated no-disclosure assessment.
  • Each direct upstream now has a distinct owning follow-up ticket, while the existing structural assertions continue to validate the seeded topology.
  • The changed file is documentation and tracking data only; the focused CI test lanes are running for this head.

Recommended Action

  1. No blocking changes requested.

@allyblockcast allyblockcast Bot left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ally — Consolidated PR Review

Lenses: pr-review-toolkit (code, tests, comments, errors, types) + gstack/review + native-codex.
Reviewed head: 44f9f87

Critical Issues (0)

Important Issues (0)

Suggestions (0)

Strengths

  • The patch preserves the distinction between transport coverage and contents assessment.
  • Each audited upstream now points to a dedicated follow-up ticket without changing the executable coverage assertions.
  • The change is confined to audit documentation and tracking metadata; no runtime behavior or topology is modified.

Recommended Action

  1. No blocking changes requested.

@kkroo
kkroo added this pull request to the merge queue Aug 30, 2026
Merged via the queue into master with commit c500b5a Aug 30, 2026
21 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