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.
| Field | Type | Required | Notes |
|---|---|---|---|
opfVersion | string | required | A 1.x version string. Current is "1.1". |
textFormat | enum | optional | How to read text fields: html, markdown or text. Defaults to html. |
metadata | object | optional | source, exportedAt, description, findingCount (all optional). |
findings | Finding[] | required | The 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.
| Field | Type | Required | Notes |
|---|---|---|---|
id | string | optional | Stable identifier for the finding, for dedup and update on re-import. |
title | string | required | Non-empty. The name of the finding. |
severity | enum | required | One of critical, high, medium, low, informational. |
category | string | optional | Grouping label, e.g. Injection. Defaults to "general". |
testType | string | optional | Engagement type, e.g. web-application, external-infrastructure. |
description | string | optional | What the issue is. Defaults to "". |
impact | string | optional | What it lets an attacker do. |
recommendation | string | optional | How to fix it. |
technicalDetails | string | optional | Request/response, payloads, evidence. |
cvssScore | number | optional | 0.0 to 10.0. Derived from the vector; a convenience. |
cvssVector | string | optional | The full vector, e.g. CVSS:3.1/AV:N/... The source of truth. |
cvssVersion | string | optional | 3.1 or 4.0. Also inferable from the vector prefix. |
cweIds | string[] | optional | One or more CWEs, e.g. ["CWE-89"]. Supersedes the 1.0 singular cweId, which is still read. |
cveIds | string[] | optional | Associated CVE identifiers. |
owaspCategory | string | optional | e.g. A03:2021 Injection. |
mitreTechniques | string[] | optional | ATT&CK technique IDs, e.g. T1190. |
affectedAssets | string[] | optional | Hosts, URLs or assets. Present on instances, omitted on library templates. |
references | Reference[] | optional | Typed links. See below. |
stepsToReproduce | string[] | optional | Ordered steps, one per array item. |
customFields | object | optional | Any keys. Preserved across a round trip. |
Reference
A reference is a typed link. Only the URL is required.
| Field | Type | Required | Notes |
|---|---|---|---|
url | string | required | The link. |
title | string | optional | Human label for the link. |
type | enum | optional | One of cve, cwe, owasp, mitre, vendor, article, other. |
Rules
- Severity is lowercase, one of the five values above.
- CVSS lives in the vector.
cvssVectoris the source of truth;cvssScoreandcvssVersionare conveniences a reader can derive from it. - CWE is a list. Use
cweIds. A 1.0 file’s singularcweIdis still read and treated as a single-item list. - Text has a declared format. The document-level
textFormat(defaulthtml) 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
customFieldsand writes them back out on export. - Order matters where it is a list.
stepsToReproduceis 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.