Skip to content

fix(policies): evaluate redacted materials against the content that was stored - #3383

Merged
jiparis merged 7 commits into
chainloop-dev:mainfrom
jiparis:jiparis/pfm-7073-redaction-send-redacted-content-to-policies
Sep 1, 2026
Merged

jiparis merged 7 commits into
chainloop-dev:mainfrom
jiparis:jiparis/pfm-7073-redaction-send-redacted-content-to-policies

Conversation

@jiparis

@jiparis jiparis commented Aug 28, 2026 •

Copy link
Copy Markdown
Member

Secret redaction (#3368) only rewrites the copy of a CHAINLOOP_AI_CODING_SESSION that leaves the machine. Policies were still evaluated against the untouched original on disk, having access to any potential secret that could have leaked into the AI session.

The invariant now implemented is that the policy engine receives exactly the bytes that were stored.

Changes

  • Crafters that do not store the artifact verbatim implement a transformCrafter seam and report the bytes they stored. materials.Craft surfaces them as CraftResult.EvaluableContent, and the crafter passes them to both policy verifiers via the new policies.WithMaterialContent per-call option.
  • Content resolution fails closed: a material annotated chainloop.material.redacted=true returns ErrRedactedContentRequired rather than falling back to the file on disk. This is uniform across CAS backends, inline included.
  • chainloop policy devel eval threads the same content through, so a policy is developed against the input the real run produces.
  • The bundled testdata policy now detects [REDACTED:<rule>] placeholders and the redaction annotations, since the raw pattern it looked for is no longer present in its input.

Materials that were not transformed are unaffected and hold nothing extra in memory: their content still resolves from the inline copy or the file on disk. This also closes a gap for skipUpload materials, whose sanitized bytes were previously retained nowhere at all.

Notable consequences

A redacted material's policy input can no longer be reconstructed from persisted crafting state alone, so a future push-time or server-side material evaluation must plumb the bytes through as well. materials.Craft now returns *CraftResult and policies.Verifier.VerifyMaterial takes variadic options — both breaking for external importers of these packages.

Not covered here

Two gaps remain that are properties of redaction rather than this plumbing: --skip-secret-redaction stores the session as captured, and secrets counted as report.Unlocated (landing in a protected path, or spanning two JSON leaves) are not redacted in the stored copy either. Detecting placeholders in the upstream ai-coding-session-no-secrets policy is the second part of the issue and lives in another repository; that policy should keep its pattern matching alongside the placeholder check, since pattern matching remains the only thing that catches the secrets redaction could not reach.

AI disclosure

Produced with assistance from Claude Code; every commit carries an Assisted-by: Claude Code trailer.

Review in cubic

…as stored

Secret redaction only rewrote the copy of an AI coding session that leaves
the machine; policies were still evaluated against the untouched original on
disk. Since a policy is user-authored Rego that can send what it reads to an
allowed hostname, that made the policy engine a path for the very credentials
redaction exists to remove. It also does not survive server-side evaluation,
where the redacted copy is the only content available.

Crafters that do not store the artifact verbatim now report the bytes they
stored through materials.Craft, and the crafter passes them to both policy
verifiers. Content resolution fails closed: a material annotated as redacted
refuses to resolve at all without its sanitized copy, uniformly across CAS
backends, rather than falling back to the file on disk. This also covers
skip-upload materials, whose sanitized bytes were previously retained nowhere.

Nothing is held in memory for materials that were not transformed: those keep
resolving their content from the inline copy or the file, as before.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>
@chainloop-platform

chainloop-platform Bot commented Aug 28, 2026 •

Copy link
Copy Markdown
Contributor

AI Session Checks — 🟡 83% · ⚠️ 1 failing

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟡 83% 1 ⚠️ 1 100% AI / 0% Human 99 +1142 / -557 72h53m51s

🟡 83% — 100% AI — ⚠️ 1 policies failing

Aug 28, 2026 11:10 UTC · 72h53m51s · $150.86 · 1.4k in / 414.2k out · claude-code 2.1.250 (claude-opus-5)

View session details ↗

Change Summary

  • Routes redacted AI-session content through crafting and policy evaluation instead of rereading the source file.
  • Simplifies the related APIs around CraftResult.Content and VerifyMaterial(..., content).
  • Updates tests and local policy fixtures, then fixes a review-caught supplied-content edge case.

AI Session Overall Score

🟡 83% — Strong session, but missing diff coverage and a review-caught bug warrant review.

AI Session Analysis Breakdown

🟢 96% · verification

🟢 Repeated automated checks were backed by manual end-to-end policy evaluation. · High Impact

🟢 93% · context-and-planning

🟢 The AI explored, replanned, and got approval before implementation. · High Impact

🟢 91% · user-trust-signal

🟢 The user kept delegating follow-on work through PR creation and review handling. · Medium Impact

🟡 74% · solution-quality

🟠 A review caught a supplied-content edge case that needed a follow-up fix before landing cleanly. · Medium Severity

💡 Exercise generalized paths against adjacent kinds, not only the currently reachable producer.

🟡 72% · alignment

🟠 The STRING-based explanation and test described an impossible case and had to be corrected. · Medium Severity

💡 Before explaining an invariant, re-check that the cited input shape can actually occur.

abstained · scope-discipline

🟡 Scope discipline was not assessed because no rendered diff was available in this workdir. · Low Severity

Missing criteria: scope-discipline


File Attribution

████████████████████ 100% AI / 0% Human

Status Attribution File Lines
modified ai pkg/attestation/crafter/api/attestation/v1/crafting_state_test.go +195 / -96
modified ai pkg/attestation/crafter/api/attestation/v1/crafting_state.go +91 / -57
modified ai pkg/policies/policies_test.go +115 / -10
modified ai pkg/attestation/crafter/materials/materials.go +73 / -43
modified ai pkg/attestation/crafter/materials/chainloop_ai_coding_session.go +54 / -48
modified ai pkg/attestation/crafter/crafter_test.go +82 / -0
modified ai pkg/attestation/crafter/materials/chainloop_ai_coding_session_redaction_test.go +56 / -25
modified ai pkg/policies/policy_groups_test.go +73 / -7
modified ai pkg/policies/policies.go +39 / -33
modified ai app/cli/internal/policydevel/eval.go +29 / -24
modified ai pkg/attestation/crafter/crafter.go +22 / -16
modified ai pkg/attestation/crafter/materials/craft_result_external_test.go +37 / -1
modified ai pkg/attestation/crafter/materials/craft_result_test.go +36 / -1
modified ai app/cli/internal/policydevel/eval_test.go +24 / -9
created ai pkg/attestation/crafter/testdata/policies/ai_session_no_secrets.yaml +25 / -0
modified ai app/cli/internal/policydevel/testdata/ai-coding-session-no-secrets-policy.yaml +16 / -5
modified ai pkg/policies/policy_groups.go +9 / -7
modified ai pkg/attestation/crafter/materials/materials_test.go +10 / -5
modified ai pkg/attestation/crafter/materials/chainloop_ai_security_context_test.go +5 / -5
modified ai pkg/attestation/crafter/materials/radamsa.go +4 / -5
modified ai pkg/attestation/crafter/materials/evidence_test.go +4 / -4
modified ai pkg/attestation/crafter/materials/oci_image.go +4 / -4
modified ai pkg/attestation/crafter/materials/oci_image_test.go +4 / -4
modified ai pkg/attestation/crafter/materials/artifact_test.go +3 / -3
modified ai pkg/attestation/crafter/materials/cyclonedxjson_test.go +3 / -3

…and 74 more file(s).


Policies (4, 1 failing)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-06ec99 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-06ec99 -
⚠️ Failed ai-config-no-secrets ai-coding-session-06ec99
  • Potential secret (Quoted API key/password) found in session content [turn=436, source=tool_result, line=10, value=Secret ...tY6"]
  • Potential secret (Quoted API key/password) found in session content [turn=58, source=tool_result, line=28, value=Secret ...tY6"]
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-06ec99 -

Security Checks — ⚠️ 1 failing

⚠️ secret-scan — 1 failing

Status Policy Messages
⚠️ Failed secrets-detection Gitleaks secret detected 779c37d:pkg/attestation/crafter/crafter_test.go:generic-api-key:826, secret: kQ7zXn2V...)

✅ sast-scan

Status Policy Messages
✅ Passed owasp-top10-2025 -
✅ Passed sast -
✅ Passed cwe-top25 -
✅ Passed cwe-top26-40-cusp -

✅ 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

View attestation ↗


Powered by Chainloop and Chainloop Trace

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 17 files

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

Comment thread pkg/attestation/crafter/api/attestation/v1/crafting_state.go Outdated
@jiparis
jiparis marked this pull request as draft August 28, 2026 17:12
Reporting the bytes a crafter stored in place of the artifact was bolted on as a
second, optional interface method alongside Craft. Fold it into Craft itself:
Craftable returns a CraftResult carrying the material and, when the artifact was
not stored verbatim, the bytes that replaced it.

One method instead of two, so a crafter cannot report content through a path the
generic craft flow does not take, and the capability is visible in the interface
rather than discovered by type assertion. Every crafter but the AI coding session
stores the artifact as it found it and leaves Transformed nil.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57
…Content

The single-argument GetEvaluableContent had no callers left outside tests once
both policy verifiers moved to the variant taking an explicit content source, so
keeping it as a delegating wrapper only offered a second way in — one that
resolves a redacted material's content from the file on disk and fails.

Give GetEvaluableContent the content parameter and drop the wrapper. Callers that
have nothing to supply pass nil, which is the behaviour the old signature had.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57
The field holds the content the crafter stored, which is what every consumer
wants from it. Naming it after the transformation described how it came to exist
rather than what it is.

Locals that carried the field's value are renamed to match; the prose that
describes crafters transforming an artifact is unchanged.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57
…option

The functional-option machinery existed to carry a single []byte, which is more
ceremony than the value deserves: a type, a constructor, an accumulator and an
option struct so that one call site could pass one slice.

VerifyMaterial takes the content as a plain parameter instead. Callers that have
nothing to supply pass nil, which reads no worse than omitting an option and
makes the alternative visible in the signature.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57
…kinds

Supplied content was selected inside the branch that resolves an artifact, so a
material carrying no artifact — a container image, a string — dropped it and fell
through to the empty "{}" projection, handing policies an empty object instead of
the bytes the caller asked to evaluate.

Choose supplied content before resolving the artifact, which is where the
fail-closed check for redacted materials already sits for the same reason.

No live caller was affected: content is only supplied for
CHAINLOOP_AI_CODING_SESSION today, which is artifact-based. Reported by cubic on
PR chainloop-dev#3383.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

All reported issues were addressed across 2 files (changes from recent commits).

Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread pkg/attestation/crafter/api/attestation/v1/crafting_state.go
@jiparis
jiparis marked this pull request as ready for review August 31, 2026 09:18
@jiparis
jiparis requested a review from a team August 31, 2026 10:34
Comment thread pkg/attestation/crafter/api/attestation/v1/crafting_state.go
…tent first

A STRING material cannot supply content: the value is the material, with no
artifact to transform. Citing it as motivation described something impossible,
and the test asserted semantics for an input that cannot occur.

State the invariant instead — supplied content is authoritative for every kind,
not only those that resolve from an artifact — and keep the one case that
illustrates it.

Assisted-by: Claude Code
Signed-off-by: Jose I. Paris <jiparis@chainloop.dev>

Chainloop-Trace-Sessions: 06ec9990-4ad5-4b3c-ad86-2421b24dba57
@jiparis
jiparis merged commit 3fe8434 into chainloop-dev:main Sep 1, 2026
15 of 17 checks passed
@jiparis
jiparis deleted the jiparis/pfm-7073-redaction-send-redacted-content-to-policies branch September 1, 2026 14:06
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.

3 participants