{
  "$schema": "https://json-schema.org/draft/2020-12/schema",
  "$id": "https://cairnsecurity.com/opf/schema/opf-1.1.schema.json",
  "title": "Open Pentest Format 1.1",
  "description": "A portable JSON document carrying a set of penetration test findings. A finding may be a library template or an instance observed during an engagement.",
  "type": "object",
  "required": ["opfVersion", "findings"],
  "additionalProperties": true,
  "properties": {
    "opfVersion": {
      "type": "string",
      "pattern": "^1\\.[0-9]+$",
      "description": "The OPF version this document conforms to. Readers accept any 1.x document.",
      "examples": ["1.1", "1.0"]
    },
    "textFormat": {
      "enum": ["html", "markdown", "text"],
      "default": "html",
      "description": "How to interpret the prose fields on every finding (description, impact, recommendation, technicalDetails). Consumers should sanitise on import."
    },
    "metadata": {
      "type": "object",
      "additionalProperties": true,
      "description": "Optional provenance for the document.",
      "properties": {
        "source": {
          "type": "string",
          "description": "The tool or organisation that produced this document."
        },
        "exportedAt": {
          "type": "string",
          "format": "date-time",
          "description": "RFC 3339 timestamp of export."
        },
        "description": {
          "type": "string",
          "description": "Human label for what this document contains."
        },
        "findingCount": {
          "type": "integer",
          "minimum": 0,
          "description": "Advisory count. Readers should trust the length of the findings array over this value."
        }
      }
    },
    "findings": {
      "type": "array",
      "description": "The findings. May be empty.",
      "items": { "$ref": "#/$defs/finding" }
    }
  },
  "$defs": {
    "severity": {
      "enum": ["critical", "high", "medium", "low", "informational"],
      "description": "Lowercase. One of the five OPF severities."
    },
    "reference": {
      "type": "object",
      "description": "A typed link.",
      "required": ["url"],
      "additionalProperties": true,
      "properties": {
        "url": {
          "type": "string",
          "format": "uri",
          "description": "The link."
        },
        "title": {
          "type": "string",
          "description": "Human label for the link."
        },
        "type": {
          "type": "string",
          "description": "What kind of reference this is. The values below are the conventional set; other values are permitted, and a reader that does not recognise one keeps it.",
          "examples": ["cve", "cwe", "owasp", "mitre", "vendor", "article", "other"]
        }
      }
    },
    "finding": {
      "type": "object",
      "description": "A single finding. Only title and severity are required, so a scanner export and a hand-authored library template both validate.",
      "required": ["title", "severity"],
      "additionalProperties": true,
      "properties": {
        "id": {
          "type": "string",
          "description": "Stable identifier, used for deduplication and update on re-import."
        },
        "title": {
          "type": "string",
          "minLength": 1,
          "description": "The name of the finding."
        },
        "severity": { "$ref": "#/$defs/severity" },
        "category": {
          "type": "string",
          "description": "Grouping label, for example \"Injection\". Readers default to \"general\"."
        },
        "testType": {
          "type": "string",
          "description": "Engagement type, for example \"web-application\" or \"external-infrastructure\"."
        },
        "description": {
          "type": "string",
          "description": "What the issue is. Interpreted per the document textFormat."
        },
        "impact": {
          "type": "string",
          "description": "What the issue lets an attacker do."
        },
        "recommendation": {
          "type": "string",
          "description": "How to fix it."
        },
        "technicalDetails": {
          "type": "string",
          "description": "Requests, responses, payloads, evidence."
        },
        "cvssScore": {
          "type": "number",
          "minimum": 0,
          "maximum": 10,
          "description": "Convenience score. Derivable from cvssVector, which is the source of truth."
        },
        "cvssVector": {
          "type": "string",
          "description": "The full CVSS vector, for example \"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H\". The source of truth for CVSS."
        },
        "cvssVersion": {
          "type": "string",
          "description": "For example \"3.1\" or \"4.0\". Also inferable from the cvssVector prefix.",
          "examples": ["3.1", "4.0"]
        },
        "cweId": {
          "type": "string",
          "deprecated": true,
          "description": "OPF 1.0 singular CWE. Superseded by cweIds. Readers still accept it and treat it as a single-item list."
        },
        "cweIds": {
          "type": "array",
          "items": { "type": "string" },
          "description": "One or more CWE identifiers, for example [\"CWE-89\"].",
          "examples": [["CWE-89"]]
        },
        "cveIds": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Associated CVE identifiers.",
          "examples": [["CVE-2021-44228"]]
        },
        "owaspCategory": {
          "type": "string",
          "description": "For example \"A03:2021 Injection\"."
        },
        "mitreTechniques": {
          "type": "array",
          "items": { "type": "string" },
          "description": "MITRE ATT&CK technique identifiers, for example [\"T1190\"]."
        },
        "affectedAssets": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Hosts, URLs or assets. Present on engagement instances, omitted on library templates."
        },
        "references": {
          "type": "array",
          "items": { "$ref": "#/$defs/reference" }
        },
        "stepsToReproduce": {
          "type": "array",
          "items": { "type": "string" },
          "description": "Ordered steps, one per array item. Read in array order."
        },
        "customFields": {
          "type": "object",
          "additionalProperties": true,
          "description": "Any keys. Preserved across a round trip."
        }
      }
    }
  }
}
