A file format for the finding library you spent years building
Your write-ups outlast any one tool. The format they live in should too. So we published one.
Every pentest team quietly builds the same asset over years: a library of findings. The careful write-up for SQL injection, the one for a wide-open S3 bucket, the Kerberos misconfiguration you meet on nearly every internal. The wording, the remediation that actually lands, the CVSS math, the references. That library is the difference between a report that takes a day and one that takes a week.
In most tools it is also a hostage. It lives in someone else’s database, behind someone else’s API, in a schema you never see. The day you want to move, or run two tools side by side, or just keep a copy you control, you find out how portable it really is. The usual answer is a CSV with three columns and none of the structure that made the write-ups worth keeping.
We did not want to ship another one of those. So Cairn reads and writes the Open Pentest Format, OPF: a plain JSON file that carries a finding library with the parts that matter left intact.
opf-minimal.opf.json
{
"opfVersion": "1.1",
"textFormat": "html",
"metadata": {
"source": "Cairn",
"exportedAt": "2026-07-31T12:00:00Z",
"findingCount": 1
},
"findings": [
{
"title": "S3 bucket allows public read",
"severity": "high",
"description": "The bucket policy grants s3:GetObject to *."
}
]
}An OPF document is a version, a small metadata block, and an array of findings. A finding is roughly what you would expect, with a bit more. Title and severity, yes, but also the split between description, impact and recommendation that separates a real finding from a scanner line. The technical detail and the steps to reproduce. The CVSS vector, not just the number it rolls up to. And the identifiers a good finding hangs off: CWE, CVE, OWASP category, MITRE technique. References are typed, so a link knows whether it points at a CVE, an OWASP page or a vendor advisory.
a single finding
{
"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.",
"cvssScore": 9.8,
"cvssVector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
"cweIds": ["CWE-89"],
"owaspCategory": "A03:2021 Injection",
"mitreTechniques": ["T1190"],
"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"
]
}Two decisions did most of the work
Findings keep their structure. A lot of “portable” formats flatten a finding into one description field and call it done. That throws away the thing you spent years tuning: where impact ends and remediation begins, which CWE this maps to, how you rate it. OPF keeps those as separate fields, so a finding survives the round trip and arrives in the next tool as a finding, not a paragraph.
Unknown fields survive. The format has a version and a defined set of keys, but a reader that meets a key it does not recognise keeps it instead of dropping it. In Cairn, anything outside the known set lands in customFields and comes back out on export. A newer file does not lose data in an older reader, and your own extensions ride along untouched. Forward compatibility is a choice you make once and are grateful for later.
A report is a deliverable with a hundred opinions baked in. A finding library is data. Data is the part you want to be able to pick up and carry.
There is a deliberate limit. OPF describes findings, not a whole report. No branding, no cover page, no client record, no engagement metadata. That is on purpose. Keeping the format small is what keeps it something another tool can actually implement, and what keeps it about the data rather than about any one vendor’s idea of a deliverable.
The open part is not just a label
Cairn imports OPF and exports it, from the same Finding Library screen, with no support ticket and no professional-services engagement to get your own data back out. The schema is published: the OPF specification is a couple of pages of field definitions on this site. If you write pentest tooling, you can read and emit OPF this afternoon. If you do not, you can still open the file in a text editor and read it, which is the honest test of a format meant to outlast the tool that wrote it.
We would rather compete on the product than on how hard it is to leave. A finding library you can carry out the door is table stakes, and we are happy to set them.