Files
dev-workflows/skills/dependency-scanning/docs/sarif-output.md
Evandro Fonseca Junior bb3fe21766 fix(skills): vendor the support files the vendored skills point at
The vendored SKILL.md files were byte-identical to upstream, but the
playbooks, templates and schemas they reference were left behind in the
original repos, so a loading skill pointed at a file that does not exist
anywhere in this repository.

Vendored 1:1 into each skill directory (so they survive
`cp -r skills/* ~/.hermes/skills/`):

- plays/ for api-security-review, code-review-security, sca-audit,
  secrets-scan, web-security-review (OWASP/secure-agent-playbook)
- templates/finding.md for the four OWASP skills that cite it
- schemas/finding.schema.json for cve-triage, patch-prioritization,
  dependency-scanning (UnitOneAI/SecuritySkills)
- docs/sarif-output.md, docs/fixer-policy.md and
  docs/normalized-json-output.md for dependency-scanning

Path fixes, the only edit to vendored text:
- the three UnitOneAI skills referenced ../../../schemas/ and ../../../docs/
  which assumed upstream's skills/<category>/<skill>/ depth; this repo is
  flat, so the links now resolve inside the skill directory
- safedeps told the agent to run <skill_dir>/../../check_deps.py while the
  script is vendored next to SKILL.md; corrected to <skill_dir>/check_deps.py
  and documented the script's exit codes (1 = finding, not a crash)
- NOTICE.md records the new files, the path edits, and the remaining known
  gaps (hermaguard's kensei-pipeline references, the ASVS dataset citations,
  and hermaguard being behind upstream)

Verified: every backticked path in every SKILL.md now resolves;
check_deps.py runs from the installed location and returns live OSV.dev
data ([email protected] -> 2 advisories); hermaguard's 46 tests still pass.
2026-09-14 07:40:21 -03:00

7.8 KiB

SARIF Output Mapping

SecuritySkills may emit SARIF-compatible JSON for tools that ingest Static Analysis Results Interchange Format (SARIF) 2.1.0. SARIF output is an export view of the normalized finding envelope documented in docs/normalized-json-output.md; it does not replace the normalized JSON contract or change the required finding fields.

Top-Level Shape

Use SARIF version 2.1.0 with the standard schema URI:

{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "SecuritySkills",
          "semanticVersion": "1.0.0",
          "rules": []
        }
      },
      "results": []
    }
  ]
}

Use one run per normalized envelope. run.tool.driver.name should identify SecuritySkills or the executing agent. run.tool.driver.semanticVersion should use the skill version when a single skill produced the run.

Mapping

Map normalized fields to SARIF as follows:

Normalized field SARIF field
run.id, run.timestamp, run.target, run.source_ref runs[].properties.securityskills.run
skill.name, skill.version, skill.path, skill.frameworks runs[].tool.driver.name, runs[].tool.driver.semanticVersion, runs[].properties.securityskills.skill
finding.id results[].ruleId and results[].properties.securityskills.finding_id
finding.fingerprint results[].partialFingerprints.securityskillsFingerprint
finding.title tool.driver.rules[].shortDescription.text
finding.description tool.driver.rules[].fullDescription.text and results[].message.text
finding.severity results[].level
finding.status results[].properties.securityskills.status
finding.cwe tool.driver.rules[].relationships when supported, and properties.securityskills.cwe
finding.framework_refs tool.driver.rules[].properties.securityskills.framework_refs
finding.evidence results[].locations plus properties.securityskills.evidence
finding.references tool.driver.rules[].helpUri or tool.driver.rules[].properties.securityskills.references
finding.remediations tool.driver.rules[].help.text and properties.securityskills.remediations, including each remediation's test_strategy

Severity

SARIF level has fewer values than the normalized contract. Use this mapping:

Normalized severity SARIF level
critical error
high error
medium warning
low note
info note

Keep the original severity in results[].properties.securityskills.severity so downstream systems can preserve the distinction between critical and high.

Locations

For source, configuration, policy, or scan-result evidence with file locations, populate results[].locations[].physicalLocation:

{
  "physicalLocation": {
    "artifactLocation": {
      "uri": "routes/users.js"
    },
    "region": {
      "startLine": 42
    }
  },
  "message": {
    "text": "DELETE /users/:id checks authentication but not administrator role."
  }
}

If the normalized evidence location does not contain a parseable file path and line number, still emit a SARIF result and store the original location under properties.securityskills.evidence. For cloud resources, identities, network assets, logs, or documents, use the best stable asset identifier as artifactLocation.uri only when it is meaningful to the SARIF consumer.

Rules

Create one tool.driver.rules[] entry per distinct ruleId. A rule should include:

  • id: the normalized finding.id, or a stable detector ID when multiple findings share the same detection rule.
  • shortDescription.text: the finding title or detector title.
  • fullDescription.text: the finding description.
  • help.text: remediation guidance and test strategy summary.
  • properties.securityskills: CWE IDs, framework references, normalized severity, and any remediation metadata that does not fit native SARIF fields.

Do not invent CWE, framework, or control identifiers for SARIF. Reuse only the values already present in the normalized finding.

Minimal Example

{
  "$schema": "https://json.schemastore.org/sarif-2.1.0.json",
  "version": "2.1.0",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "api-security",
          "semanticVersion": "1.0.0",
          "rules": [
            {
              "id": "API-SEC-001",
              "shortDescription": {
                "text": "Administrative endpoint lacks authorization check"
              },
              "fullDescription": {
                "text": "The delete-user route accepts authenticated requests without verifying administrative privileges."
              },
              "help": {
                "text": "Require an administrator role check before invoking deleteUser. Validate with integration tests for non-admin and admin delete flows."
              },
              "properties": {
                "securityskills": {
                  "severity": "high",
                  "cwe": ["CWE-862"],
                  "framework_refs": [
                    {
                      "framework": "OWASP-API-Top-10-2023",
                      "control": "API5:2023",
                      "name": "Broken Function Level Authorization"
                    }
                  ]
                }
              }
            }
          ]
        }
      },
      "results": [
        {
          "ruleId": "API-SEC-001",
          "level": "error",
          "message": {
            "text": "Administrative endpoint lacks authorization check"
          },
          "locations": [
            {
              "physicalLocation": {
                "artifactLocation": {
                  "uri": "routes/users.js"
                },
                "region": {
                  "startLine": 42
                }
              },
              "message": {
                "text": "DELETE /users/:id checks authentication but not administrator role."
              }
            }
          ],
          "partialFingerprints": {
            "securityskillsFingerprint": "api-security:users-delete:missing-admin-auth"
          },
          "properties": {
            "securityskills": {
              "finding_id": "API-SEC-001",
              "severity": "high",
              "status": "open",
              "remediations": [
                {
                  "guidance": "Require an administrator role check before invoking deleteUser.",
                  "confidence": "high",
                  "blast_radius": "User administration routes only.",
                  "behavior_change_risk": "medium",
                  "test_strategy": {
                    "summary": "Proves non-admin users cannot delete accounts while admins still can.",
                    "recommended_tests": [
                      {
                        "name": "Reject non-admin delete",
                        "type": "integration",
                        "purpose": "Confirms the authorization bypass no longer succeeds.",
                        "command": "npm test -- users-authz.test.js",
                        "expected_result": "Non-admin DELETE returns 403 and admin DELETE succeeds."
                      }
                    ]
                  }
                }
              ]
            }
          }
        }
      ],
      "properties": {
        "securityskills": {
          "run": {
            "id": "run-001",
            "timestamp": "2026-06-16T12:00:00Z",
            "target": "payments-api",
            "source_ref": "abc1234"
          },
          "skill": {
            "name": "api-security",
            "version": "1.0.0",
            "path": "skills/appsec/api-security/SKILL.md",
            "frameworks": ["OWASP-API-Top-10-2023", "CWE"]
          }
        }
      }
    }
  ]
}