Skip to content

feat(cli): accept an archive of dranzer reports as one material - #3310

Merged
javirln merged 1 commit into
chainloop-dev:mainfrom
javirln:javier/pfm-6467-cli-explode-archive-materials-in-chainloop-att-add-ziptargz-2
Jul 28, 2026
Merged

feat(cli): accept an archive of dranzer reports as one material#3310
javirln merged 1 commit into
chainloop-dev:mainfrom
javirln:javier/pfm-6467-cli-explode-archive-materials-in-chainloop-att-add-ziptargz-2

Conversation

@javirln

@javirln javirln commented Jul 28, 2026

Copy link
Copy Markdown
Member

Summary

A single CERT/CC dranzer run emits one plain-text report per test mode (-b, -p, -s, -t), so the evidence arrives as an archive rather than a single file. CERTCC_DRANZER now accepts either a single report or a zip/tar.gz holding several, following the RADAMSA_CRASHES precedent: the archive is recorded whole as one material under the contract's declared material name, with the number of reports it holds recorded as an annotation.

Because the policy engine reads dranzer content, recording the archive is not sufficient on its own — parsing archive bytes as text yields an empty report, which makes a policy skip rather than evaluate. The policy-input projection therefore aggregates the archive's entries, summing the run counters and unioning the per-object and finding data, and carries a per-report breakdown alongside so a policy can be precise about which mode reported what. The aggregate is promoted to the top level of the projection, so policies that read tool, summary and findings behave the same whether the material was a single report or a bundle.

Archive container detection and walking move into a new archiveio leaf package, so the crafters and the policy-input projections share one implementation of the entry-count, uncompressed-size and path-traversal guards rather than each carrying their own. Two gaps in those guards are closed as part of the move: the size cap reported its error alongside a full buffer, which io.ReadFull discards, so a stream read in exact block sizes passed the limit unreported; and entries skipped as directories or symlinks counted against neither limit.

The dranzer fingerprint no longer falls back to searching the raw text for a per-object banner. The CSV companion file that ships beside real reports quotes that banner inside one of its columns, so the fallback accepted a file carrying no version, objects, findings or counters — which would then skip policy evaluation rather than being rejected. Genuine reports carry the banner on its own line and are unaffected.

Refs PFM-6467

AI assistance

This change was produced with assistance from Claude Code, disclosed via the Assisted-by trailer on the commit.

Review in cubic

@chainloop-platform

chainloop-platform Bot commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

AI Session Analysis

Avg score Sessions Failing policies Attribution Files Lines Total Duration
🟢 91% 1 ✅ 0 95% AI / 5% Human 19 +1827 / -289 1h48m25s

🟢 91% — 95% AI — ✅ All policies passing

Jul 28, 2026 07:28 UTC · 1h48m25s · $65.96 · 1.6k in / 704.1k out · claude-code 2.1.220 (claude-opus-5)

View session details ↗

Change Summary

  • Adds support for Dranzer report bundles from zip and tar.gz inputs.
  • Aggregates bundle parsing, crafter acceptance, and projection for policy evaluation.
  • Adds and updates tests, then validates the behavior through local CLI policy evals on real materials.

AI Session Overall Score

🟢 91% — Strong execution and verification, with only minor workflow ambiguity.

AI Session Analysis Breakdown

🟢 94% · context-and-planning

🟢 The session used design review, clarifying questions, TDD, and task breakdown. · High Impact

🟢 94% · user-trust-signal

No notes.

🟢 93% · solution-quality

🟢 Reviewer-found guard bugs were traced to capReader and fixed at the shared layer. · High Impact

🟢 92% · verification

🟢 The change was validated with RED/GREEN tests and real CLI policy evals. · High Impact

🟡 The user never explicitly confirmed the eval output, though they proceeded to ship after the checks. · Low Severity

🟢 88% · alignment

🟡 AI pushed to the fork instead of the literal upstream remote, then surfaced the judgment call immediately. · Low Severity

🟢 86% · scope-discipline

No notes.


File Attribution

███████████████████░ 95% AI / 5% Human

Status Attribution File Lines
created ai pkg/attestation/crafter/materials/archiveio/archiveio.go +391 / -0
created ai pkg/attestation/crafter/materials/dranzer/bundle_test.go +342 / -0
created ai pkg/attestation/crafter/materials/archiveio/archiveio_test.go +309 / -0
created ai pkg/attestation/crafter/materials/dranzer/bundle.go +283 / -0
modified ai pkg/attestation/crafter/materials/archive.go +38 / -218
modified ai pkg/attestation/crafter/materials/dranzer/dranzer_test.go +130 / -0
modified ai pkg/attestation/crafter/materials/dranzer_test.go +113 / -0
modified ai pkg/attestation/crafter/materials/dranzer/dranzer.go +58 / -22
modified ai pkg/attestation/crafter/materials/dranzer.go +36 / -21
created human pkg/attestation/crafter/materials/testdata/dranzer-bundle/example-app_1.0.0_t_Result.txt +44 / -0
modified ai pkg/attestation/crafter/api/attestation/v1/crafting_state_test.go +36 / -0
modified ai pkg/attestation/crafter/materials/archive_test.go +0 / -25
created human pkg/attestation/crafter/materials/testdata/dranzer-bundle/example-app_1.0.0_p_Result.txt +12 / -0
created human pkg/attestation/crafter/materials/testdata/dranzer-bundle/example-app_1.0.0_s_Result.txt +12 / -0
created human pkg/attestation/crafter/materials/testdata/dranzer-bundle/example-app_1.0.0_b_Result.txt +11 / -0
modified ai pkg/attestation/crafter/api/attestation/v1/crafting_state.go +7 / -2
modified ai app/controlplane/api/workflowcontract/v1/crafting_schema.proto +3 / -1
created human pkg/attestation/crafter/materials/testdata/dranzer-bundle/checkResult_Dranzer.csv +2 / -0
created human pkg/attestation/crafter/api/attestation/v1/testdata/dranzer-bundle.zip +0 / -0

Policies (4)

Status Policy Material Messages
✅ Passed ai-config-ai-agents-allowed ai-coding-session-2b0a76 -
✅ Passed ai-config-no-dangerous-commands ai-coding-session-2b0a76 -
✅ Passed ai-config-no-secrets ai-coding-session-2b0a76 -
✅ Passed ai-config-mcp-servers-allowed ai-coding-session-2b0a76 -

Powered by Chainloop and Chainloop Trace

@javirln javirln self-assigned this Jul 28, 2026
@javirln
javirln requested a review from a team July 28, 2026 09:03

@cubic-dev-ai cubic-dev-ai Bot 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.

All reported issues were addressed across 21 files

Tip: cubic can generate docs of your entire codebase and keep them up to date. Try it here.

Re-trigger cubic

Comment thread pkg/attestation/crafter/materials/dranzer/dranzer.go Outdated
Comment thread pkg/attestation/crafter/materials/archiveio/archiveio.go Outdated
A single dranzer run emits one report per test mode (-b, -p, -s, -t), so the
evidence arrives as a bundle rather than a single file. CERTCC_DRANZER now
accepts either a single report or a zip/tar.gz of them, following the
RADAMSA_CRASHES precedent: the archive is recorded whole under the contract's
declared material name, with the report count as an annotation.

Recording the archive alone is not enough, because the policy engine reads
dranzer content: parsing archive bytes as text yields an empty report, which
makes the ActiveX policy skip rather than evaluate and reads as a clean run.
The projection therefore aggregates the archive's entries, summing every
counter and unioning objects and findings, and keeps a per-report breakdown
alongside. The aggregate is promoted to the top level so existing policies
reading tool, summary and findings are unaffected.

Craft time and evaluation time share both predicates -- what counts as an
archive and what counts as a report -- because a disagreement would accept a
material whose projection then silently skips. Container detection is by content
on both sides for the same reason: a zip carrying a prepended stub still opens,
since its central directory is at the end, but no longer starts with the zip
magic, so detecting by filename at craft time would accept what the projection
cannot read.

Archive container detection and walking move to a new archiveio leaf package so
the projection can share the entry-count, size and traversal guards with the
crafters without an import cycle. Those guards are also corrected: the size cap
reported its error alongside a full buffer, which io.ReadFull discards, so a
stream read in exact block sizes -- every tar header -- passed the limit
unreported; and entries skipped as directories or symlinks counted against
neither limit, letting a directory-only tar.gz decompress unmeasured.

LooksLikeDranzer now judges only what the parser extracted rather than the
presence of a phrase in the raw text. The CSV companion that ships beside real
reports quotes both the per-object banner and an error line inside its columns,
so a substring match accepted a file that yields no version, objects, findings or
counters -- which would then skip policy evaluation. Genuine reports put those
lines on their own line where the anchored patterns match them, and a parsed
counter counts even when zero, so a run that found no COM objects is still
recognized.

Container detection fills its peek buffer instead of trusting a single read,
since the tar marker sits at offset 257 and a short read would misdetect a valid
tar as a plain file, and it now surfaces read failures rather than reporting them
as "not an archive".

Refs PFM-6467

Assisted-by: Claude Code
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>

Chainloop-Trace-Sessions: 2b0a76d9-abe5-44af-919a-419aca149653
@javirln
javirln force-pushed the javier/pfm-6467-cli-explode-archive-materials-in-chainloop-att-add-ziptargz-2 branch from 31c2512 to b8a3ffa Compare July 28, 2026 09:17
@javirln

javirln commented Jul 28, 2026

Copy link
Copy Markdown
Member Author

Both findings addressed in b8a3ffa.

LooksLikeDranzer raw-substring fallback — valid, and the same weakness I had removed for the per-object banner while leaving it in place for the counter label. Recognition is now based on parsed content only (Summary.Counters), so text merely quoting a dranzer label is rejected. This also fixes a case the substring was masking: a parsed counter counts even when its value is zero, so a legitimate run that found no COM objects is recognized rather than relying on the version banner.

Short header read / swallowed read errors — valid. The tar marker sits at offset 257, so a single Read returning less than the peek buffer could misdetect a valid tar as a plain file, and the discarded error turned an I/O failure into "not an archive". Detection now uses io.ReadFull, tolerating the expected short read for files smaller than the buffer and surfacing anything else.

Both are covered by tests that failed before the fix. End-to-end behaviour against a real dranzer bundle is unchanged.

@javirln
javirln enabled auto-merge (squash) July 28, 2026 10:41

@jiparis jiparis left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

looks goodl

@javirln
javirln merged commit 1ffc856 into chainloop-dev:main Jul 28, 2026
16 checks passed
javirln added a commit to javirln/chainloop that referenced this pull request Jul 29, 2026
Resolves the archive.go conflict against upstream's archiveio refactor
(chainloop-dev#3310/chainloop-dev#3301): keep upstream's archiveio-backed primitives and re-apply the
AllocateNamed allocator and NormalizeArchivePath helper on top. Regenerated the
workflowcontract proto artifacts so the additive match_mode field coexists with
the upstream protovalidate bump.

Assisted-by: Claude Code
Signed-off-by: Javier Rodriguez <javier@chainloop.dev>
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.

2 participants