Safety and limitations
- Observe only. Advice is wrapped with
suppress = Throwable.classand never changes arguments, return values or exceptions. Secret key material is only read from a short list of reviewed JDK key classes (SecretKeySpec, SunJCE's DES/DESede keys), whosegetEncoded()returns a copy that is then wiped. Third-party keys are never exported; PKCS#11 (HSM/token) keys are not inspected at all. The agent also avoids JCA accessors that change state:KeyGenerator.getProvider()and theKeyPairGeneratordelegate'sgetProvider()switch off provider failover, so their provider is read from the field instead, and other providers are asked only afterinithas fixed them. - Bounded memory. Distinct assets and call sites are capped (
maxAssets,maxSites); names are truncated (after normalization, on character boundaries). Dropped assets make the collection incomplete and failcheck; call sites beyondmaxSiteslose only their attribution (the asset and its verdict are kept), socheckwarns. The writer also bounds report bytes and parser memory; it folds call-site groups into<overflow>when necessary, preserving assets, verdicts and observation counts. See output budgets. If asset data alone cannot fit, writing fails explicitly without replacing the previous output. - Namespaced. Every class in the agent jar lives under
com.developmentsprint.cryptocensus.agent(Byte Buddy and the core library are relocated), so adding it to the bootstrap class path cannot shadow your classes. - Safe output. A CBOM is written through the single handle that exclusively created a random temporary file (
CREATE_NEW, which never follows or reuses an existing entry), then atomically renamed into place: a symlink or FIFO at the destination is replaced, never opened. On POSIX the file is0600; on Windows it inherits the directory's ACL, so keep output in a directory only you can read. Inputs larger than 16 MiB are refused; generated reports stay within an 8 MiB output budget. The output directory itself is trusted: someone who can rename entries inside it can still swap files around. - Fails open, reports honestly. If the agent cannot start, it logs one line and the application runs normally; if anything went wrong while recording, the CBOM says so (
cryptocensus:transformErrors,recordingFailures,dropped), and so does a JDK class it could not instrument (cryptocensus:missingTargets, also printed on stderr). - Installs once. A repeated
-javaagentor attach is ignored. - Coexists with other agents. Verified next to the OpenTelemetry Java agent (in either order), Open Liberty's bootstrap agent and WildFly's JBoss Modules agent.
- Compiled for Java 17; runs on the JDKs, vendors and servers listed under Compatibility.
Known limitations
- Self-reported evidence. The agent runs inside the application it observes; hostile code in the same JVM could suppress or forge observations, and anything running as the same OS user could edit the output. Treat a CBOM as developer evidence. For audit-grade provenance, collect it with a trusted pipeline and sign it outside the observed JVM (the merged CBOM records a SHA-256 of every input).
- Coverage = what ran. Code paths your tests or workload never exercise are not observed.
- Alias resolution needs the provider. A provider that registers an algorithm under a non-standard canonical name will be reported (and judged) under that name.
- TLS key-exchange group. The TLS asset records protocol and cipher suite. The negotiated group (
X25519,secp256r1,X25519MLKEM768) shows up as separate key-agreement / KEM assetsvia jsse, so a hybrid ML-KEM handshake appears as an ML-KEM asset and an X25519 asset. - TLS sessions are recorded when JSSE creates them, which is before the handshake has finished authenticating; a handshake that later fails is still counted. TLS 1.3 resumption tickets add session objects.
- Not captured: OAEP parameters, the MGF1 hash of RSASSA-PSS, PSS parameters set only after
init(the PSS hash set beforeinitis captured, asSHA256withRSASSA-PSS; when it is not, the signature is reported as unknown rather than judged on the key alone), GCM tag length, PBE iteration counts, the named curve behind an EC key (only its size), and a DSA key's subgroup size N (only the modulus L), so the N conditions of SP 800-131A Table 2 are not assessed. - Shutdown boundary. Collection ends when the agent's shutdown hook runs; crypto performed by other shutdown hooks may be missed. In containers, stop the server gracefully (
docker stopsends SIGTERM); a killed JVM writes no CBOM. - Third-party provider keys. Keys generated by providers other than the JDK's own (OpenJCEPlus on Semeru, HSM providers) are never exported, so their sizes are often not observed: an AES key stays
acceptable(AES has no weak sizes), an HMAC or public key becomesunknown. - JDK internals. TLS sessions are observed through
sun.security.ssl.SSLSessionImpl, a JSSE-internal class. A JDK that renames it (or any other fixed target) is reported as an instrumentation gap rather than silently losing TLS observations; seefailOnMissingTargets. - Dynamic attach (JEP 451): attaching to a running JDK 21+ prints a warning, and classes already used before the attach are only observed from then on.
- CDS warning (JDK 17+). Like other agents that extend the bootstrap class path (OpenTelemetry's, for example), the JVM prints
Sharing is only supported for boot loader classes because bootstrap classpath has been appended. It is harmless; class-data sharing stays on for JDK classes. - Overhead has not been benchmarked yet. Each recorded event walks the stack. The agent is aimed at test suites and staging today.
- Bouncy Castle's lightweight API (
org.bouncycastle.crypto.*) bypasses the JCA and is not observed. Bouncy Castle used as a JCA provider is.
Maven evidence coverage
Not detected (by design or not yet):
- one test JVM with the agent cannot vouch for a different execution that never had it (for example a Failsafe execution with its own hard-coded
argLine) when the other produced evidence; - tests run with reporting disabled and an explicit skip property set;
- code paths your tests never exercise;
- hostile code inside the observed JVM (see above).