{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "title": "Vulnlog",
  "description": "Vulnlog YAML format for tracking, analyzing, and managing software vulnerabilities reported by SCA scanners. Each file is scoped to a single release branch and serves as a human-authored decision ledger from which the CLI generates scanner suppression files and HTML reports. See https://vulnlog.dev for the full specification",
  "examples": [
    {
      "schemaVersion": "1",
      "project": {
        "organization": "Example Org",
        "name": "demo-project",
        "author": "Security Team",
        "contact": "security@example.org"
      },
      "tags": [
        {
          "id": "container",
          "description": "Shipped as Docker container"
        }
      ],
      "releases": [
        {
          "id": "1.0.0",
          "published_at": "2026-01-15"
        },
        {
          "id": "1.1.0"
        }
      ],
      "vulnerabilities": [
        {
          "id": "CVE-2026-5678",
          "releases": [ "1.0.0" ],
          "packages": [ "pkg:npm/image-lib@3.1.0" ],
          "reports": [
            {
              "reporter": "trivy",
              "at": "2026-02-01"
            }
          ],
          "analysis": "The vulnerable code path requires SVG input. Our application only accepts PNG and JPEG.",
          "analyzed_at": "2026-02-02",
          "verdict": "not affected",
          "justification": "vulnerable code not in execute path"
        }
      ]
    }
  ],
  "type": "object",
  "required": [ "schemaVersion", "project", "releases", "vulnerabilities" ],
  "properties": {
    "schemaVersion": {
      "description": "Schema version of the Vulnlog file",
      "const": "1"
    },
    "project": {
      "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1project.json"
    },
    "tags": {
      "description": "Tag definitions for categorizing and scoping vulnerability entries. Optional",
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1tag.json"
      }
    },
    "releases": {
      "description": "Releases tracked in this Vulnlog file. Must be in chronological order (oldest first). Array position is used by the CLI to resolve '--release' range filtering",
      "type": "array",
      "minItems": 1,
      "items": {
        "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1release.json"
      }
    },
    "vulnerabilities": {
      "description": "Vulnerability entries. May be empty. Convention: newest entries first (enforced by the linter, no functional impact)",
      "type": "array",
      "items": {
        "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1vulnerability.json"
      }
    }
  },
  "additionalProperties": false,
  "$defs": {
    "https://vulnlog.dev/schemas/v1/defs/project.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Project",
      "description": "Project-level metadata. Used in report generation and as author information in VEX documents",
      "examples": [
        {
          "organization": "Vulnlog",
          "name": "Vulnlog",
          "author": "Vulnlog Dev Team"
        },
        {
          "organization": "Example Org",
          "name": "demo-project",
          "author": "Security Team",
          "contact": "security@example.org"
        }
      ],
      "type": "object",
      "required": [ "organization", "name", "author" ],
      "properties": {
        "organization": {
          "description": "Name of the organization or vendor",
          "examples": [ "Vulnlog" ],
          "type": "string",
          "minLength": 1
        },
        "name": {
          "description": "Name of the software project",
          "examples": [ "Vulnlog" ],
          "type": "string",
          "minLength": 1
        },
        "author": {
          "description": "Name of the responsible security team or author. Used as the author in VEX documents",
          "examples": [ "Vulnlog Dev Team" ],
          "type": "string",
          "minLength": 1
        },
        "contact": {
          "description": "Contact email for the security team. Used as the author contact in VEX documents",
          "examples": [ "security@example.org" ],
          "type": "string",
          "format": "email"
        }
      },
      "additionalProperties": false
    },
    "https://vulnlog.dev/schemas/v1/defs/common.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Common Definitions",
      "description": "Shared types and formats used across the Vulnlog schema",
      "$defs": {
        "date": {
          "description": "Date in ISO 8601 format (YYYY-MM-DD)",
          "examples": [ "2026-04-07" ],
          "type": "string",
          "pattern": "^\\d{4}-\\d{2}-\\d{2}$"
        },
        "purl": {
          "description": "Package URL (purl) identifying a package. Follows the format 'pkg:<type>/<namespace>/<name>@<version>'. See https://github.com/package-url/purl-spec",
          "examples": [
            "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
            "pkg:npm/image-lib@3.1.0",
            "pkg:docker/demo-project@8.1.1",
            "pkg:golang/golang.org/x/net@0.17.0",
            "pkg:pypi/requests@2.28.0",
            "pkg:generic/demo-project@8.1.1"
          ],
          "type": "string",
          "pattern": "^pkg:[a-zA-Z][a-zA-Z0-9.+-]*/"
        },
        "verdict": {
          "description": "The human triage decision for a vulnerability. Absence indicates the vulnerability is under investigation. 'affected' and 'risk acceptable' require a severity; 'not affected' requires a justification",
          "oneOf": [
            {
              "description": "The vulnerability impacts this release and remediation is expected. Requires 'severity'",
              "const": "affected"
            },
            {
              "description": "The vulnerability is present in a dependency but does not impact this release. Requires 'justification'. Automatically included in generated suppression files",
              "const": "not affected"
            },
            {
              "description": "The vulnerability impacts this release but the risk has been assessed and accepted without remediation. Requires 'severity' and an explicit 'suppress' block to suppress the finding",
              "const": "risk acceptable"
            }
          ]
        },
        "severity": {
          "description": "Severity assessment of a vulnerability. Required when verdict is 'affected' or 'risk acceptable'",
          "oneOf": [
            {
              "description": "Immediate action required. Exploitable with severe impact",
              "const": "critical"
            },
            {
              "description": "Action required. Significant impact if exploited",
              "const": "high"
            },
            {
              "description": "Action recommended. Moderate impact",
              "const": "medium"
            },
            {
              "description": "Low priority. Minimal impact or difficult to exploit",
              "const": "low"
            }
          ]
        },
        "justification": {
          "description": "Explains why a vulnerability does not affect this release. Values align with the OpenVEX justification vocabulary. Required when verdict is 'not affected'",
          "oneOf": [
            {
              "description": "The vulnerable component is not included in the deliverable",
              "const": "component not present"
            },
            {
              "description": "The vulnerable code is present but the specific vulnerable function or code path is not included",
              "const": "vulnerable code not present"
            },
            {
              "description": "The vulnerable code is present but cannot be reached during execution",
              "const": "vulnerable code not in execute path"
            },
            {
              "description": "The vulnerable code is reachable but cannot be triggered by an attacker",
              "const": "vulnerable code cannot be controlled by adversary"
            },
            {
              "description": "The vulnerability is mitigated by existing controls (e.g., WAF, input validation)",
              "const": "inline mitigations already exist"
            }
          ]
        },
        "reporterType": {
          "description": "The scanner or source type that reported the vulnerability. Determines the suppression file format for reporters with suppression support",
          "oneOf": [
            {
              "description": "Trivy. Suppression output: .trivyignore.yaml",
              "const": "trivy"
            },
            {
              "description": "Snyk. Suppression output: .snyk",
              "const": "snyk"
            },
            {
              "description": "OWASP Dependency Check. Suppression support planned",
              "const": "dependency-check"
            },
            {
              "description": "Grype. Suppression support planned",
              "const": "grype"
            },
            {
              "description": "Cargo Audit (Rust). Suppression support planned",
              "const": "cargo-audit"
            },
            {
              "description": "Semgrep. No suppression support",
              "const": "semgrep"
            },
            {
              "description": "npm audit. No suppression support",
              "const": "npm-audit"
            },
            {
              "description": "GitHub Advisory Database. No suppression support",
              "const": "github-advisory"
            },
            {
              "description": "Generic or non-scanner source. Requires the 'source' field on the report entry",
              "const": "other"
            }
          ]
        }
      }
    },
    "https://vulnlog.dev/schemas/v1/defs/release.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Release",
      "description": "A release tracked in this Vulnlog file. Releases must be listed in chronological order (oldest first) in the parent array. Array position is used by the CLI to resolve '--release' range filtering: a release at position N includes all releases at positions 0 through N",
      "examples": [
        {
          "id": "8.1.1",
          "published_at": "2026-01-15"
        },
        {
          "id": "2.0.0",
          "purls": [
            {
              "purl": "pkg:generic/demo-project@2.0.0",
              "tags": [ "binary" ]
            },
            {
              "purl": "pkg:docker/demo-project@2.0.0",
              "tags": [ "container" ]
            }
          ]
        }
      ],
      "type": "object",
      "required": [ "id" ],
      "properties": {
        "id": {
          "description": "Unique release identifier. Typically a semantic version. Referenced by vulnerability entries",
          "examples": [ "8.1.1", "2.0.0-SNAPSHOT", "0.12.0" ],
          "type": "string",
          "minLength": 1
        },
        "published_at": {
          "description": "Publication date of the release. Absence indicates the release is not yet published",
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/date"
        },
        "purls": {
          "description": "Versioned Package URLs identifying the release artifacts. Used as product identifiers in VEX documents",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1release.json/$defs/purlEntry"
          }
        }
      },
      "additionalProperties": false,
      "$defs": {
        "purlEntry": {
          "description": "A release artifact identified by its Package URL, optionally tagged for scoped VEX generation",
          "type": "object",
          "required": [ "purl" ],
          "properties": {
            "purl": {
              "description": "The versioned Package URL for this artifact in this release",
              "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/purl"
            },
            "tags": {
              "description": "Tags associated with this purl. Must reference tag IDs defined in the top-level 'tags' section. Used to match vulnerability tags for scoped VEX generation",
              "examples": [
                [ "binary" ],
                [ "container" ]
              ],
              "type": "array",
              "minItems": 1,
              "items": {
                "type": "string",
                "minLength": 1
              }
            }
          },
          "additionalProperties": false
        }
      }
    },
    "https://vulnlog.dev/schemas/v1/defs/tag.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Tag",
      "description": "A tag for categorizing and scoping vulnerability entries. Tags can represent customer-facing deliverables ('binary', 'container', 'sdk'), internal usage contexts ('build-dep', 'test-infra'), or any other team-defined categorization",
      "examples": [
        {
          "id": "container",
          "description": "Shipped as Docker container"
        }
      ],
      "type": "object",
      "required": [ "id" ],
      "properties": {
        "id": {
          "description": "Unique identifier for the tag. Referenced by vulnerability entries and release purl entries",
          "examples": [ "binary", "container", "build-dep", "dev dependency" ],
          "type": "string",
          "minLength": 1
        },
        "description": {
          "description": "Human-readable description of what this tag represents",
          "examples": [ "Shipped as Docker container", "Build-time dependency only" ],
          "type": "string"
        }
      },
      "additionalProperties": false
    },
    "https://vulnlog.dev/schemas/v1/defs/report.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Report Entry",
      "description": "Records that a specific SCA scanner or other source reported the vulnerability. Each scanner that reported the same finding is listed as a separate report entry, enabling per-scanner suppression configuration",
      "examples": [
        {
          "reporter": "trivy",
          "at": "2026-04-07"
        },
        {
          "reporter": "snyk",
          "at": "2026-04-07",
          "vuln_ids": [ "SNYK-JAVA-CHQOSLOGBACK-6097492" ],
          "suppress": {
            "expires_at": "2026-10-01"
          }
        }
      ],
      "type": "object",
      "if": {
        "properties": {
          "reporter": {
            "const": "other"
          }
        }
      },
      "then": {
        "required": [ "source" ]
      },
      "required": [ "reporter" ],
      "properties": {
        "reporter": {
          "description": "The scanner or source type that reported this vulnerability",
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/reporterType"
        },
        "at": {
          "description": "Date the vulnerability was reported. When omitted, the CLI uses the current date during output generation",
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/date"
        },
        "source": {
          "description": "Free-text description of the report source. Optional for scanner reporters, required when reporter is 'other'",
          "examples": [ "Internal security review", "Customer report" ],
          "type": "string",
          "minLength": 1
        },
        "vuln_ids": {
          "description": "Scanner-specific vulnerability identifiers. Used instead of the vulnerability's 'id' when generating scanner-specific suppression files. Set this when the scanner uses its own identifier rather than the CVE",
          "examples": [
            [ "SNYK-JAVA-CHQOSLOGBACK-6097492" ],
            [ "GHSA-2m67-wjpj-xhg9" ]
          ],
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "suppress": {
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1report.json/$defs/suppressEntry"
        }
      },
      "additionalProperties": false,
      "$defs": {
        "suppressEntry": {
          "description": "Scanner-specific suppression configuration. Required to suppress findings with verdict 'affected' (without resolution), 'risk acceptable', or absent verdict. Not required for 'not affected' findings (always included in suppression output) or 'affected' findings with a 'resolution' (never included). When present without 'expires_at', the suppression is permanent",
          "examples": [
            {},
            {
              "expires_at": "2026-06-01"
            }
          ],
          "type": "object",
          "properties": {
            "expires_at": {
              "description": "Expiration date of the suppression. After this date, the entry is excluded from the generated suppression file. Omit for permanent suppression",
              "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/date"
            }
          },
          "additionalProperties": false
        }
      }
    },
    "https://vulnlog.dev/schemas/v1/defs/vulnerability.json": {
      "$schema": "https://json-schema.org/draft/2020-12/schema",
      "title": "Vulnlog Vulnerability Entry",
      "description": "A single vulnerability finding and the human verdict about it. The verdict should reflect the most severe impact across all listed packages. Each vulnerability's 'id' must be unique across all entries and must not appear in the 'aliases' of another entry",
      "examples": [
        {
          "id": "CVE-2021-44228",
          "name": "Log4Shell",
          "releases": [ "8.1.1" ],
          "packages": [ "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1" ],
          "reports": [
            {
              "reporter": "trivy",
              "at": "2021-12-10"
            }
          ],
          "analysis": "The JNDI lookup feature is disabled in our configuration.",
          "analyzed_at": "2021-12-11",
          "verdict": "not affected",
          "justification": "inline mitigations already exist"
        },
        {
          "id": "CVE-2023-6481",
          "releases": [ "0.11.0" ],
          "tags": [ "dev dependency" ],
          "packages": [ "pkg:maven/ch.qos.logback/logback-classic@1.3.5" ],
          "reports": [
            {
              "reporter": "snyk",
              "at": "2026-04-06"
            }
          ],
          "analysis": "Logback is a ktlint configuration dependency and not part of the application.",
          "analyzed_at": "2026-04-06",
          "verdict": "risk acceptable",
          "severity": "low",
          "resolution": {
            "in": "0.12.0",
            "at": "2026-04-07",
            "note": "Update ktlint to 14.2.0 also updates logback to 1.3.14"
          }
        }
      ],
      "type": "object",
      "allOf": [
        {
          "if": {
            "required": [ "verdict" ],
            "properties": {
              "verdict": {
                "enum": [ "affected", "risk acceptable" ]
              }
            }
          },
          "then": {
            "required": [ "severity" ]
          }
        },
        {
          "if": {
            "required": [ "verdict" ],
            "properties": {
              "verdict": {
                "const": "not affected"
              }
            }
          },
          "then": {
            "required": [ "justification" ]
          }
        },
        {
          "if": {
            "not": {
              "required": [ "verdict" ],
              "properties": {
                "verdict": {
                  "const": "not affected"
                }
              }
            }
          },
          "then": {
            "not": {
              "required": [ "justification" ]
            }
          }
        }
      ],
      "required": [ "id", "releases", "packages", "reports" ],
      "properties": {
        "id": {
          "description": "Primary identifier of the vulnerability. Preferably a CVE ID; may be a scanner-specific ID if no CVE exists. Must be unique across all vulnerability entries and aliases in the file",
          "examples": [ "CVE-2024-12798", "GHSA-2m67-wjpj-xhg9" ],
          "type": "string",
          "minLength": 1
        },
        "name": {
          "description": "Common name of the vulnerability, if one exists. Displayed prominently in reports",
          "examples": [ "Log4Shell", "Heartbleed", "Spring4Shell" ],
          "type": "string",
          "minLength": 1
        },
        "aliases": {
          "description": "Alternative identifiers for the same vulnerability (e.g., GHSA IDs, scanner-specific IDs). Must not overlap with any 'id' or 'aliases' in other entries",
          "examples": [
            [ "GHSA-2m67-wjpj-xhg9", "SNYK-JAVA-TOOLSJACKSONCORE-15907550" ]
          ],
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "releases": {
          "description": "Release identifiers affected by this vulnerability. Must reference IDs from the top-level 'releases' section",
          "examples": [
            [ "0.11.0" ],
            [ "8.1.1", "8.2.0" ]
          ],
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "description": {
          "description": "Brief human-readable description of the vulnerability. Optional.",
          "type": "string",
          "minLength": 1
        },
        "packages": {
          "description": "Package URLs of affected dependencies. Most SCA scanners include the PURL in their findings.",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/purl"
          }
        },
        "reports": {
          "description": "Scanner reports that identified this vulnerability. Each scanner that reported the same finding is a separate entry",
          "type": "array",
          "minItems": 1,
          "items": {
            "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1report.json"
          }
        },
        "tags": {
          "description": "Tags for categorizing and scoping this vulnerability. Must reference tag IDs defined in the top-level 'tags' section. Used for CLI '--tag' filtering on 'report' and 'suppress' commands",
          "examples": [
            [ "container" ],
            [ "dev dependency" ]
          ],
          "type": "array",
          "minItems": 1,
          "items": {
            "type": "string",
            "minLength": 1
          }
        },
        "analysis": {
          "description": "Free-text analysis and rationale for the triage decision. Presence of this field moves the entry out of the 'under investigation' state",
          "examples": [
            "The affected StreamReadConstraints class is not used in the application."
          ],
          "type": "string"
        },
        "analyzed_at": {
          "description": "Date the analysis was performed. Must not be before the earliest report date",
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/date"
        },
        "comment": {
          "description": "Additional information not related to the analysis itself. Not used in any output generation",
          "examples": [ "Fix will be in 1.5.25" ],
          "type": "string"
        },
        "severity": {
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/severity"
        },
        "verdict": {
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/verdict"
        },
        "justification": {
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/justification"
        },
        "resolution": {
          "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1vulnerability.json/$defs/resolutionEntry"
        }
      },
      "additionalProperties": false,
      "$defs": {
        "resolutionEntry": {
          "description": "Records that the underlying dependency was updated or the vulnerability was otherwise addressed. Independent of the verdict: a 'not affected' entry may still have a defensive update, and an 'affected' entry may be fixed in a subsequent release. Entries with a 'verdict: affected' and a resolution are excluded from generated suppression files",
          "type": "object",
          "required": [ "in" ],
          "properties": {
            "in": {
              "description": "Release in which the resolution was applied. Must reference a release ID from the top-level 'releases' section",
              "examples": [ "0.12.0", "2.0.1" ],
              "type": "string",
              "minLength": 1
            },
            "at": {
              "description": "Date the resolution was applied",
              "$ref": "#/$defs/https:~1~1vulnlog.dev~1schemas~1v1~1defs~1common.json/$defs/date"
            },
            "ref": {
              "description": "Reference to the issue, pull request, or ticket tracking the resolution",
              "examples": [ "https://github.com/vulnlog/vulnlog/pull/82" ],
              "type": "string",
              "format": "uri"
            },
            "note": {
              "description": "Brief description of how the vulnerability was resolved",
              "examples": [ "Updated image-lib from 3.1.0 to 3.2.0" ],
              "type": "string"
            }
          },
          "additionalProperties": false
        }
      }
    }
  }
}
