SARIF integration guide

Export Stella Ops Scanner results as SARIF 2.1.0 and feed them into GitHub, GitLab, Azure DevOps, or Gitea CI. This guide is for pipeline engineers wiring scanner output into a code-scanning surface; it covers both the Smart-Diff SARIF surface (risk changes between two scans) and the full-scan finding SARIF surface (one scan). For how the underlying priority scores are computed and tuned, see scoring configuration.

Provenance: SPRINT_3500_0004_0001 · SDIFF-BIN-032.

Reconciliation note (verify against source): This guide was reconciled against src/Scanner in the main repo. The authoritative SARIF generator for Smart-Diff is src/Scanner/__Libraries/StellaOps.Scanner.SmartDiff/Output/SarifOutputGenerator.cs (rule catalog, properties, schema URI, tool name) and the HTTP surface is src/Scanner/StellaOps.Scanner.WebService/Endpoints/SmartDiffEndpoints.cs, ExportEndpoints.cs, and GitHubCodeScanningEndpoints.cs. Earlier drafts of this guide described an aspirational rule set (still visible in the stale fixture src/Scanner/__Tests/StellaOps.Scanner.SmartDiff.Tests/Fixtures/sarif-golden.v1.json) that does not match the shipping code; the tables below reflect what the code emits today.

Overview

StellaOps Scanner supports SARIF (Static Analysis Results Interchange Format) 2.1.0 output for integration with CI/CD platforms including GitHub, GitLab, and Azure DevOps. Two distinct SARIF surfaces exist today:

This guide focuses on the Smart-Diff surface (the /smart-diff/.../sarif endpoint and the stellaops scan sarif CLI command). The full-scan finding rule catalog is summarized in Full-Scan Finding Rules.

Supported Platforms

PlatformIntegration MethodNative Support
GitHub ActionsCode Scanning API (POST /api/v1/scans/{scanId}/github/upload-sarif) plus upload-sarif actionYes
GitLab CISAST report artifact (SARIF file upload)Yes
Azure DevOpsSARIF Viewer extension (artifact upload)Yes
Gitea ActionsWorkflow generator target (CiPlatform.GiteaActions)Yes
OtherFile upload (SARIF 2.1.0 file artifact)Yes

Reconciliation note: Earlier drafts listed a “Jenkins / SARIF Plugin” row. No Jenkins workflow generator or integration exists in source — src/Tools/StellaOps.Tools.WorkflowGenerator/CiPlatform.cs defines only GitHubActions, GitLabCi, AzureDevOps, and GiteaActions. The Jenkins row was removed; Gitea Actions (which is generated) was added. Native GitHub support is backed by the Code Scanning upload endpoint in GitHubCodeScanningEndpoints.cs. GitLab/Azure “native” support means the SARIF file is consumable by those platforms’ built-in viewers via artifact upload — there is no dedicated GitLab/Azure push integration in source.

Quarantine note (PLAN_20260704 A5 / D3): SARIF file export (the surfaces this guide documents) is fully available. The Stella CLI push verb stella github upload-sarif(and the rest of the stella github group) is quarantined — not wired in this release; it exits with a clean not available in this release error. To publish SARIF to GitHub Code Scanning, emit the file with the scanner and upload it with GitHub’s own github/codeql-action/upload-sarif action. See PLAN_20260704 “Explicitly deferred / cut”.

Direct-upload boundary (verified 2026-07-19): the Scanner WebService route and REST client are implemented and fail loud when unconfigured. The scan-scoped status/alert routes reject absent or cross-tenant scans before calling GitHub, and the accepted upload response uses named-route link generation so its status URL and Location preserve the configured API base path. Production reachability rows currently reach the SARIF bridge without severity, affected-version, or source-location data, however, and repository credentials/default coordinates are host-level rather than bound to the tenant and scan. Use the file-export plus platform action path when those richer or tenant-specific bindings are required.

Quick Start

API Endpoint

The Smart-Diff SARIF endpoint is served under the configured API base path (default /api/v1):

# Get Smart-Diff SARIF output for a scan
curl -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/sarif+json" \
  "https://scanner.example.com/api/v1/smart-diff/scans/{scanId}/sarif"

# With pretty printing
curl -H "Authorization: Bearer $TOKEN" \
  -H "Accept: application/sarif+json" \
  "https://scanner.example.com/api/v1/smart-diff/scans/{scanId}/sarif?pretty=true"

The response content type is application/sarif+json. The endpoint requires the scanner.scans.read authorization policy. A full-scan finding SARIF document for a single scan is available at GET /api/v1/scans/{scanId}/exports/sarif (same scanner.scans.read policy).

Reconciliation note: The only query parameter the Smart-Diff SARIF handler reads is pretty (HandleGetScanSarifAsync). The --include-hardening / --include-reachability / --min-severity options are CLI-side flags translated by the CLI’s backend client into includeHardening, includeReachability, and minSeverity query parameters; the in-tree Smart-Diff handler always enables hardening, VEX, and reachability inclusion and does not currently honor minSeverity.

CLI Usage

# Export an existing scan's results as SARIF 2.1.0
stellaops scan sarif --scan-id <scanId> --output results.sarif

# Pretty-print to stdout, including hardening and reachability sections
stellaops scan sarif --scan-id <scanId> --pretty \
  --include-hardening --include-reachability

# Limit to a minimum SARIF level
stellaops scan sarif --scan-id <scanId> --min-severity warning

CLI options (from CommandFactory / HandleScanSarifExportAsync):

OptionDescription
--scan-id (required)Scan identifier to export.
--output, -oOutput file path (defaults to stdout).
--prettyPretty-print JSON output.
--include-hardeningInclude binary hardening flags in SARIF output.
--include-reachabilityInclude reachability analysis in SARIF output.
--min-severityMinimum SARIF level to include (none, note, warning, error).

Reconciliation note: Earlier drafts showed stellaops scan image:tag --output-format sarif and a top-level stellaops smart-diff --output-format sarif command. Neither exists. The shipping command is the scan sarif subcommand above (no --output-format, --tier, or --min-priority flags on this command). The CLI backend client requests api/scans/{scanId}/sarif (see BackendOperationsClient.GetScanSarifAsync).

SARIF Rule Definitions (Smart-Diff)

StellaOps.Scanner.SmartDiff emits exactly four rules. IDs, names, and default levels below are taken verbatim from SarifOutputGenerator.CreateRules():

Rule IDNameDefault LevelDescription
SDIFF001MaterialRiskChangewarningA vulnerability finding underwent a material risk state change between scans. (Level is note when the change direction is decreased.)
SDIFF002HardeningRegressionerrorA binary lost security hardening flags compared to the previous scan.
SDIFF003VexCandidateGeneratednoteA VEX not_affected candidate was generated because vulnerable APIs are no longer present.
SDIFF004ReachabilityFlipwarningThe reachability of a vulnerability flipped between scans.

Reconciliation note: Earlier drafts listed SDIFF001 ReachabilityChange, SDIFF002 VexStatusFlip, SDIFF003 HardeningRegression, SDIFF004 IntelligenceSignal. That mapping matches the stale sarif-golden.v1.json fixture, not the code. The unit tests in SarifOutputGeneratorTests.cs assert the IDs/names/levels above (e.g. SDIFF001 → MaterialRiskChange, SDIFF002 → Error, SDIFF003 → Note). There is no EPSS/KEV “IntelligenceSignal” rule in the Smart-Diff generator. The tool driver also advertises a CWE taxonomy reference and per-rule helpUri values of the form https://stellaops.dev/docs/scanner/smart-diff/rules/SDIFF00x.

GitHub Actions Integration

StellaOps offers two GitHub paths: (1) export the SARIF file and use the standard github/codeql-action/upload-sarif action, or (2) push directly to the Code Scanning API via the scanner’s POST /api/v1/scans/{scanId}/github/upload-sarif endpoint.

name: Security Scan
on: [push, pull_request]

jobs:
  security:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Export StellaOps SARIF
        run: |
          stellaops scan sarif \
            --scan-id "${{ env.STELLAOPS_SCAN_ID }}" \
            --output results.sarif

      - name: Upload SARIF
        uses: github/codeql-action/upload-sarif@v3
        with:
          sarif_file: results.sarif
          category: stellaops

Reconciliation note: The previous example called stellaops scan <repo> --output-format sarif --output results.sarif. The CLI exports an already-completed scan by --scan-id; it does not scan a repository slug inline. Substitute your own scan-id source (e.g. captured from a prior scan step).

GitLab CI Integration

security_scan:
  stage: test
  image: stellaops/cli:latest
  script:
    - stellaops scan sarif --scan-id "$STELLAOPS_SCAN_ID" --output gl-sast-report.sarif
  artifacts:
    reports:
      sast: gl-sast-report.sarif

Azure DevOps Integration

trigger:
  - main

pool:
  vmImage: 'ubuntu-latest'

steps:
  - task: Bash@3
    displayName: 'Export StellaOps SARIF'
    inputs:
      targetType: 'inline'
      script: |
        stellaops scan sarif --scan-id "$(stellaopsScanId)" --output $(Build.ArtifactStagingDirectory)/results.sarif

  - task: PublishBuildArtifacts@1
    inputs:
      pathToPublish: '$(Build.ArtifactStagingDirectory)/results.sarif'
      artifactName: 'security-results'

SARIF Schema Details

Result Levels

Smart-Diff levels are assigned per-rule (see the rule table above). The full-scan finding mapper (SarifRuleRegistry.GetLevel) maps severity to level and elevates KEV / runtime-reachable findings to error:

SARIF LevelStellaOps Severity (full-scan mapper)Notes
errorCritical, High; or any KEV / runtime-reachable findingRequires immediate attention
warningMedium (and the default fallback)Should be reviewed
noteLowFor awareness
noneSuppressed / informational

Reconciliation note: The SarifLevel enum (SarifModels.cs) is none, note, warning, error — there is no separate “Info” level. Severity-to-level mapping lives in SarifRuleRegistry.GetLevel for full-scan findings; Smart-Diff results use the fixed per-rule levels above.

Result Kinds

Reconciliation note — NOT IMPLEMENTED: The Smart-Diff SarifResult record (SarifModels.cs) does not emit a kind property (fail / pass / notApplicable / informational); it carries ruleId, level, message, locations, fingerprints, partialFingerprints, and properties. The earlier “Result Kinds” table described SARIF spec values that StellaOps does not populate today and has been removed to avoid implying behavior the code does not have.

Location Information

What the Smart-Diff generator actually emits for locations:

Reconciliation note: Earlier drafts claimed every result includes a component-PURL/function logical location and an OCI/SBOM URI. The generator emits logicalLocations for none of the four rules, and does not attach function names. PURL and vuln id are carried in fingerprints, not in a logical location. Component digests are surfaced at the run level (see properties below), not per result.

Example SARIF Output (Smart-Diff)

The following reflects the fields the generator actually produces (camelCase, nulls omitted; tool name and schema URI are constants in SarifOutputGenerator):

{
  "version": "2.1.0",
  "$schema": "https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json",
  "runs": [
    {
      "tool": {
        "driver": {
          "name": "StellaOps.Scanner.SmartDiff",
          "version": "1.0.0",
          "informationUri": "https://stellaops.dev/docs/scanner/smart-diff",
          "rules": [
            {
              "id": "SDIFF001",
              "name": "MaterialRiskChange",
              "shortDescription": { "text": "Material risk change detected" },
              "fullDescription": {
                "text": "A vulnerability finding has undergone a material risk state change between scans."
              },
              "defaultConfiguration": { "level": "warning" },
              "helpUri": "https://stellaops.dev/docs/scanner/smart-diff/rules/SDIFF001"
            }
          ],
          "supportedTaxonomies": [
            { "name": "CWE", "guid": "25F72D7E-8A92-459D-AD67-64853F788765" }
          ]
        }
      },
      "results": [
        {
          "ruleId": "SDIFF001",
          "level": "warning",
          "message": {
            "text": "Material risk change for CVE-2024-1234 in pkg:npm/lodash@4.17.20: New vulnerability introduced"
          },
          "fingerprints": {
            "purl": "pkg:npm/lodash@4.17.20",
            "vulnId": "CVE-2024-1234"
          }
        }
      ],
      "invocations": [
        { "executionSuccessful": true, "startTimeUtc": "2025-01-15T10:30:00+00:00" }
      ],
      "properties": {
        "stellaops.diff.base.digest": "sha256:789xyz012abc",
        "stellaops.diff.target.digest": "sha256:abc123def456"
      }
    }
  ]
}

Reconciliation note: Earlier drafts showed tool.driver.name = "StellaOps Scanner", a main/sarif-2.1/schema/... schema URI, per-result properties such as vulnerability / tier / direction, and logical locations with function names. None of those are emitted by the current generator. The constants above are verbatim from SarifOutputGenerator (ToolName, ToolInfoUri, SchemaUri). The fixed message templates are "Material risk change for {vulnId} in {purl}: {reason}" (SDIFF001), "Hardening flag '{flag}' was {enabled/disabled} but is now {enabled/disabled} in {binaryPath}" (SDIFF002), "VEX not_affected candidate for {vulnId} in {purl}: {justification}" (SDIFF003), and "Vulnerability {vulnId} in {purl} became reachable|became unreachable" (SDIFF004).

Filtering Results

Reconciliation note — NOT IMPLEMENTED: Earlier drafts documented --tier tainted_sink|executed and --min-priority 0.7 flags on a scan/smart-diff SARIF command. Those flags do not exist on the SARIF export command. The closest in-source filters are:

  • stellaops scan sarif --min-severity <none|note|warning|error> (CLI-side level filter).
  • The Smart-Diff changes endpoint GET /api/v1/smart-diff/scans/{scanId}/changes?minPriority=<0..1> filters material changes by PriorityScore (this returns JSON, not SARIF).
  • --tier exists on other commands (e.g. stellaops witness list --tier confirmed|likely|present|unreachable and reachability commands), not on SARIF export.

StellaOps Property Keys (Smart-Diff)

Sprint: SPRINT_4400_0001_0001 (Signed Delta Verdict Attestation), SPRINT_3500_0004_0001

SARIF properties bag extensions actually populated by SarifOutputGenerator (CreateRunProperties / CreateMaterialChangeResult). Keys use dot notation (stellaops.<x>), not slash notation.

Run-Level Properties

Property KeyTypeDescription
stellaops.diff.base.digeststringBase image digest for the diff (omitted when absent).
stellaops.diff.target.digeststringTarget image digest for the diff (omitted when absent).
stellaops.deltaVerdictRefstringLegacy delta-verdict reference (backwards compatibility).
stellaops.attestationobjectSigned delta-verdict attestation: digest, predicateType, and optional ociReference, rekorLogId, signatureKeyId.

When none of the above are present, the run properties bag is omitted entirely.

Result-Level Properties

Property KeyTypeDescription
deltaVerdictRefstringPresent on SDIFF001 (MaterialRiskChange) results only, when a delta-verdict reference was supplied.

Reconciliation note — NOT IMPLEMENTED: Earlier drafts documented a rich stellaops/* (slash-form) result/run property set: stellaops/nodeHash, stellaops/pathHash, stellaops/topKNodeHashes, stellaops/evidenceUri, stellaops/attestationUri, stellaops/rekorUri, stellaops/witnessId, stellaops/witnessHash, stellaops/scanId, stellaops/graphHash, stellaops/sbomDigest, stellaops/feedSnapshot. None of these keys are emitted by any SARIF generator in source (a repo-wide search returns zero matches). The concepts (node hashes, path hashes, witness IDs, graph hashes) exist elsewhere in the platform but are not exported as SARIF properties. Where attestation/diff metadata is exported, it uses the dot-form run-level keys listed above. (Note: the full-scan finding mapper uses detector/* slash-form keys — e.g. detector/fingerprint, detector/confidence — for language/secret detector signals; those are a different surface, documented under full-scan findings.)

Witness IDs

Witness identifiers in StellaOps are content-addressed and prefixed, e.g. sup:sha256:<64-hex> for suppression witnesses (see SuppressionWitnessBuilder) and wit:sha256:<hex> for reachability witnesses (CLI witness show accepts wit:sha256:...). They are not UUIDs and are not surfaced as a SARIF stellaops/witnessId property.


Full-Scan Finding Rules

For a single scan (not a diff), GET /api/v1/scans/{scanId}/exports/sarif returns findings mapped by StellaOps.Scanner.Sarif.SarifRuleRegistry. Rule IDs follow the STELLA-<CATEGORY>-NNN scheme:

PrefixCategoryExample rules
STELLA-VULN-VulnerabilitiesSTELLA-VULN-001 (Critical) … 004 (Low); 005 RuntimeReachable; 006 StaticReachable
STELLA-SEC-Secrets001 HardcodedSecret; 002 PrivateKeyExposure; 003 CredentialPattern
STELLA-SC-Supply chain001 UnsignedPackage; 002 UnknownProvenance; 003 TyposquatCandidate; 004 DeprecatedPackage
STELLA-BIN-Binary hardening001 MissingRelro; 002 MissingStackCanary; 003 MissingPie; 004 MissingFortifySource
STELLA-LIC-License001 LicenseCompliance
STELLA-CFG-Configuration001 ConfigurationIssue
STELLA-LANG-Language detector001 LanguageDetectorSignal

Per-rule helpUri values point at https://stellaops.io/docs/findings/.... KEV and runtime-reachable findings are elevated to error regardless of base severity.


Troubleshooting

SARIF Validation Errors

If your CI platform rejects the SARIF output:

  1. Check required fields:
    • $schema and version ("2.1.0") are present (always emitted).
    • Each result has ruleId, level, and message.text.
  2. Confirm the schema URI matches the SARIF 2.1.0 schema your platform expects. StellaOps emits https://raw.githubusercontent.com/oasis-tcs/sarif-spec/master/Schemata/sarif-schema-2.1.0.json.

Reconciliation note — NOT IMPLEMENTED: Earlier drafts referenced a stellaops validate-sarif <file> CLI command. No such command exists in the CLI. SARIF schema conformance is exercised in tests (SarifOutputGeneratorTests.GeneratedSarif_PassesSchemaValidation, SarifSchemaValidationTests), not via a shipped validation subcommand. Use a standard SARIF validator if you need to validate output in CI.

Empty Results

If SARIF contains no results: