PM-4777: Binary scanning for Java artifacts, with declared and transitive dependency reporting - #539
Conversation
…g Boot) Adds `cycode scan -t sca binary <path>` and `cycode report sbom binary <path>`. The CLI opens a Java archive locally, identifies the open-source components inside it from embedded Maven metadata (pom.properties, then optionally a Maven Central SHA-1 lookup, then MANIFEST.MF), synthesises a CycloneDX 1.4 document, and feeds it to the existing SCA scan path. The archive itself is never uploaded. What ships: - Hardened zip reader (zip-slip, symlinks, absolute paths, compression and entry-count bombs, duplicate entry names read by central-directory record). - Identification ladder with an explicit `unidentified` result; no coordinate is ever guessed from a filename or an unshaped manifest value. - Dependency graph from containment plus real edges from embedded pom.xml (parsed with a DOCTYPE/ENTITY guard, no defusedxml needed). - `--max-depth`, `--offline`, `--maven-central` (opt-in, sends the hash only), `--project-name`, `--keep-bom`, `--include-binaries`. - Coverage line and JSON fields so CI can gate on identification, not guess. - `DigestResolver` seam so the Cycode backend digest index can replace the Maven Central implementation without caller changes. No new runtime dependency. All HTTP in tests is mocked with `responses`.
Adds three opt-in flags to `scan binary` and `report sbom binary`:
- `--include-declared` reads every embedded pom.xml the way Maven does
(properties, ${project.*}, the parent chain, dependencyManagement with
imported BOMs, dependencies inherited from parents) and reports the
compile- and runtime-scope dependencies the artifact does not ship as
components marked `declared`, with no hashes and the declaring pom as
their path. Test, provided, system and optional are left out because
Maven never hands them to a consumer.
- `--include-test-scope` lifts that filter so totals line up with SCA
tools that count every declared dependency. Scope stays on each
component.
- `--include-transitive` follows each declared dependency through its
own pom on Maven Central: nearest declaration wins, exclusions apply
down their branch, optional stops, scope combines per Maven's table,
and the root pom's management overrides transitive versions. Each
transitive records what pulled it in (`cycode:via`) and the graph
links them. Bounded to 10 hops and 250 poms per embedded pom.
Parent and dependency poms come from repo1.maven.org behind a PomSource
seam, so `--maven-central` is required for parent-managed versions and
for transitives. Coordinates read from an untrusted pom are validated
before they can shape a URL, and bodies are capped at 1 MB. A version
that cannot be established is listed under "Declared, version
unresolved" with the reason, never guessed.
Motivation: a customer's Checkmarx report on 16 vendor jars listed 29
vulnerabilities, every one on a declared-but-unshipped dependency. With
all three flags Cycode reports all 61 of those declared packages and 30
findings; the difference is vulnerability-database content only.
Claude-Session: https://claude.ai/code/session_016SxZLL1buHZ8jZ9T8onEZ8
| if not chunk: | ||
| break | ||
|
|
||
| digest.update(chunk) |
There was a problem hiding this comment.
❗Cycode: SAST violation: 'Usage of weak hashing library (SHA-1)'.
Risk Score: 65 (MEDIUM)
Severity: Medium
Description
Using a weak hashing library like SHA-1 increases the risk of data breaches. SHA-1 in particular is vulnerable to collision attacks, where two different inputs can produce the same hash value, compromising data integrity and security.
Cycode Remediation Guideline
✅ Do
- Do opt for stronger hashing algorithms such as SHA-256 to enhance security.
hashlib.sha256('password').digest()
❌ Don't
- Do not use SHA-1 for hashing. It is no longer considered secure due to its vulnerability to collision attacks.
hashlib.sha1('password').digest() # unsafe
🎥 Learning materials (by Secure Code Warrior)
Tell us how you wish to proceed using one of the following commands:
| Tag | Short Description |
|---|---|
| #cycode_sast_false_positive <reason> | Mark as false positive — applies to this violation only |
| #cycode_sast_ignore_here <reason> | Ignore this violation — applies to this violation only |
| #cycode_ai_remediation | Request remediation guidance using Cycode AI |
| self._check_compression_ratio(entry, read_bytes) | ||
| self._budget.consume(len(chunk)) | ||
|
|
||
| sha1.update(chunk) |
There was a problem hiding this comment.
❗Cycode: SAST violation: 'Usage of weak hashing library (SHA-1)'.
Risk Score: 65 (MEDIUM)
Severity: Medium
Description
Using a weak hashing library like SHA-1 increases the risk of data breaches. SHA-1 in particular is vulnerable to collision attacks, where two different inputs can produce the same hash value, compromising data integrity and security.
Cycode Remediation Guideline
✅ Do
- Do opt for stronger hashing algorithms such as SHA-256 to enhance security.
hashlib.sha256('password').digest()
❌ Don't
- Do not use SHA-1 for hashing. It is no longer considered secure due to its vulnerability to collision attacks.
hashlib.sha1('password').digest() # unsafe
🎥 Learning materials (by Secure Code Warrior)
Tell us how you wish to proceed using one of the following commands:
| Tag | Short Description |
|---|---|
| #cycode_sast_false_positive <reason> | Mark as false positive — applies to this violation only |
| #cycode_sast_ignore_here <reason> | Ignore this violation — applies to this violation only |
| #cycode_ai_remediation | Request remediation guidance using Cycode AI |
Supersedes #524, which was closed on 1 September before the second commit landed. Same branch, two commits.
What
Adds binary composition analysis to the CLI: point it at a built Java artifact and get the same SCA findings a source scan gives, plus an explicit account of what could not be identified.
Supported: JAR, WAR, EAR, Spring Boot fat JAR, nested archives to
--max-depth(default 3).How it works
META-INF/maven/**/pom.properties(exact) → optional Maven Central SHA-1 lookup with--maven-central(exact, opt-in, sends the hash only) →MANIFEST.MFattributes when they are shaped like real coordinates and carry a declared group (markedambiguous, never gates the exit code) → otherwise unidentified, reported by path, digest and size.cycode:evidence/cycode:confidence/cycode:pathproperties, and a dependency graph from containment overlaid with real edges from embeddedpom.xml.pom.xml(the engine routes a barebom.jsonto nothing), goes through the existingzip_documents→api/v4/scans/clipath. Nothing belowscan_documentschanges.Product stance
binary.unidentifiedin JSON, with a coverage line that is always printed and always true:9 identified (2 low confidence) | 7 unidentified | 23 vulnerabilities.Options
--max-depth--maven-central--offline--offline--project-name--monitoron a bare filename--keep-bom--include-binariesscan path, also extract Java archives met during the walkSecurity hardening in the reader
Zip-slip and absolute/drive/UNC paths, symlinks and non-regular entries, per-entry and total size limits, compression-ratio and entry-count bombs, truncated central directories, and duplicate entry names (Python's
ZipFile.open(name)returns the last duplicate; every entry is read from its ownZipInfoso a vulnerable jar cannot hide behind a patched one with the same name). Embeddedpom.xmlis refused if it carries a DOCTYPE or ENTITY declaration, which closes entity expansion without addingdefusedxml. Console output from entry names is stripped of rich markup, control characters and unbounded length.Verification
ruff checkandruff format --checkclean; Python 3.9 floor respected; all HTTP in tests mocked withresponses, including the Maven Central client.Declared and transitive dependencies (second commit)
A customer's Checkmarx report on 16 vendor jars listed 29 vulnerabilities, every one on a dependency the jars declare in their embedded pom but do not ship. Three opt-in flags on
scan binaryandreport sbom binaryclose that gap:--include-declaredpom.xmlthe way Maven does (properties, parent chain,dependencyManagementincl. imported BOMs, inherited parent dependencies) and reports compile/runtime dependencies that were not shipped as components markedcycode:presence=declared, no hashes, path = the declaring pom. Test/provided/system/optional are left out: Maven never hands them to a consumer.--include-test-scope--include-transitivedependencyManagementoverrides. Recordscycode:viaand links the graph. Bounded to 10 hops and 250 poms per embedded pom, cached per run. Requires--maven-central.Parent and dependency poms come from
repo1.maven.orgbehind aPomSourceseam. Coordinates read from an untrusted pom are validated before they can shape a URL; bodies are capped at 1 MB; a version that cannot be established is listed under "Declared, version unresolved" with the reason, never guessed. The backend was probed first: it evaluates a bare pom only for literal and in-file-property versions, drops test scope, and resolves neither parents nor transitives, so the resolution has to live in the CLI.Result on the same 16 jars with all three flags: all 61 declared packages on the Checkmarx list are reported, 30 findings to Checkmarx's 29, the difference being vulnerability-database content only. Run time about 40 seconds. 1,364 tests pass.
Not in this PR
DigestResolverseam; needs a backend endpoint that does not exist yet.BinaryExtractorinterface is ready; Java only ships now.jsonschema; a structural validator ships instead.main, not touched:report sbomprints "Report saved to" and writes no file (reproduces onreport sbom path .).Note on the ticket prefix
Branch and title use the product ticket
PM-4777. If aCM-ticket should own this, the title and branch are a rename away.🤖 Generated with Claude Code
https://claude.ai/code/session_016SxZLL1buHZ8jZ9T8onEZ8