SpecificationVersion 1.1

Open Pentest Format

A portable JSON format for pentest finding libraries. One file, human-readable, with the structure of a finding kept intact. Free to implement.

OPF is a plain JSON document that carries a set of pentest findings between tools without flattening them into prose. It describes findings, not whole reports, so it stays small enough to implement in an afternoon. A finding can be a library template or an instance from an engagement: instance-only fields like affectedAssetsare optional. Cairn imports and exports it. This page is the reference; the reasoning behind it is in the announcement post.

Document

A UTF-8 JSON object. The suggested file extension is .opf.json.

FieldTypeRequiredNotes
opfVersionstringrequiredA 1.x version string. Current is "1.1".
textFormatenumoptionalHow to read text fields: html, markdown or text. Defaults to html.
metadataobjectoptionalsource, exportedAt, description, findingCount (all optional).
findingsFinding[]requiredThe findings. May be empty.

a complete document

{
  "opfVersion": "1.1",
  "textFormat": "html",
  "metadata": {
    "source": "Cairn",
    "exportedAt": "2026-07-31T12:00:00Z",
    "description": "Web application finding templates",
    "findingCount": 1
  },
  "findings": [
    {
      "id": "global-cwe-89-1",
      "title": "SQL injection in the report search endpoint",
      "severity": "critical",
      "category": "Injection",
      "testType": "web-application",
      "description": "The q parameter is concatenated into the query without parameterisation.",
      "impact": "An attacker can read or modify arbitrary rows in the application database.",
      "recommendation": "Use parameterised queries. Reject input that does not match the expected shape.",
      "technicalDetails": "GET /api/reports?q=' OR 1=1 returns every report.",
      "cvssScore": 9.8,
      "cvssVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
      "cvssVersion": "3.1",
      "cweIds": ["CWE-89"],
      "cveIds": [],
      "owaspCategory": "A03:2021 Injection",
      "mitreTechniques": ["T1190"],
      "affectedAssets": ["app.example.com"],
      "references": [
        { "title": "CWE-89", "url": "https://cwe.mitre.org/data/definitions/89.html", "type": "cwe" }
      ],
      "stepsToReproduce": [
        "Send ' OR 1=1 in the q parameter",
        "Observe the full result set returned"
      ],
      "customFields": { "internalId": "WEB-014" }
    }
  ]
}

Finding

Only title and severity are required. Everything else is optional, so a scanner export and a hand-authored template both validate. The point of the format is that the optional fields are there when you have them, kept as fields rather than folded into one blob.

FieldTypeRequiredNotes
idstringoptionalStable identifier for the finding, for dedup and update on re-import.
titlestringrequiredNon-empty. The name of the finding.
severityenumrequiredOne of critical, high, medium, low, informational.
categorystringoptionalGrouping label, e.g. Injection. Defaults to "general".
testTypestringoptionalEngagement type, e.g. web-application, external-infrastructure.
descriptionstringoptionalWhat the issue is. Defaults to "".
impactstringoptionalWhat it lets an attacker do.
recommendationstringoptionalHow to fix it.
technicalDetailsstringoptionalRequest/response, payloads, evidence.
cvssScorenumberoptional0.0 to 10.0. Derived from the vector; a convenience.
cvssVectorstringoptionalThe full vector, e.g. CVSS:3.1/AV:N/... The source of truth.
cvssVersionstringoptional3.1 or 4.0. Also inferable from the vector prefix.
cweIdsstring[]optionalOne or more CWEs, e.g. ["CWE-89"]. Supersedes the 1.0 singular cweId, which is still read.
cveIdsstring[]optionalAssociated CVE identifiers.
owaspCategorystringoptionale.g. A03:2021 Injection.
mitreTechniquesstring[]optionalATT&CK technique IDs, e.g. T1190.
affectedAssetsstring[]optionalHosts, URLs or assets. Present on instances, omitted on library templates.
referencesReference[]optionalTyped links. See below.
stepsToReproducestring[]optionalOrdered steps, one per array item.
customFieldsobjectoptionalAny keys. Preserved across a round trip.

Reference

A reference is a typed link. Only the URL is required.

FieldTypeRequiredNotes
urlstringrequiredThe link.
titlestringoptionalHuman label for the link.
typeenumoptionalOne of cve, cwe, owasp, mitre, vendor, article, other.

Rules

  • Severity is lowercase, one of the five values above.
  • CVSS lives in the vector. cvssVector is the source of truth; cvssScore and cvssVersion are conveniences a reader can derive from it.
  • CWE is a list. Use cweIds. A 1.0 file’s singular cweId is still read and treated as a single-item list.
  • Text has a declared format. The document-level textFormat (default html) says how to read description, impact and the other prose fields. Consumers should sanitise on import. Cairn does.
  • Unknown keys are preserved. A reader that meets a field it does not know keeps it rather than dropping it. Cairn collects them into customFields and writes them back out on export.
  • Order matters where it is a list. stepsToReproduce is read in array order.

Versioning

The current version is "1.1". A reader accepts any 1.xdocument: minor versions only add fields, never rename or remove them, and unknown fields are preserved, so a 1.1 file stays readable by a 1.0 parser and the reverse. A 1.0 file is still valid. If a breaking change is ever needed, the major version moves.

Validate

A minimal JSON Schema (draft-07) for the parts that are strict:

{
  "$schema": "https://json-schema.org/draft-07/schema#",
  "title": "Open Pentest Format",
  "type": "object",
  "required": ["opfVersion", "findings"],
  "properties": {
    "opfVersion": { "type": "string", "pattern": "^1\\.\\d+$" },
    "textFormat": { "enum": ["html", "markdown", "text"] },
    "metadata": { "type": "object" },
    "findings": {
      "type": "array",
      "items": {
        "type": "object",
        "required": ["title", "severity"],
        "properties": {
          "id": { "type": "string" },
          "title": { "type": "string", "minLength": 1 },
          "severity": {
            "enum": ["critical", "high", "medium", "low", "informational"]
          },
          "cvssScore": { "type": "number", "minimum": 0, "maximum": 10 },
          "cvssVector": { "type": "string" },
          "cweIds": { "type": "array", "items": { "type": "string" } }
        }
      }
    }
  }
}

License

OPF is open to implement, with no attribution required and no license to sign. Cairn maintains version 1.0 and will keep the format small on purpose. If you build pentest tooling and want a field added, or you have adopted OPF and want to be listed, get in touch through the contact page.

Try it against a real library
Export your finding library from Cairn as OPF, open the file, and read it. Import it back, or into the next tool that speaks the format.
See the Report module
← Why we built OPF