Generated:2026-03-14
Agent:GitHub Copilot (Claude Sonnet 4.6)
CVE:CVE-2025-69873
Product:@cyclonedx/cdxgen v12.1.2
Affected lib:ajv v8.18.0
CVSS (library):7.5 HIGH (AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H)
CVSS (cdxgen):0.0 (NOT REACHABLE)
⚠ AI-generated. Must be reviewed by a human security expert before acting.
Chapter 1
Executive Summary
Audience: Management & Decision Makers
Bottom Line: cdxgen is NOT vulnerable in its current configuration
The CVE-2025-69873 vulnerability exists in a third-party dependency (ajv) but the specific feature required to trigger it is not enabled anywhere in cdxgen. No immediate action is required, though proactive hardening measures are recommended.
CVE ID
CVE-2025-69873
ReDoS in ajv
Library CVSS Score
7.5 / 10
HIGH severity
Risk to cdxgen
0.0
Code path unreachable
Affected component
ajv
JSON Schema validator
Vulnerability class
ReDoS
Denial of Service
Status
NOT REACHABLE
Confirmed by code review

🔍 What Is This Vulnerability?

CVE-2025-69873 is a ReDoS (Regular Expression Denial of Service) vulnerability discovered in ajv, the most widely used JSON Schema validation library in the Node.js ecosystem. When exploited, this type of vulnerability causes an application to consume 100% of a CPU core for an extended period, potentially rendering the service unresponsive.

The specific attack vector requires the $data option to be deliberately enabled when creating an ajv instance. This advanced feature allows JSON schema validation rules to reference values within the data being validated itself -- a powerful but dangerous capability that opens the door to attacker-controlled regular expressions.

🏭 Business Risk Assessment

For general consumers of ajv who use the $data feature, this is a HIGH severity vulnerability. An attacker could craft a malicious JSON payload containing a pathological regular expression, causing the server to hang and denying service to legitimate users.

For cdxgen specifically: After thorough code review of the entire source tree, the $data option is never enabled in any ajv instantiation. The cdxgen project's use of ajv is limited to validating internally generated SBOM (Software Bill of Materials) files against fixed CycloneDX schemas. The schema definitions are loaded from disk, not from user input. The risk surface is therefore effectively zero for the current codebase.

📋 Recommended Actions

PriorityActionRationale
Recommended Add a code comment in validator.js explicitly documenting that $data must never be enabled due to this CVE Prevents future developers from inadvertently enabling the vulnerable feature
Nice to have Add an automated test that asserts the Ajv instance is created without $data: true Regression protection -- catches accidental introduction during refactoring
Monitor Watch the ajv project for a patched release addressing the underlying ReDoS patterns Even without $data, other latent ReDoS patterns may exist in the library (acknowledged by repo owner)
Informational Close or accept the GitHub issue with a documented mitigation rationale The issue is correctly labeled code_not_reachable by the maintainer
Chapter 2
Technical Deep-Dive
Audience: Security Engineers & Senior Developers

🔌 Technical Context: What is ReDoS?

Regular Expression Denial of Service (ReDoS) exploits catastrophic backtracking in regex engines. When a regex with ambiguous quantifiers (e.g., (a+)+) is applied to an input that almost-but-not-quite matches, the engine explores an exponential number of paths. For a suitably crafted input string of length n, evaluation time grows as O(2n), blocking the JavaScript event loop and causing a full server hang.

The ajv-specific attack surface ($data) is described below.

🔍 The $data Feature in AJV Explained

Ajv's $data option enables runtime pattern injection. When enabled, schema keywords like pattern, minimum, and maximum can reference another field in the validated data using JSON Pointer syntax:

      json - malicious schema (only dangerous when $data: true)
      {
  "properties": {
    "regex_field": { "type": "string" },
    "target_field": {
      "type": "string",
      "pattern": { "$data": "1/regex_field" }
      // ^^^ The pattern is taken FROM the data being validated!
    }
  }
}
    

An attacker who controls regex_field can inject (a+)+$ (a catastrophic backtracking pattern) and trigger 100% CPU consumption when target_field contains a long non-matching string.

💾 The Vulnerable Call Chain

There are three entry points in cdxgen that call validateBom(). Each is analyzed below.

Path 1 — cdxgen CLI (--validate flag)

CLI: cdxgen --validate
bin/cdxgen.js
createBom()
lib/cli/index.js
postProcess()
lib/stages/postgen/postgen.js
if (options.validate)
bin/cdxgen.js:~1093
validateBom()
lib/helpers/validator.js:21

Note: validation is gated behind options.validate in Path 1. It only runs when the user explicitly passes the --validate flag.

Path 2 — evinse CLI (always validates)

CLI: evinse -i bom.json
bin/evinse.js
prepareDB()
lib/evinser/evinser.js
analyzeProject()
lib/evinser/evinser.js
createEvinseFile()
lib/evinser/evinser.js
validateBom()
lib/helpers/validator.js:21

Evinse always calls validateBom() unconditionally after SBOM generation -- there is no flag gate. The input BOM comes from a user-supplied file (-i bom.json), making this the wider attack surface of the two paths.

Path 3 — REPL Mode

CLI: cdxi (repl)
bin/repl.js
validateBom()
lib/helpers/validator.js:21

📊 Attack Entry Point Diagram

flowchart TD A1["💻 User: cdxgen --validate ..." ] --> B1["bin/cdxgen.js\nmain IIFE"] A2["💻 User: evinse -i bom.json ..."] --> B2["bin/evinse.js\nmain IIFE"] A3["💻 User: cdxi (repl)"] --> B3["bin/repl.js\nmain IIFE"] B1 -->|"options.validate == true"| G["lib/helpers/validator.js\nvalidateBom()"] B2 -->|"always"| G B3 -->|"conditional"| G G --> H["new Ajv({ schemas,\nstrict: false,\nlogger: false,\n...\nNO $data option!})\nlines 47-57"] H -->|"$data ABSENT"| I["✅ SAFE PATH\najv validates against\nstatic CycloneDX schema"] H -.->|"IF $data: true were set\n(HYPOTHETICAL)"| J["🚨 VULNERABLE PATH\nAttacker-controlled regex\ninjected via schema pattern\n-> ReDoS / CPU exhaustion"] style J fill:#fef2f2,stroke:#ef4444,color:#7f1d1d style I fill:#f0fdf4,stroke:#22c55e,color:#166534 style H fill:#fffbeb,stroke:#f59e0b,color:#78350f
Fig. 1 — Attack call tree with the safe and hypothetical vulnerable paths distinguished. The dashed edge (hypothetical path) is NOT reachable in the current codebase.

🔓 The Critical Evidence: Ajv Instantiation

This is the exact Ajv instantiation at lib/helpers/validator.js lines 47-57. The absence of $data: true is the key mitigation:

      lib/helpers/validator.js -- lines 1-78 (validateBom function)
      import { readFileSync } from "node:fs";
import { join } from "node:path";
import Ajv from "ajv";          // version 8.18.0 (pinned in package.json overrides)
import addFormats from "ajv-formats";

export const validateBom = (bomJson) => {
  if (!bomJson) return true;
  const specVersion = bomJson.specVersion;
  const schema = JSON.parse(
    readFileSync(join(dirName, "data", `bom-${specVersion}.schema.json`), "utf-8")
  );
  // ... (three more schema files loaded from disk, not from user input)

  const ajv = new Ajv({
    schemas,
    strict: false,
    logger: false,
    verbose: true,
    code: { source: true, lines: true, optimize: true },
    // $data: true <-- NOT PRESENT. CVE-2025-69873 code path is BLOCKED HERE.
  });
  addFormats(ajv);
  const validate = ajv.getSchema(`http://cyclonedx.org/schema/bom-${specVersion}.schema.json`);
  const isValid = validate(bomJson);
  if (!isValid) {
    console.log(`Schema validation failed for ${bomJson.metadata.component.name}`);
    return false;
  }
  return (
    validateMetadata(bomJson) &&
    validatePurls(bomJson)    &&
    validateRefs(bomJson)     &&
    validateProps(bomJson)
  );
};
    

📈 Data Flow: Why the Path is Blocked

sequenceDiagram participant Attacker participant cdxgen_CLI as cdxgen CLI / evinse participant validateBom as validateBom() [validator.js] participant Ajv as Ajv instance Attacker->>cdxgen_CLI: Provide crafted BOM JSON input cdxgen_CLI->>validateBom: validateBom(bomJson) Note over validateBom: bomJson contains attacker data validateBom->>validateBom: Load schema files FROM DISK (trusted) validateBom->>Ajv: new Ajv({ schemas, strict:false, ...}) Note over Ajv: $data option NOT passed
Runtime pattern injection DISABLED validateBom->>Ajv: getSchema(CycloneDX schema URL) validateBom->>Ajv: validate(bomJson) Note over Ajv: Schema pattern keywords use
HARDCODED values from disk schema.
Attacker data CANNOT inject a regex. Ajv-->>validateBom: validation result (true/false) validateBom-->>cdxgen_CLI: boolean result Note over Attacker: ReDoS CANNOT be triggered.
No attacker-controlled regex ever executes.
Fig. 2 — Data flow diagram showing how the absence of $data prevents attacker-controlled regex injection.

🏷 CWE / OWASP Mapping

IdentifierNameApplicabilityEvidence
CWE-400 Uncontrolled Resource Consumption Primary ReDoS consumes unbounded CPU by design; no timeout in Node.js regex evaluation
CWE-1333 Inefficient Regular Expression Complexity Primary The ajv library uses regex patterns that can catastrophically backtrack on adversarial input
CWE-20 Improper Input Validation Secondary Root cause: when $data:true, attacker-supplied data is used as a schema pattern without sanitization
CWE-829 Inclusion of Functionality from Untrusted Control Sphere Secondary The $data feature dynamically sources validation logic (regex) from untrusted input
OWASP A06:2021 Vulnerable and Outdated Components Primary The vulnerability is in ajv (a third-party dependency); cdxgen depends on it without isolation
OWASP A05:2021 Security Misconfiguration Secondary Enabling $data: true without input sanitization represents a dangerous optional feature misconfiguration
ASVS V5.3.5 Verify regex not susceptible to catastrophic backtracking ASVS Control The ajv library's internal pattern handling does not guarantee polynomial-time evaluation
ASVS V14.2.1 Verify all components are up to date ASVS Control Dependency management should track known CVEs in transitive dependencies

🔧 Exploitability Assessment (CVSS v3.1 Dimensions)

DimensionValue for ajv (library)Value for cdxgenRationale
Attack VectorNetworkN/A (not exploitable)ajv can be reached remotely if serving HTTP; cdxgen BOM validation is local
Attack ComplexityLowN/ACrafting a catastrophic regex is well-documented; PoC exists publicly
Privileges RequiredNoneN/ANo authentication required to supply a BOM to evinse's -i flag
User InteractionNoneN/AFully automated trigger once the file is submitted
ScopeUnchangedN/ADoS is confined to the Node.js process; no privilege escalation
ConfidentialityNoneNoneReDoS leaks no data
IntegrityNoneNoneNo data modification
AvailabilityHighNoneIn the library, full CPU hang. In cdxgen, $data is absent so no impact.

🛠 Remediation Options

Option 1 (Recommended): Defensive Comment + Negative Assertion Test

Add an explicit comment documenting the CVE and a test that asserts $data is never present in the Ajv config. This costs nothing and provides permanent regression protection.

📄 Before / After Code Change — validator.js

Before (current, lib/helpers/validator.js ~line 47):

  const ajv = new Ajv({
    schemas,
    strict: false,
    logger: false,
    verbose: true,
    code: { source: true, lines: true, optimize: true },
  });

After (hardened):

  // SECURITY: $data option intentionally NOT enabled.
  // Enabling $data: true would expose CVE-2025-69873 (ReDoS via runtime pattern injection).
  // See https://github.com/cdxgen/cdxgen/issues/3484
  const ajv = new Ajv({
    schemas,
    strict: false,
    logger: false,
    verbose: true,
    code: { source: true, lines: true, optimize: true },
    // DO NOT ADD $data: true -- CVE-2025-69873
  });
📄 Regression Test (validator.poku.js or validator.test.js)
// Test that Ajv is never instantiated with $data: true (CVE-2025-69873 guard)
import assert from "node:assert";
import { readFileSync } from "node:fs";

const validatorSource = readFileSync("./lib/helpers/validator.js", "utf-8");
// Ensure $data: true is never present in the Ajv options
assert.ok(
  !/new\s+Ajv\s*\([^)]*\$data\s*:\s*true/s.test(validatorSource),
  "CVE-2025-69873: Ajv must not be instantiated with $data:true"
);

Option 2: Pin ajv to a patched version when available

When ajv releases a version that mitigates the underlying ReDoS patterns (not just the $data gate), update the overrides section in package.json. Currently pinned at:

// package.json overrides section (current)
"ajv": "8.18.0"         // update to patched version when released
"ajv-formats": "3.0.1" // keep in sync

Option 3: Input BOM validation isolation (defense-in-depth)

A more robust long-term option for the evinse path is to add a file size and structure sanity check before invoking validateBom(), to limit the amount of attacker-controlled data that enters the validator:

📄 Defensive pre-validation guard (evinse.js)
// In bin/evinse.js, before calling validateBom(bomJson)
const MAX_BOM_SIZE_BYTES = 10 * 1024 * 1024; // 10 MB
const inputStats = fs.statSync(args.input);
if (inputStats.size > MAX_BOM_SIZE_BYTES) {
  console.error("Input BOM file exceeds maximum size limit. Skipping validation.");
  process.exit(1);
}
// Then validate normally
if (!validateBom(bomJson)) {
  process.exit(1);
}

📄 Evidence Appendix

Abyss Queries Executed

Ajv JSON schema validator instantiation options configuration ajv $data option schema validation pattern regex how does cdxgen validate JSON schema and handle untrusted user input cdxgen environment variable feature flag configuration options CLI arguments who calls validateBom function in cdxgen

Files Read in Full

lib/helpers/validator.js bin/cdxgen.js bin/evinse.js package.json

External Sources Consulted

https://github.com/CycloneDX/cdxgen/issues/3484 https://github.com/ajv-validator/ajv/issues/2581 https://github.com/EthanKim88/ethan-cve-disclosures/blob/main/CVE-2025-69873-ajv-ReDoS.md (429 on fetch)
Chapter 3
Junior Analyst Learning Brief
Audience: Junior Security Analysts & Developers learning AppSec

📚 What Class of Vulnerability Is This?

ReDoS (Regular Expression Denial of Service)
A type of Denial of Service where an attacker crafts an input that causes a regular expression to take exponential time to evaluate. No memory is exhausted -- only CPU. The JavaScript event loop is single-threaded, so the entire Node.js server hangs for the duration.
📈
Catastrophic Backtracking
Certain regex patterns, like (a+)+$ or (a|aa)+$, cause the regex engine to retry exponentially many path combinations when the input nearly (but not quite) matches. The time grows as O(2n) where n is the input length.
🔑
Third-Party Supply Chain Risk
This CVE is a perfect example of supply chain risk: the vulnerable code is not in cdxgen itself but in a dependency (ajv). Even if you write perfect code, your dependencies may not. This is why dependency scanning is critical.
🔌
Feature Flag Gating
This CVE requires a specific optional feature ($data: true) to be enabled. This is a very common pattern: a library offers a powerful but dangerous advanced feature. The safe default is to leave it off. Always check how you instantiate third-party libraries.

🔍 How Would You Detect This in a Code Review?

Look for these patterns when reviewing Node.js code that uses ajv:

  • Any call to new Ajv({ $data: true }) — this is the direct trigger
  • User-supplied JSON being used as a JSON Schema (the schema itself comes from user input)
  • regex/pattern values in schemas that reference external HTTP sources or user uploads
  • Large JSON files or arbitrary user content being passed to any validator without size limits
  • Static schema files loaded from disk at startup — safe because the attacker cannot control them
  • Schemas pinned at build time and committed to the repository — safe

🧰 How Would You Test for This?

Manual Test Approach

  1. Find all Ajv instantiations in the codebase: grep -r "new Ajv(" --include="*.js" .
  2. Check each instantiation for the $data option.
  3. For any $data: true instance, trace where the schema comes from. If it can be influenced by user input, it is vulnerable.
  4. Proof of concept: Construct a schema with "pattern": {"$data": "1/userControlledField"} and a payload with userControlledField: "(a+)+$" and targetField: "aaaaaaaaaaaaaaab". Measure evaluation time.

Automated Tools

ToolTypeWhat it finds
safe-regex / vuln-regex-detectorStatic analysisDetects catastrophic backtracking patterns in regex literals
npm audit / Snyk / Socket.devSCA (Software Composition Analysis)Matches package versions against CVE databases including CVE-2025-69873
SemgrepSASTWrite a custom rule: search for new Ajv({ ... $data: true ... })
OWASP Dependency-CheckSCAScans node_modules against NVD CVE database
k6 / ArtilleryLoad testingTest server response time degradation with crafted payloads

⚡ Key Takeaways

  • Not all CVEs are equal. This CVE has a CVSS score of 7.5 for the ajv library, but 0.0 for cdxgen because the vulnerable feature is disabled. Always analyze the reachability of a vulnerability in the specific application context, not just the library score.
  • Optional dangerous features are a common trap. Libraries often expose powerful features that are safe when used correctly but dangerous when misused. Read the library documentation carefully before enabling advanced options. When you see "experimental" or "advanced" in the docs, threat-model that feature.
  • Defense-in-depth still matters. Even though cdxgen is not exploitable today, a future developer could accidentally enable $data: true. A comment and a regression test cost nothing and prevent the bug from ever being introduced.
  • Understand the event loop. In Node.js, the event loop is single-threaded. Any synchronous CPU-bound operation (like catastrophic regex backtracking) blocks ALL requests. This is why ReDoS can take down an entire Node.js server with a single request.
  • Read the issue tracker. The cdxgen repo owner's comment "code_not_reachable" is exactly the right approach. Security issues require not just finding CVEs, but analyzing reachability in your specific deployment. A CVE in a dependency does not automatically mean your application is vulnerable.

🔗 Learning Resources