{"_id":"@cairnsec/opf-tools","_rev":"5-5d4097b7fef2d13b4a54ff18a0ec1e2a","name":"@cairnsec/opf-tools","dist-tags":{"latest":"0.2.1"},"versions":{"0.1.0":{"name":"@cairnsec/opf-tools","version":"0.1.0","keywords":["opf","open-pentest-format","sarif","defectdojo","gitlab","pentest","security","converter","vulnerability"],"author":{"name":"Cairn Security"},"license":"MIT","_id":"@cairnsec/opf-tools@0.1.0","maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"homepage":"https://cairnsecurity.com/opf","bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"bin":{"opf":"dist/cli.js"},"dist":{"shasum":"7ce69cadea6c05b210ca92d1d37fa6ec40f6a7d0","tarball":"https://registry.npmjs.org/@cairnsec/opf-tools/-/opf-tools-0.1.0.tgz","fileCount":23,"integrity":"sha512-i1mRi83kcbG39sfmDJEb25jy6Rj90iUzsZd48JoecKBj3M5Motx0LySkN1tYZKyfCqanQBRgshIof7OOIRy3xQ==","signatures":[{"sig":"MEUCIQDdKkfrNW0HLztzjZYQty2c6oYHAPOPNRgFA7t1T1MIGwIgTToNc/mWHFOHqrVFcrhvV2iG1BLpPlxn2YL48aA1fGY=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":57186},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ad3ec6d80a2de9f1c049d68241254299512ef1fe","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"cairnsec","email":"paul@cairnsecurity.com"},"repository":{"url":"git+https://github.com/cairnsec/opf-tools.git","type":"git"},"_npmVersion":"11.18.0","description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, GitLab, Markdown, HTML and CSV.","directories":{},"sideEffects":false,"_nodeVersion":"26.6.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^1.3.0","typescript":"^5.4.0","@types/node":"^20.14.0"},"_npmOperationalInternal":{"tmp":"tmp/opf-tools_0.1.0_1785973316350_0.030411662762414027","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@cairnsec/opf-tools","version":"0.1.1","keywords":["opf","open-pentest-format","sarif","defectdojo","gitlab","pentest","security","converter","vulnerability"],"author":{"name":"Cairn Security"},"license":"MIT","_id":"@cairnsec/opf-tools@0.1.1","maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"homepage":"https://cairnsecurity.com/opf","bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"bin":{"opf":"dist/cli.js"},"dist":{"shasum":"e7c75c2cdf8dc73a15eaa03f4b8dbfc8f339bd8c","tarball":"https://registry.npmjs.org/@cairnsec/opf-tools/-/opf-tools-0.1.1.tgz","fileCount":23,"integrity":"sha512-VKQHX8k7K3gphTFLuWVQOsa9eQY55YGjm17e5lHKfLPJj6s307xlIapyzxeMkPHkn7bXZvYmFXmvP6hSlL3TAQ==","signatures":[{"sig":"MEYCIQCdz/iNsHRPknLzfXyzTswLlEUFcxsYRo1dsXlh12tOFwIhAOw1cWU0+TiXB5Kpx09qj76ep2pr4Y2HhukUY861E0oh","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cairnsec%2fopf-tools@0.1.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":57242},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"ae3e5d9ace42ab91cc22f7d18727ca151e0dcc74","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89c0d8b7-276e-4e99-b30e-655f4cbe3792"}},"repository":{"url":"git+https://github.com/cairnsec/opf-tools.git","type":"git"},"_npmVersion":"12.0.2","description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, GitLab, Markdown, HTML and CSV.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.6","typescript":"^5.4.0","@types/node":"^20.14.0"},"_npmOperationalInternal":{"tmp":"tmp/opf-tools_0.1.1_1785976546946_0.36525538393782586","host":"s3://npm-registry-packages-npm-production"}},"0.1.2":{"name":"@cairnsec/opf-tools","version":"0.1.2","keywords":["opf","open-pentest-format","sarif","defectdojo","gitlab","pentest","security","converter","vulnerability"],"author":{"name":"Cairn Security"},"license":"MIT","_id":"@cairnsec/opf-tools@0.1.2","maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"homepage":"https://cairnsecurity.com/opf","bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"bin":{"opf":"dist/cli.js"},"dist":{"shasum":"0ff0e0b3b36638b4274c029e5b663cd179089d75","tarball":"https://registry.npmjs.org/@cairnsec/opf-tools/-/opf-tools-0.1.2.tgz","fileCount":23,"integrity":"sha512-ARZ811TTd2vHeHPpwjKrJPIqId+3Lb9FWdtJjsksAmYHDXmBMFWkfvLzj+P4eRUN1a7Fm67nv6oi27Inrx0XVA==","signatures":[{"sig":"MEYCIQDWoiv5qlE74VCEV4XJHUATeXHOhOukyP3H9GDlo1dCSQIhAP3JRqF0lOuV12m7Y5IVjD/3VekXyQQsdUywNu5/O/7D","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cairnsec%2fopf-tools@0.1.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":57692},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"738cb58994a017473c34e02a120f8fecc054d31b","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89c0d8b7-276e-4e99-b30e-655f4cbe3792"}},"repository":{"url":"git+https://github.com/cairnsec/opf-tools.git","type":"git"},"_npmVersion":"12.0.2","description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, GitLab, Markdown, HTML and CSV.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.6","typescript":"^5.4.0","@types/node":"^20.14.0"},"_npmOperationalInternal":{"tmp":"tmp/opf-tools_0.1.2_1785978893328_0.46178479591233357","host":"s3://npm-registry-packages-npm-production"}},"0.2.0":{"name":"@cairnsec/opf-tools","version":"0.2.0","keywords":["opf","open-pentest-format","sarif","defectdojo","jira","github","gitlab","linear","azure-devops","servicenow","issue-tracker","pentest","security","converter","vulnerability"],"author":{"name":"Cairn Security"},"license":"MIT","_id":"@cairnsec/opf-tools@0.2.0","maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"homepage":"https://cairnsecurity.com/opf","bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"bin":{"opf":"dist/cli.js"},"dist":{"shasum":"2de41d565321e5d3fe258dce73a99c29b4cbb590","tarball":"https://registry.npmjs.org/@cairnsec/opf-tools/-/opf-tools-0.2.0.tgz","fileCount":37,"integrity":"sha512-/0H82RioglVyxvcSwhu8NUIUn3CiXdkNN99vdPiwQZYPrbABltKbwZVRZxUJL44a/DTtXG1tQ65nFqkdEqVvZw==","signatures":[{"sig":"MEYCIQClAKReFhYxNI+ycQwSAH4L0IS8SOaojHjWxFqjTP+/wQIhANW4ezlwoAqXEEAbWUPXd2QUKvSrhAF1cHgEA7EnXsmJ","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cairnsec%2fopf-tools@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":111067},"main":"dist/index.js","type":"module","types":"dist/index.d.ts","engines":{"node":">=18"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"gitHead":"c7b0b502d6d8f1fa4b3f842e9cb4b2637bc99854","scripts":{"test":"vitest run","build":"tsc -p tsconfig.json","prepublishOnly":"npm run build && npm test"},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89c0d8b7-276e-4e99-b30e-655f4cbe3792"}},"repository":{"url":"git+https://github.com/cairnsec/opf-tools.git","type":"git"},"_npmVersion":"12.0.2","description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, Jira, GitHub, GitLab, Linear, Azure Boards, ServiceNow, Markdown, HTML and CSV.","directories":{},"sideEffects":false,"_nodeVersion":"22.23.1","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"vitest":"^3.2.6","typescript":"^5.4.0","@types/node":"^20.14.0"},"_npmOperationalInternal":{"tmp":"tmp/opf-tools_0.2.0_1786369650027_0.42758108237128933","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@cairnsec/opf-tools","version":"0.2.1","description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, Jira, GitHub, GitLab, Linear, Azure Boards, ServiceNow, Markdown, HTML and CSV.","type":"module","license":"MIT","author":{"name":"Cairn Security"},"keywords":["opf","open-pentest-format","sarif","defectdojo","jira","github","gitlab","linear","azure-devops","servicenow","issue-tracker","pentest","security","converter","vulnerability"],"homepage":"https://cairnsecurity.com/opf","repository":{"type":"git","url":"git+https://github.com/cairnsec/opf-tools.git"},"bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"bin":{"opf":"dist/cli.js"},"main":"dist/index.js","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"}},"sideEffects":false,"publishConfig":{"access":"public"},"scripts":{"build":"tsc -p tsconfig.json","test":"vitest run","prepublishOnly":"npm run build && npm test"},"engines":{"node":">=18"},"devDependencies":{"@types/node":"^20.14.0","typescript":"^5.4.0","vitest":"^3.2.6"},"gitHead":"d2117942e3940cb901aa0ca8809ce2421e4934c6","_id":"@cairnsec/opf-tools@0.2.1","_nodeVersion":"22.23.1","_npmVersion":"12.0.2","dist":{"integrity":"sha512-AoQr03RWPuNljuHmQFCY4zPGgrgNwWhK9Knbgb8MH7mMd1f/dKl1CzJ0u73YZASbgmjF2kCos25cDJpHyvqOtw==","shasum":"d08e8764a817c99370b3f2f5d445ebfdf9ee5d4c","tarball":"https://registry.npmjs.org/@cairnsec/opf-tools/-/opf-tools-0.2.1.tgz","fileCount":37,"unpackedSize":127179,"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@cairnsec%2fopf-tools@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQCNXxVjBPGLYsXJaf2uqgr88Rwylj1UX67m1tJG9CmZFQIgEah7AeqQSD50hanywKjvfKLlSlNHEjE27Q+z5sg5Jhk="}]},"_npmUser":{"name":"GitHub Actions","email":"npm-oidc-no-reply@github.com","trustedPublisher":{"id":"github","oidcConfigId":"oidc:89c0d8b7-276e-4e99-b30e-655f4cbe3792"}},"directories":{},"maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/opf-tools_0.2.1_1786483854662_0.4990052396391358"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-05T23:41:56.165Z","modified":"2026-08-11T21:30:55.262Z","0.1.0":"2026-08-05T23:41:56.475Z","0.1.1":"2026-08-06T00:35:47.093Z","0.1.2":"2026-08-06T01:14:53.476Z","0.2.0":"2026-08-10T13:47:30.201Z","0.2.1":"2026-08-11T21:30:54.826Z"},"bugs":{"url":"https://github.com/cairnsec/opf-tools/issues"},"author":{"name":"Cairn Security"},"license":"MIT","homepage":"https://cairnsecurity.com/opf","keywords":["opf","open-pentest-format","sarif","defectdojo","jira","github","gitlab","linear","azure-devops","servicenow","issue-tracker","pentest","security","converter","vulnerability"],"repository":{"type":"git","url":"git+https://github.com/cairnsec/opf-tools.git"},"description":"Converters between the Open Pentest Format (OPF) and SARIF, DefectDojo, Jira, GitHub, GitLab, Linear, Azure Boards, ServiceNow, Markdown, HTML and CSV.","maintainers":[{"name":"cairnsec","email":"paul@cairnsecurity.com"}],"readme":"# opf-tools\n\nConverters between the [Open Pentest Format](https://github.com/cairnsec/opf) (OPF) and the formats security teams already use.\n\nOPF is a small, portable JSON format for pentest findings and finding libraries. On its own a portable format is only half the story, it has to reach the tools people actually run. `opf-tools` is that bridge: it turns an OPF library into SARIF, DefectDojo, GitLab, Jira, issue-tracker CSV, Markdown, HTML or CSV, and reads SARIF, CSV and DefectDojo findings back into OPF.\n\nZero runtime dependencies. Library + `opf` CLI. MIT.\n\n## Converters\n\n| Command | From → To | Use it for |\n|---------|-----------|------------|\n| `sarif` | OPF → SARIF 2.1.0 | GitHub code scanning, Azure DevOps, VS Code SARIF Viewer |\n| `from-sarif` | SARIF → OPF | Bring scanner output into an OPF library |\n| `defectdojo` | OPF → DefectDojo Generic Findings Import | Import a library into DefectDojo, no custom parser |\n| `from-defectdojo` | DefectDojo findings JSON → OPF | Turn a saved DefectDojo export into OPF |\n| `defectdojo-pull` | live DefectDojo → OPF | Pull findings from the DefectDojo API into OPF |\n| `gitlab` | OPF → GitLab SAST report | Surface findings on GitLab MRs / security dashboard |\n| `jira-csv` | OPF → Jira-importable CSV | Bulk-create issues via Jira's CSV import wizard |\n| `jira-rest` | OPF → Jira bulk-create JSON | Review the `/issue/bulk` payload before sending it |\n| `jira-push` | OPF → live Jira issues | Create issues directly via the Jira REST API |\n| `github-rest` / `github-push` | OPF → GitHub issues (JSON / live) | Review payloads, or create via the GitHub REST API |\n| `gitlab-issues-rest` / `gitlab-issues-push` | OPF → GitLab issues (JSON / live) | Create issues via the GitLab API |\n| `linear-rest` / `linear-push` | OPF → Linear issues (JSON / live) | Create issues via the Linear GraphQL API |\n| `azure-rest` / `azure-push` | OPF → Azure Boards work items (JSON / live) | Create work items via the Azure DevOps API |\n| `servicenow-rest` / `servicenow-push` | OPF → ServiceNow records (JSON / live) | Insert records via the ServiceNow Table API |\n| `github-csv` | OPF → GitHub Issues CSV | Import into GitHub issues |\n| `linear-csv` | OPF → Linear CSV | Import into Linear |\n| `azure-csv` | OPF → Azure Boards CSV | Import into Azure DevOps Boards |\n| `issues-csv` | OPF → generic issue CSV | Any tracker with a CSV importer |\n| `markdown` | OPF → Markdown | A readable, diffable finding document |\n| `html` | OPF → standalone HTML | A self-contained page to open or share |\n| `csv` | OPF → CSV | Spreadsheets, triage, bulk edit |\n| `from-csv` | CSV → OPF | Turn a spreadsheet back into a library |\n| `validate` | OPF → pass/fail | Check a document in CI or a pre-commit hook |\n\n## Install\n\n```bash\nnpm install @cairnsec/opf-tools\n```\n\n## CLI\n\n```bash\nopf <command> [input] [output]\n\n# examples\nopf sarif library.opf.json library.sarif.json\nopf defectdojo library.opf.json | curl -F 'file=@-' ...        # into DefectDojo\nopf jira-csv library.opf.json > jira-import.csv                # Jira CSV import wizard\nopf markdown library.opf.json > FINDINGS.md\nopf validate library.opf.json                                  # non-zero exit if invalid\ncat scan.sarif.json | opf from-sarif > scan.opf.json           # scanner → OPF\n\n# pull findings out of a live DefectDojo (every scanner it aggregates becomes OPF)\nexport DEFECTDOJO_URL=https://dojo.example.com DEFECTDOJO_TOKEN=...\nopf defectdojo-pull > library.opf.json\n\n# create issues in a live Jira instance\nexport JIRA_BASE_URL=https://acme.atlassian.net JIRA_EMAIL=you@acme.com JIRA_TOKEN=... JIRA_PROJECT=SEC\nopf jira-push library.opf.json\n\n# create issues in a live GitHub repository\nexport GITHUB_OWNER=cairnsec GITHUB_REPO=findings GITHUB_TOKEN=ghp_...\nopf github-push library.opf.json\n```\n\n### Issue-tracker coverage\n\nEvery finding maps to a neutral `IssueDraft`; each tracker is a thin adapter over it. All push commands print a JSON preview (`*-rest`) so you can review what will be created before sending it.\n\n> New in 0.2.0: the REST/GraphQL push adapters are built against each API's documented contract and unit-tested with mocked transports, but not yet exercised against live instances. Preview with the `*-rest` command and start on a scratch project. Please report any real-world payload mismatches.\n\n| Tracker | CSV import | REST push | Push env vars |\n|---------|:----------:|:---------:|---------------|\n| Jira | `jira-csv` | `jira-push` | `JIRA_BASE_URL`, `JIRA_EMAIL`, `JIRA_TOKEN`, `JIRA_PROJECT` |\n| GitHub | `github-csv` | `github-push` | `GITHUB_OWNER`, `GITHUB_REPO`, `GITHUB_TOKEN` |\n| GitLab | — | `gitlab-issues-push` | `GITLAB_PROJECT`, `GITLAB_TOKEN`, `GITLAB_URL`* |\n| Linear | `linear-csv` | `linear-push` | `LINEAR_API_KEY`, `LINEAR_TEAM_ID` |\n| Azure Boards | `azure-csv` | `azure-push` | `AZURE_ORG`, `AZURE_PROJECT`, `AZURE_TOKEN` |\n| ServiceNow | — | `servicenow-push` | `SN_INSTANCE`, `SN_USER`, `SN_PASSWORD`, `SN_TABLE`* |\n| generic | `issues-csv` | — | — |\n\n\\* optional (self-managed GitLab base URL; ServiceNow table defaults to `incident`).\n\nReads a file argument or stdin; writes a file argument or stdout.\n\n## Library\n\n```ts\nimport {\n  opfToSarif,\n  opfToDefectDojo,\n  defectDojoToOpf,\n  fetchDefectDojoOpf,\n  opfToIssues,\n  opfToIssuesCsv,\n  opfToJiraRest,\n  pushToJira,\n  pushToGitHub,\n  pushToGitLabIssues,\n  pushToLinear,\n  pushToAzure,\n  pushToServiceNow,\n  sarifToOpf,\n  validateOpf,\n} from '@cairnsec/opf-tools'\n\nconst sarif = opfToSarif(opfDocument)\nconst dd = opfToDefectDojo(opfDocument)\nconst opf = sarifToOpf(sarifLog)\nconst { valid, errors } = validateOpf(opfDocument)\n\n// DefectDojo, both directions\nconst fromExport = defectDojoToOpf(defectDojoFindingsJson)     // a saved export → OPF\nconst fromApi = await fetchDefectDojoOpf({ baseUrl, apiToken, filters: { engagement: 7 } })\n\n// issue trackers\nconst drafts = opfToIssues(opfDocument)                        // tracker-neutral tickets\nconst csv = opfToIssuesCsv(opfDocument, 'linear')              // any tracker profile\nconst payload = opfToJiraRest(opfDocument, { projectKey: 'SEC' })\nawait pushToJira(opfDocument, { baseUrl, email, apiToken, projectKey: 'SEC' })\nawait pushToGitHub(opfDocument, { owner: 'cairnsec', repo: 'findings', token })\nawait pushToGitLabIssues(opfDocument, { projectId: 'group/app', token })\nawait pushToLinear(opfDocument, { apiKey, teamId })\nawait pushToAzure(opfDocument, { org: 'acme', project: 'Security', token })\nawait pushToServiceNow(opfDocument, { instanceUrl, user, password })\n```\n\nEvery `push*` takes an injectable `fetch` for testing, and each has a pure counterpart (`opfTo*`) that builds the request without sending it.\n\n## Mapping notes\n\n- **Severity → tool severity.** OPF `critical/high/medium/low/informational` maps to each target's scale (SARIF `error/warning/note`, DefectDojo/GitLab `Critical…Info`).\n- **CVSS carries through.** The score becomes SARIF `security-severity` (what GitHub ranks alerts on), DefectDojo `cvssv3_score`, and so on. When a finding has no CVSS score, one is synthesised from its severity so it still buckets correctly.\n- **Identifiers travel.** CWE, CVE, OWASP and MITRE ATT&CK map to each format's native identifier or tag (`external/cwe/cwe-89` for SARIF, integer `cwe` for DefectDojo, typed `identifiers[]` for GitLab).\n- **Text is normalised.** OPF text is often HTML (`textFormat: \"html\"`); it is stripped to plain text for text fields and lightly formatted for Markdown/HTML.\n- **Round trips keep structure.** OPF → CSV → OPF and OPF → SARIF → OPF preserve the fields those formats can represent, so a finding survives as a finding, not a flattened paragraph.\n- **DefectDojo works both ways.** `opfToDefectDojo` imports a library in; `defectDojoToOpf` / `fetchDefectDojoOpf` read findings back out (the REST-API finding shape, not the generic-import shape), so everything DefectDojo aggregates becomes reachable as OPF. The read direction defaults to active, non-false-positive, non-duplicate findings. A few DefectDojo fields have no clean OPF home and are handled best-effort: flat `tags` are split into `owaspCategory`/`mitreTechniques` with the rest under `customFields.tags`, and `endpoints` (returned as ids by the API) are carried through only when already URL-like.\n- **One issue model, many trackers.** Each finding maps once to a neutral `IssueDraft` (summary, priority, labels, a Markdown body, and structured CVSS/CWE/CVE fields). Severity becomes each tracker's priority scale, and every tracker is then a thin adapter over that draft: a CSV column profile (Jira, GitHub, Linear, Azure Boards, generic) or a REST/GraphQL payload builder plus a live pusher (Jira, GitHub, GitLab, Linear, Azure Boards, ServiceNow). Adding another API-backed tracker is a small file modelled on the existing adapters.\n\n## Why\n\nA finding library is an asset a team builds over years. It should not be trapped in one vendor's database. OPF makes it portable; `opf-tools` makes that portability real by connecting OPF to the tools findings actually flow through.\n\n## See also\n\n- [**cairnsec/opf**](https://github.com/cairnsec/opf) — the OPF specification, JSON Schema and examples.\n\n## License\n\nMIT.\n","readmeFilename":"README.md"}