fix(controlplane): cap the policy evaluations inlined by the workflow-run View API - #3408
Conversation
…-run View API A single attestation can carry a six-figure number of policy violations. Inlining one in a View response held the payload in memory several times over -- CAS download buffer, decoded bundle, regrouped evaluations and response protos -- which was enough to exhaust the control plane. The View API now asks the CAS for the bundle size before downloading it. Bundles above a configurable cap, defaulting to 10MiB, are returned as a reference carrying the digest, size and reason instead of the evaluations themselves, and are never downloaded. The same reference is returned when the bundle cannot be resolved or decoded, so no failure path falls back to an unbounded download. Status and counters continue to be served from the predicate, so gating, bypass and violation counts stay complete regardless. The CLI renders the counters plus the artifact download command in place of the policy table, and exposes the reference in its JSON output. Attestation crafting and push are unchanged. Assisted-by: Claude Code Signed-off-by: Javier Rodriguez <javier@chainloop.dev> Chainloop-Trace-Sessions: d400245f-b8d2-4805-998e-b23f9be4a7fb
AI Session Checks — 🟢 92% · ✅ 0 failing
|
| Status | Policy | Material | Messages |
|---|---|---|---|
| ✅ Passed | ai-config-ai-agents-allowed |
ai-coding-session-d40024 |
- |
| ✅ Passed | ai-config-no-dangerous-commands |
ai-coding-session-d40024 |
- |
| ✅ Passed | ai-config-no-secrets |
ai-coding-session-d40024 |
- |
| ✅ Passed | ai-config-mcp-servers-allowed |
ai-coding-session-d40024 |
- |
Security Checks — ✅ 8 passing
✅ secret-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | secrets-detection |
- |
✅ sast-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | sast |
- |
| ✅ Passed | cwe-top25 |
- |
| ✅ Passed | cwe-top26-40-cusp |
- |
| ✅ Passed | owasp-top10-2025 |
- |
✅ iac-scan
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | iac-misconfiguration |
- |
PR info
| Status | Policy | Messages |
|---|---|---|
| ✅ Passed | pr-description-required |
- |
| ✅ Passed | pr-user-story-linked |
- |
⏭️ 2 scans not applied
| Scan | Reason |
|---|---|
vulnerability-scan |
no manifest/lockfile changed |
github-actions-scan |
no workflow files changed |
Powered by Chainloop and Chainloop Trace
There was a problem hiding this comment.
All reported issues were addressed across 23 files
Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.
Re-trigger cubic
…eived The size check trusted the CAS backend's metadata in two ways it should not have. A reported size of zero fell through the cap comparison and started an unbounded download: some backends omit the content length and the proto getter then yields zero, so zero means unknown rather than empty. And a correct-looking size was never reconciled with the bytes actually received, so an under-reporting backend could still exhaust the cap. Zero-size responses are now reported as unavailable without a download, and the download target is a writer that refuses to grow past the cap and records that it did, so the bound holds against the bytes received rather than the size claimed. Assisted-by: Claude Code Signed-off-by: Javier Rodriguez <javier@chainloop.dev> Chainloop-Trace-Sessions: d400245f-b8d2-4805-998e-b23f9be4a7fb
Context
Related to PFM-7045.
A single attestation can carry a six-figure number of policy violations. When the workflow-run
ViewAPI inlined one of those, it held the payload in memory several times over — the CAS download buffer, the decoded bundle, the regrouped evaluations, and the response protos — which was enough to exhaust the control plane.What changed
The
ViewAPI now asks the CAS for the policy-evaluation bundle size before downloading it. Bundles larger than a configurable cap are never downloaded: the response carries a reference with the digest, size and reason instead of the evaluations themselves. Bundles under the cap behave exactly as before.The same reference is returned when the bundle cannot be resolved or decoded, so no failure path falls back to an unbounded download. Where a bundle is already cached, it is still served without any CAS round trip.
Status and counters continue to be derived from the attestation predicate rather than from the evaluations, so policy status, gating, bypass and violation counts stay complete regardless of whether the evaluations were inlined.
New pieces:
AttestationItem.policy_evaluations_ref, an additive field carrying digest, size, media type and the reason the evaluations were left out. Clients that ignore it see an empty evaluations map, which is already how an unresolvable bundle behaved.Attestations.policy_evaluations_max_inline_bytesin the control-plane config, defaulting to 10MiB.Describeon the control plane's CAS downloader, wrapping the metadata endpoint the CAS already exposed.In the CLI,
workflow run describerenders the policy counters and thechainloop artifact downloadcommand to fetch the bundle in place of the policy table, and exposes the reference in its JSON output. It also surfaces the canonical policy status summary the server has been sending but the CLI never displayed.Attestation crafting and push are unchanged: the bundle format, the CAS upload and the signed predicate are all untouched. This is a read-side change only.
Scope
Two related paths are deliberately left alone. Attestations whose evaluations live inline in the predicate — runs on an inline CAS backend, and anything pushed before the CAS offload — carry no reference to gate on and remain uncapped. The repeated predicate extraction on the ingest path is also unchanged. Both belong to the wider PFM-7045 work.
AI disclosure
This contribution was produced with AI assistance (Claude Code). The affected commit carries an
Assisted-by: Claude Codetrailer.