Now I have a comprehensive picture of the codebase. Let me compile the security audit report.
Security Audit Report
Executive Summary
This is a well-engineered cryptographic library with strong security fundamentals. The codebase demonstrates awareness of advanced cryptographic threats (partitioning oracles, nonce budget exhaustion, key commitment), uses established primitives from the @noble/* family, and implements important safety rails (verify-after-encrypt, constant-time comparisons, SecureBuffer with mlock, indistinguishable authentication errors).
No Critical findings were identified. The library's cryptographic architecture is sound β nonces are CSPRNG-sourced, AAD binds all relevant fields, key separation is enforced via HKDF with distinct info strings, and commitment prevents multi-key attacks.
A small number of Medium and Low findings are noted below, primarily around CI/CD configuration and minor hardening opportunities.
Critical Findings
None.
High Findings
None.
Medium Findings
M-1: pull_request_target with secrets: inherit in Dependabot Handler
File: .github/workflows/dependabot-handler.yml:13,25
Description: The workflow triggers on pull_request_target and passes secrets: inherit to a reusable workflow at de-otio/.github. While gated by if: github.actor == 'dependabot[bot]', this pattern inherits all repository secrets into a context where code from the PR (not the base branch) could potentially be executed if the called reusable workflow checks out the PR head ref.
Attack scenario: If the reusable workflow at de-otio/.github/.github/workflows/dependabot-claude-review.yml@main uses actions/checkout with ref: ${{ github.event.pull_request.head.ref }} and then runs arbitrary code (npm scripts, test hooks), a compromised Dependabot commit could exfiltrate secrets. The dependabot[bot] actor check narrows the window significantly (an attacker must compromise Dependabot itself), but the pattern is flagged by GitHub's own security guidance.
Fix: Audit the called workflow to ensure it never checks out the PR head or runs user-controlled code with inherited secrets. Alternatively, pass only the specific secrets needed rather than secrets: inherit.
M-2: InMemoryMessageCounter Resets Across Restarts β AES-GCM Nonce Budget Bypass
File: src/message-counter.ts:41-73; src/envelope-client.ts:140
Description: The default MessageCounter is in-memory and documented as unsafe for multi-process use. However, the library defaults to it when no counter is provided. If a consumer uses AES-256-GCM with a long-lived key across process restarts (serverless, workers, container restarts), the counter resets to 0 each time β the 2Β³Β² nonce budget could be silently exceeded without triggering NonceBudgetExceeded.
Attack scenario: A chaoskb deployment using AES-256-GCM (selected for FIPS interop) on AWS Lambda processes ~10k messages/day. Over 1.2 years with frequent cold starts, the actual cumulative message count per key exceeds 2Β³Β² but the in-memory counter never reaches the cap. Random nonce collision probability rises, potentially enabling AES-GCM forgery (Joux multi-collision on shared nonces).
Mitigation already present: The library emits a console.warn on first use, and XChaCha20-Poly1305 (the default) is unaffected. The code comments document this well.
Recommended fix: Consider making MessageCounter a required parameter when algorithm: 'AES-256-GCM' is selected, rather than silently falling back to in-memory. This would force callers to acknowledge the operational requirement.
M-3: Unpinned npm install -g @anthropic-ai/claude-code in CI Workflows
File: .github/workflows/security-review.yml:52, .github/workflows/weekly-security-audit.yml:54
Description: The security review and weekly audit workflows install @anthropic-ai/claude-code globally without a version pin. A supply chain compromise of this package (typosquatting, maintainer account takeover, or registry hijack) would execute arbitrary code with repository permissions (contents: read, pull-requests: write, issues: write, id-token: write).
Attack scenario: An attacker compromises the @anthropic-ai/claude-code npm package. On the next PR or weekly schedule trigger, the compromised package runs with OIDC tokens that grant AWS Bedrock access and GitHub write permissions, enabling silent exfiltration or code injection.
Fix: Pin to a specific version: npm install -g @anthropic-ai/claude-code@0.2.x (or use a hash-pinned lockfile approach). Better: vendor or cache the package in a controlled artifact.
Low Findings
L-1: JSON.parse of Decrypted Plaintext Without Prototype Pollution Guard
File: src/envelope/v1.ts:154
Description: After successful AEAD decryption, the plaintext is parsed with JSON.parse(...) and cast to Record<string, unknown>. If the plaintext contains "__proto__" or "constructor" keys, downstream consumers that perform naive property spreading could be vulnerable to prototype pollution.
Impact: Low β this is an authenticated path (only the legitimate key holder can produce parseable plaintext), and the vulnerability is in the consumer code, not this library. However, the library could defensively strip dangerous keys.
Fix (optional): This is informational. Documenting that consumers should use Object.create(null) when spreading decrypted payloads into sensitive contexts is sufficient. The library should not alter plaintext content.
L-2: esbuild Dev Dependency Has Known High-Severity Vulnerability
File: package.json:49 (devDependencies)
Description: npm audit reports a high-severity vulnerability in esbuild (GHSA-gv7w-rqvm-qjhr β missing binary integrity verification enabling RCE via NPM_CONFIG_REGISTRY, and GHSA-g7r4-m6w7-qqqr β arbitrary file read on Windows dev server).
Impact: Low in context β esbuild is a devDependency used only in the regen-vectors script. It does not ship with the library, is not used in CI tests, and the vulnerabilities require either a malicious registry or running a Windows dev server. No production or consumer risk.
Fix: npm audit fix or pin esbuild to a patched version when available.
L-3: Claude Agent Workflow Has Empty allowed_non_write_users
File: .github/workflows/claude-agent.yml:67
Description: The allowed_non_write_users: "" setting means only users with write access to the repository can trigger the Claude agent via @claude mentions. This is the correct restrictive default, but worth documenting that any future addition of usernames here expands the attack surface (an allowed non-write user could instruct Claude to push malicious code).
Impact: Currently safe. Informational for future maintenance.
L-4: test-slow Job Uses Unpinned actions/checkout@v4 (Inconsistent with Others)
File: .github/workflows/ci.yml:52
Description: The test-slow job uses actions/checkout@v4 while all other jobs use @v6. This is likely a typo/oversight. While not a direct vulnerability (both resolve to maintained major tags), consistency ensures all jobs receive the same security patches.
Fix: Change to actions/checkout@v6 for consistency.
Dependency Review
| Dependency |
Version |
Type |
Risk Assessment |
@noble/ciphers |
^2.2.0 |
runtime |
Low risk β audited, no known CVEs, minimal dependency tree |
@noble/hashes |
^2.2.0 |
runtime |
Low risk β audited, no known CVEs, widely reviewed |
cborg |
^5.1.0 |
runtime |
Low risk β CBOR codec, no known CVEs |
sodium-native |
^5.1.0 |
runtime |
Low risk β native binding to libsodium, well-maintained |
esbuild |
^0.28.0 |
dev |
Medium β known vuln (GHSA-gv7w-rqvm-qjhr), dev-only |
vitest |
^4.1.4 |
dev |
Low risk β test framework, dev-only |
typescript |
~6.0.0 |
dev |
Low risk β compiler, dev-only |
@biomejs/biome |
^1.9.4 |
dev |
Low risk β linter, dev-only |
The runtime dependency graph is minimal (4 packages) and well-vetted. @noble/* packages are authored by Paul Miller, audited, and are the standard recommendation for JS cryptography. sodium-native is a mature native binding. cborg is a focused CBOR codec with no transitive dependencies of concern.
Positive Security Observations
These design choices significantly raise the bar:
- Verify-after-encrypt (
src/envelope/v1.ts:81-86) β catches AEAD implementation bugs before releasing ciphertext
- Indistinguishable authentication errors (
src/errors.ts:122-129) β prevents partitioning oracle attacks
- HKDF key separation with distinct info strings (
src/primitives/hkdf.ts:10-11) β prevents CEK/commit-key confusion
- No user-supplied nonces in the public API (
src/primitives/aead.ts:68) β nonce reuse is architecturally impossible
- Constant-time comparison via
timingSafeEqual on Node, XOR-accumulate fallback on browser (src/internal/constant-time.ts)
- Secure memory with
sodium_malloc/mlock and explicit insecureMemory acknowledgement for browsers
- PBKDF2 iteration floor of 1,000,000 (
src/passphrase.ts:60) β above OWASP minimums
- Exhaustive key zeroing in
finally blocks (src/envelope/rewrap.ts:192-201)
- RFC 8785 canonicalization with surrogate-pair validation (
src/canonical-json.ts:115-126)
- Provenance-signed npm publishing via OIDC Trusted Publishing (
publish.yml:58)
Summary Table
| # |
Severity |
File |
Finding |
| M-1 |
Medium |
.github/workflows/dependabot-handler.yml:13,25 |
pull_request_target + secrets: inherit pattern |
| M-2 |
Medium |
src/message-counter.ts:41 / src/envelope-client.ts:140 |
In-memory counter default allows AES-GCM nonce budget bypass on restart |
| M-3 |
Medium |
.github/workflows/security-review.yml:52 |
Unpinned @anthropic-ai/claude-code install in CI |
| L-1 |
Low |
src/envelope/v1.ts:154 |
JSON.parse of decrypted data β consumer-side prototype pollution risk |
| L-2 |
Low |
package.json:49 |
esbuild dev dependency has known high-severity CVE |
| L-3 |
Low |
.github/workflows/claude-agent.yml:67 |
Empty allowed_non_write_users β informational |
| L-4 |
Low |
.github/workflows/ci.yml:52 |
Inconsistent actions/checkout@v4 vs @v6 |
Overall assessment: The cryptographic core is well-implemented with no identified vulnerabilities. Findings are limited to CI/CD supply-chain hygiene (Medium) and informational best-practice items (Low). The library's security architecture β CSPRNG nonces, AAD binding, key commitment, verify-after-encrypt, constant-time operations, secure memory β is sound and consistent with its documented threat model.
Now I have a comprehensive picture of the codebase. Let me compile the security audit report.
Security Audit Report
Executive Summary
This is a well-engineered cryptographic library with strong security fundamentals. The codebase demonstrates awareness of advanced cryptographic threats (partitioning oracles, nonce budget exhaustion, key commitment), uses established primitives from the
@noble/*family, and implements important safety rails (verify-after-encrypt, constant-time comparisons, SecureBuffer with mlock, indistinguishable authentication errors).No Critical findings were identified. The library's cryptographic architecture is sound β nonces are CSPRNG-sourced, AAD binds all relevant fields, key separation is enforced via HKDF with distinct info strings, and commitment prevents multi-key attacks.
A small number of Medium and Low findings are noted below, primarily around CI/CD configuration and minor hardening opportunities.
Critical Findings
None.
High Findings
None.
Medium Findings
M-1:
pull_request_targetwithsecrets: inheritin Dependabot HandlerFile:
.github/workflows/dependabot-handler.yml:13,25Description: The workflow triggers on
pull_request_targetand passessecrets: inheritto a reusable workflow atde-otio/.github. While gated byif: github.actor == 'dependabot[bot]', this pattern inherits all repository secrets into a context where code from the PR (not the base branch) could potentially be executed if the called reusable workflow checks out the PR head ref.Attack scenario: If the reusable workflow at
de-otio/.github/.github/workflows/dependabot-claude-review.yml@mainusesactions/checkoutwithref: ${{ github.event.pull_request.head.ref }}and then runs arbitrary code (npm scripts, test hooks), a compromised Dependabot commit could exfiltrate secrets. Thedependabot[bot]actor check narrows the window significantly (an attacker must compromise Dependabot itself), but the pattern is flagged by GitHub's own security guidance.Fix: Audit the called workflow to ensure it never checks out the PR head or runs user-controlled code with inherited secrets. Alternatively, pass only the specific secrets needed rather than
secrets: inherit.M-2:
InMemoryMessageCounterResets Across Restarts β AES-GCM Nonce Budget BypassFile:
src/message-counter.ts:41-73;src/envelope-client.ts:140Description: The default
MessageCounteris in-memory and documented as unsafe for multi-process use. However, the library defaults to it when no counter is provided. If a consumer usesAES-256-GCMwith a long-lived key across process restarts (serverless, workers, container restarts), the counter resets to 0 each time β the 2Β³Β² nonce budget could be silently exceeded without triggeringNonceBudgetExceeded.Attack scenario: A chaoskb deployment using AES-256-GCM (selected for FIPS interop) on AWS Lambda processes ~10k messages/day. Over 1.2 years with frequent cold starts, the actual cumulative message count per key exceeds 2Β³Β² but the in-memory counter never reaches the cap. Random nonce collision probability rises, potentially enabling AES-GCM forgery (Joux multi-collision on shared nonces).
Mitigation already present: The library emits a
console.warnon first use, and XChaCha20-Poly1305 (the default) is unaffected. The code comments document this well.Recommended fix: Consider making
MessageCountera required parameter whenalgorithm: 'AES-256-GCM'is selected, rather than silently falling back to in-memory. This would force callers to acknowledge the operational requirement.M-3: Unpinned
npm install -g @anthropic-ai/claude-codein CI WorkflowsFile:
.github/workflows/security-review.yml:52,.github/workflows/weekly-security-audit.yml:54Description: The security review and weekly audit workflows install
@anthropic-ai/claude-codeglobally without a version pin. A supply chain compromise of this package (typosquatting, maintainer account takeover, or registry hijack) would execute arbitrary code with repository permissions (contents: read,pull-requests: write,issues: write,id-token: write).Attack scenario: An attacker compromises the
@anthropic-ai/claude-codenpm package. On the next PR or weekly schedule trigger, the compromised package runs with OIDC tokens that grant AWS Bedrock access and GitHub write permissions, enabling silent exfiltration or code injection.Fix: Pin to a specific version:
npm install -g @anthropic-ai/claude-code@0.2.x(or use a hash-pinned lockfile approach). Better: vendor or cache the package in a controlled artifact.Low Findings
L-1:
JSON.parseof Decrypted Plaintext Without Prototype Pollution GuardFile:
src/envelope/v1.ts:154Description: After successful AEAD decryption, the plaintext is parsed with
JSON.parse(...)and cast toRecord<string, unknown>. If the plaintext contains"__proto__"or"constructor"keys, downstream consumers that perform naive property spreading could be vulnerable to prototype pollution.Impact: Low β this is an authenticated path (only the legitimate key holder can produce parseable plaintext), and the vulnerability is in the consumer code, not this library. However, the library could defensively strip dangerous keys.
Fix (optional): This is informational. Documenting that consumers should use
Object.create(null)when spreading decrypted payloads into sensitive contexts is sufficient. The library should not alter plaintext content.L-2:
esbuildDev Dependency Has Known High-Severity VulnerabilityFile:
package.json:49(devDependencies)Description:
npm auditreports a high-severity vulnerability inesbuild(GHSA-gv7w-rqvm-qjhr β missing binary integrity verification enabling RCE viaNPM_CONFIG_REGISTRY, and GHSA-g7r4-m6w7-qqqr β arbitrary file read on Windows dev server).Impact: Low in context β
esbuildis a devDependency used only in theregen-vectorsscript. It does not ship with the library, is not used in CI tests, and the vulnerabilities require either a malicious registry or running a Windows dev server. No production or consumer risk.Fix:
npm audit fixor pinesbuildto a patched version when available.L-3: Claude Agent Workflow Has Empty
allowed_non_write_usersFile:
.github/workflows/claude-agent.yml:67Description: The
allowed_non_write_users: ""setting means only users with write access to the repository can trigger the Claude agent via@claudementions. This is the correct restrictive default, but worth documenting that any future addition of usernames here expands the attack surface (an allowed non-write user could instruct Claude to push malicious code).Impact: Currently safe. Informational for future maintenance.
L-4:
test-slowJob Uses Unpinnedactions/checkout@v4(Inconsistent with Others)File:
.github/workflows/ci.yml:52Description: The
test-slowjob usesactions/checkout@v4while all other jobs use@v6. This is likely a typo/oversight. While not a direct vulnerability (both resolve to maintained major tags), consistency ensures all jobs receive the same security patches.Fix: Change to
actions/checkout@v6for consistency.Dependency Review
@noble/ciphers@noble/hashescborgsodium-nativeesbuildvitesttypescript@biomejs/biomeThe runtime dependency graph is minimal (4 packages) and well-vetted.
@noble/*packages are authored by Paul Miller, audited, and are the standard recommendation for JS cryptography.sodium-nativeis a mature native binding.cborgis a focused CBOR codec with no transitive dependencies of concern.Positive Security Observations
These design choices significantly raise the bar:
src/envelope/v1.ts:81-86) β catches AEAD implementation bugs before releasing ciphertextsrc/errors.ts:122-129) β prevents partitioning oracle attackssrc/primitives/hkdf.ts:10-11) β prevents CEK/commit-key confusionsrc/primitives/aead.ts:68) β nonce reuse is architecturally impossibletimingSafeEqualon Node, XOR-accumulate fallback on browser (src/internal/constant-time.ts)sodium_malloc/mlock and explicitinsecureMemoryacknowledgement for browserssrc/passphrase.ts:60) β above OWASP minimumsfinallyblocks (src/envelope/rewrap.ts:192-201)src/canonical-json.ts:115-126)publish.yml:58)Summary Table
.github/workflows/dependabot-handler.yml:13,25pull_request_target+secrets: inheritpatternsrc/message-counter.ts:41/src/envelope-client.ts:140.github/workflows/security-review.yml:52@anthropic-ai/claude-codeinstall in CIsrc/envelope/v1.ts:154JSON.parseof decrypted data β consumer-side prototype pollution riskpackage.json:49esbuilddev dependency has known high-severity CVE.github/workflows/claude-agent.yml:67allowed_non_write_usersβ informational.github/workflows/ci.yml:52actions/checkout@v4vs@v6Overall assessment: The cryptographic core is well-implemented with no identified vulnerabilities. Findings are limited to CI/CD supply-chain hygiene (Medium) and informational best-practice items (Low). The library's security architecture β CSPRNG nonces, AAD binding, key commitment, verify-after-encrypt, constant-time operations, secure memory β is sound and consistent with its documented threat model.