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 7 commits into
Conversation
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.52% 91.45% -0.07%
==========================================
Files 44 44
Lines 9989 10169 +180
Branches 1811 1839 +28
==========================================
+ Hits 9142 9300 +158
- Misses 847 869 +22
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. |
| console.warn('Gradle could not be invoked to compute artifact hashes, SBOM will be generated without hashes') | ||
| if (debug) { | ||
| console.error(`Gradle hash: selectToolBinary failed => ${error.stack || error.message}`) | ||
| } |
There was a problem hiding this comment.
I think the error message should be included in the console.warn and no need for the extra console.error
There was a problem hiding this comment.
@a-oren I think claude missed some more 😄 https://github.com/guacsec/trustify-da-javascript-client/pull/614/changes#diff-0bdedc185a11a524cec48c2e0a2b28a8deab9d7606b6152db64b23de9466440fR371-R373 and https://github.com/guacsec/trustify-da-javascript-client/pull/614/changes#diff-0bdedc185a11a524cec48c2e0a2b28a8deab9d7606b6152db64b23de9466440fR409-R412 too
ae9d806 to
87669ea
Compare
a6c4c71 to
f630d1b
Compare
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
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>
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>
…print Replace readFileSync with createReadStream + event listeners when computing SHA-256 hashes. Large artifacts (e.g., AWS SDK bundles ~300MB) now process in chunks instead of loading entirely into memory, preventing OOM in memory-constrained CI containers. Additional simplifications: - Remove #lookupHashes helper, inline hashMap?.get() at call-sites - Consolidate error logging into single console.warn with message Mirrors Maven streaming approach from PR guacsec#612. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
…dlock The Gradle hash map now streams artifact reads via createReadStream + event listeners (the OOM fix for large jars). Sinon's default useFakeTimers() also fakes process.nextTick/setImmediate, which Node streams depend on, so the reader deadlocks. Restrict faking to Date across all Gradle suites. Additional fixes: - Remove unused throws import - Add await to all async provider method calls (provideStack, provideComponent, parseGradleHashes) - Replace throws() with expect(...).to.be.rejected for async assertions - Make all affected test functions async Mirrors Maven streaming fix from commit eb00a42. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
Include the error detail in the console.warn on the init-script and unexpected-failure degradation paths, dropping the duplicate debug-gated console.error (per PR review). 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 resolved artifact hashing to Gradle-generated SBOMs.
New Features:
Enhancements:
Tests: