feat(providers): add SHA-256 hash computation for Gradle providers - #614
feat(providers): add SHA-256 hash computation for Gradle providers#614a-oren wants to merge 4 commits into
Conversation
Compute SHA-256 hashes for Gradle dependency artifacts by asking Gradle
itself for the resolved artifact files via an init script, then hashing
those files with the Node.js crypto module. This mirrors the existing
init-script pattern (GRADLE_INIT_SCRIPT / discoverGradleSubprojects /
parseGradleInitScriptOutput) rather than scanning the local Gradle cache.
A new GRADLE_HASH_INIT_SCRIPT emits a `::DA_HASH::group:name:version::<file>`
line per resolved module artifact using a lenient artifact view, so fileless
artifacts (BOM/platform() dependencies) are skipped. parseGradleHashes builds
a Map<"group:name@version", [{alg, content}]> that is threaded through
#buildSbom() and #buildDirectDependenciesSbom() into sbom.addDependency().
Both Groovy and Kotlin variants inherit this behaviour.
Degrades gracefully: an uninvokable Gradle, a failing init script, or an
artifact with no readable file omits the hash rather than throwing.
Golden SBOM fixtures and the Groovy/Kotlin tests are updated to the
SBOM_CASES deep-equal pattern, with targeted tests for the exact digest
and graceful degradation.
Implements TC-5550
Assisted-by: Claude Code
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Reviewer's GuideAdds SHA-256 hash computation for Gradle (Groovy and Kotlin) providers by invoking a Gradle init script to list resolved artifacts, hashing their files, and threading those hashes into SBOM generation, along with corresponding tests and fixtures updates. Sequence diagram for Gradle hash computation and SBOM enrichmentsequenceDiagram
participant Java_gradle
participant Gradle
participant Node_fs
participant Node_crypto
participant Sbom
Java_gradle->>Java_gradle: parseGradleHashes(manifest, opts)
Java_gradle->>Java_gradle: selectToolBinary(manifest, opts)
Java_gradle->>Node_fs: writeFileSync(initScriptPath, GRADLE_HASH_INIT_SCRIPT)
Java_gradle->>Gradle: _invokeCommand(gradle, [--init-script, daListHashes])
Gradle-->>Java_gradle: ::DA_HASH:: lines
Java_gradle->>Java_gradle: parseGradleHashScriptOutput(output)
loop for each artifact
Java_gradle->>Java_gradle: hashKeyFromComponentId(id)
Java_gradle->>Node_fs: readFileSync(file)
Node_fs-->>Java_gradle: artifact bytes
Java_gradle->>Node_crypto: createHash('sha256').update(bytes).digest('hex')
Node_crypto-->>Java_gradle: digest
Java_gradle->>Java_gradle: hashMap.set(key, [{alg: SHA-256, content: digest}])
end
Java_gradle->>Java_gradle: #buildSbom(content, properties, manifestPath, opts, hashMap)
Java_gradle->>Sbom: addDependency(currentParent, purl, scope, #lookupHashes(purl, hashMap))
Java_gradle->>Java_gradle: #buildDirectDependenciesSbom(content, properties, manifestPath, opts, hashMap)
Java_gradle->>Sbom: addDependency(rootPurl, purl, scope, #lookupHashes(purl, hashMap))
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The helper logic for
HASH_FIXTURE_DIR,extractCoordinates,artifactFileFor,buildHashScriptOutput, andmockInvokeCommandis duplicated between the Groovy and Kotlin Gradle tests; consider extracting these into a shared test utility to keep the behavior in sync and reduce maintenance overhead. - In
parseGradleHashes, the broadtry { ... } catch { return hashMap }blocks will swallow unexpected programming errors as silent hash omissions; consider narrowing the catch scope or at least logging under a debug flag (similar toTRUSTIFY_DA_DEBUG) so genuine failures are observable. - The temp artifact files created in
artifactFileForunder the OS temp directory are never cleaned up; it may be worth deleting them at suite teardown (or using a per-test temporary directory) to avoid unbounded growth of test artifacts on long-lived CI agents.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The helper logic for `HASH_FIXTURE_DIR`, `extractCoordinates`, `artifactFileFor`, `buildHashScriptOutput`, and `mockInvokeCommand` is duplicated between the Groovy and Kotlin Gradle tests; consider extracting these into a shared test utility to keep the behavior in sync and reduce maintenance overhead.
- In `parseGradleHashes`, the broad `try { ... } catch { return hashMap }` blocks will swallow unexpected programming errors as silent hash omissions; consider narrowing the catch scope or at least logging under a debug flag (similar to `TRUSTIFY_DA_DEBUG`) so genuine failures are observable.
- The temp artifact files created in `artifactFileFor` under the OS temp directory are never cleaned up; it may be worth deleting them at suite teardown (or using a per-test temporary directory) to avoid unbounded growth of test artifacts on long-lived CI agents.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #614 +/- ##
==========================================
- Coverage 91.22% 91.18% -0.04%
==========================================
Files 43 43
Lines 9558 9731 +173
Branches 1717 1739 +22
==========================================
+ Hits 8719 8873 +154
- Misses 839 858 +19
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
|
[sdlc-workflow/verify-pr] Re: @sourcery-ai[bot] review — the review body contained three suggestions, each classified as suggestion. No sub-tasks created.
Classified by sdlc-workflow/verify-pr v0.13.8. |
Verification Report for TC-5550 (commit 2fa8a41)
Overall: PASSNo issues require attention. The implementation reuses the existing Note: 17 pre-existing test failures in the OCI/python-pip/python-poetry suites are environmental (missing skopeo/docker/poetry, local package-version mismatches) and unrelated to this change — no gradle test is among them. This comment was AI-generated by sdlc-workflow/verify-pr v0.13.8. |
Apply two lessons from the Maven SHA PR (guacsec#612) to the Gradle provider: Key drift (lesson guacsec#1): derive both the stored hash-map key and the lookup key from the canonical PURL (`toPurl(...).toString()` / `purl.toString()`), the same builder parseDep uses, so the two keys cannot drift in formatting. Refactor hashKeyFromComponentId into parseComponentId (parse/validate only); the class method builds the canonical key. Degradation warning (lesson guacsec#2): parseGradleHashes previously degraded completely silently on every failure path. Emit a console.warn when gradle cannot be invoked, the init script fails, hashing fails, or some resolved artifacts cannot be read (with an attempted/missed count summary), mirroring the pip/cargo providers so incomplete hash coverage is visible without TRUSTIFY_DA_DEBUG. Add regression tests (groovy + kotlin): canonical-key round trip for a conflict-resolved (`->`) transitive dependency, and warning emission on the partial-miss and failing-init-script paths. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The broad catch blocks in parseGradleHashes degrade to an SBOM without hashes, which is correct for expected failures (gradle missing, init script failing, unreadable artifacts) but also silently swallows genuine programming errors as ordinary graceful degradation. Capture the caught error on every path and log it (with stack for the unexpected-failure path) when TRUSTIFY_DA_DEBUG is set, so real bugs are observable during debugging without changing the graceful-degradation behavior required by the feature. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…tures The HASH_FIXTURE_DIR constant and the extractCoordinates, artifactFileFor, buildHashScriptOutput, getStubbedResponse and mockInvokeCommand helpers were duplicated byte-for-byte between the Groovy and Kotlin Gradle test suites. Extract them into a shared gradle_hash_test_utils.js so the two suites cannot drift and are maintained in one place. Also add cleanupHashFixtures() and call it from each suiteTeardown so the stand-in artifact files written under the OS temp dir are removed after the run, avoiding unbounded accumulation on long-lived CI agents. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Description
Adds SHA-256 hash computation for Gradle dependency artifacts (both Groovy and Kotlin variants). Rather than scanning the local Gradle cache layout, the provider asks Gradle itself for the resolved artifact files via an init script, then hashes those files with the Node.js
cryptomodule — the same technique the CycloneDX Gradle plugin uses.What changed
GRADLE_HASH_INIT_SCRIPT— anallprojects { task daListHashes }block mirroring the existingGRADLE_INIT_SCRIPT. It iterates resolvable configurations via a lenientartifactViewand emits::DA_HASH::group:name:version::<absolute-file-path>per resolved module artifact. Fileless artifacts (BOM/platform()) are skipped.parseGradleHashes(manifest, opts)— writes the init script to a temp file (crypto.randomUUID()name, removed infinally), invokes Gradle with--init-script … daListHashes, parses the output, and computes SHA-256 per file. Returns aMap<"group:name@version", [{alg, content}]>.#buildSbom()and#buildDirectDependenciesSbom()intosbom.addDependency(source, target, scope, hashes)at both the transitive and direct add-dependency sites.Testing
SBOM_CASESdeep-equal pattern against regenerated golden fixtures (which now include hashes).npm test: all 28 Gradle tests pass.npm run coverage: 90.8% (above the 82% threshold). Verified end-to-end against a real Gradle project — the SBOM digest matched an independentsha256sumof the resolved jar.Implements TC-5550
🤖 Generated with Claude Code
Summary by Sourcery
Add SHA-256 hash computation for Gradle dependency artifacts and propagate these hashes into SBOMs for both stack and component analysis while ensuring graceful degradation and updated test coverage.
New Features:
Enhancements:
Tests: