If you’ve ever tried to integrate an iOS security tool into CI, you’ve hit the same problem: the tool emits findings as a PDF, or a custom JSON shape, or a CSV nobody can parse. Your CI pipeline can’t surface those findings as code-scanning alerts. Your engineers don’t see them until they read the PDF post-release, by which point the relevant commit is buried under twenty merges.
SARIF, Static Analysis Results Interchange Format, solves this. It’s an OASIS standard (current version: 2.1.0), supported natively by GitHub Code Scanning, GitLab Security Dashboard, Azure DevOps, and a dozen other CI platforms. Emit SARIF, and your findings appear inline in pull-request reviews where they can be acted on.
SentinelDen Studio emits SARIF 2.1.0 natively. This post walks the format, the iOS-specific decisions baked into our emitter, and how to wire the output into GitHub Code Scanning so findings appear as PR comments without any custom glue.
SARIF in one paragraph
A SARIF document is a JSON file containing one or more runs, where each run describes a single analysis tool execution. A run contains a tool description, a list of rules (the kinds of findings the tool can produce), and a list of results (specific findings from this run). Each result references a rule by ID, points at a location in source code or a binary, has a level (error / warning / note), and a message describing the finding.
That’s the whole format. The standard adds a long tail of optional structure, code flows, fix suggestions, web requests, attachments, but for a static-analysis tool emitting findings against an iOS binary, the four required pieces (tool, rules, results, locations) cover 95% of what you ship.
The iOS-specific mapping decisions
Adapting SARIF to iOS binaries involves choices the standard doesn’t dictate. Here’s how Studio resolves them, and why:
tool.name = “SentinelDen Studio”. Simple. Set once.
tool.version = the Studio build version. Bump on every release so SARIF consumers can see which Studio version produced a given finding.
rules = the rule pack identifiers, one per checkable pattern. Each rule has a stable id (e.g., MASVS-CRYPTO-1.0, SD-INFOPLIST-001), a short name, a one-line shortDescription, and a markdown-formatted fullDescription with remediation guidance. Rules are persistent, the same rule emits the same id across Studio versions, so trend analysis works.
results[].location is where iOS-specific design gets interesting. SARIF was designed for source-code analyzers. We’re analyzing a compiled binary. The standard offers three location shapes:
- artifactLocation, a file URI, with optional region (line/column). Best for findings tied to source files (entitlement plist, Info.plist, embedded resources).
- logicalLocation, a symbolic identifier (function name, class name). Best for findings tied to symbols in the binary that have no source-file mapping.
- physicalLocation with
binaryRegion, byte offsets into the binary. Best for findings tied to specific bytes in the Mach-O (cryptid flag, segment offsets, header anomalies).
Studio emits the most specific location type available for each finding. A Mach-O cryptid issue gets a physicalLocation with the byte offset of the encryption_info_command. An Info.plist issue gets an artifactLocation pointing at Info.plist with a region. A hard-coded API key found in a resource gets an artifactLocation with file + region.
results[].level maps from Studio’s internal severity scale (info / low / medium / high / critical) to SARIF’s note / warning / error. The mapping is:
- info, low →
note - medium →
warning - high, critical →
error
GitHub Code Scanning surfaces error-level results as failing checks (blocking the PR), warning as a non-blocking annotation, and note as informational. The mapping is deliberately conservative, Studio doesn’t fail your PR on a “medium” by default.
results[].fingerprints is the underrated field that makes SARIF actually useful in CI. A fingerprint is a stable hash of (rule, location, surrounding context) that lets consumers deduplicate findings across runs. Without fingerprints, every run emits a new “alert” for the same long-standing issue, and your Code Scanning dashboard becomes useless within weeks. Studio computes a partialFingerprints per result so GitHub can track “this is the same finding as last week” cleanly.
A real Studio SARIF result, annotated
{
"ruleId": "MASVS-CRYPTO-2.0",
"ruleIndex": 14,
"level": "error",
"message": {
"text": "Hard-coded AES key detected at byte offset 0x18A4 in __TEXT segment. This key is shared across all installs; an attacker who extracts it from one binary owns the decryption key for every user.",
"markdown": "Hard-coded AES key detected at byte offset `0x18A4` in `__TEXT` segment. This key is shared across all installs; an attacker who extracts it from one binary owns the decryption key for every user.\n\nSee MASVS-CRYPTO-2: \"Cryptographic key material is protected from disclosure.\""
},
"locations": [
{
"physicalLocation": {
"artifactLocation": { "uri": "Payload/MyApp.app/MyApp" },
"region": { "byteOffset": 6308, "byteLength": 32 }
}
}
],
"partialFingerprints": {
"binaryRegion/v1": "sha256:7fa3...c2d1"
},
"properties": {
"tags": ["security", "masvs-crypto", "hardcoded-secret"],
"security-severity": "8.5"
}
}
A few notes:
- The
markdownvariant is what GitHub renders in the PR comment. Markdown lets us link to MASVS and embed code snippets. partialFingerprints.binaryRegion/v1lets GitHub track this specific finding across runs even if the offset shifts slightly (the algorithm hashes the bytes themselves, not just the offset).properties.security-severityis a CVSS-style numeric score. GitHub uses it to rank findings in the security tab when multiple are open.properties.tagsadds searchable taxonomy. Engineers can filter the security tab to just MASVS findings, or just crypto findings.
Wiring SARIF into GitHub Code Scanning
The CI integration is two GitHub Actions steps:
# .github/workflows/ios-security-audit.yml
name: iOS Security Audit
on:
pull_request:
paths: ['**/*.swift', '**/*.m', '**/*.entitlements', '**/Info.plist', 'Package.*']
jobs:
studio-audit:
runs-on: macos-14
permissions:
security-events: write # required for upload-sarif
actions: read
contents: read
steps:
- uses: actions/checkout@v4
- name: Build .ipa
run: xcodebuild ... archive
- name: SentinelDen Studio CLI audit
run: |
/Applications/Sentinel\ Studio.app/Contents/MacOS/sentinel-cli \
audit MyApp.ipa \
--rules masvs-l2,sd-default \
--output-format sarif \
--output findings.sarif
- name: Upload SARIF to Code Scanning
uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: findings.sarif
category: sentinel-studio
The category: sentinel-studio parameter lets GitHub keep Studio’s findings separated from other security tools (CodeQL, Snyk, etc.) in the same repo. Without it, multiple tools’ findings get merged in confusing ways.
What this enables in practice
PR-inline review. A new finding shows up as a comment on the offending line/file/binary region in the pull request. The reviewer sees it before merging. No PDF round-trip.
Trend tracking. GitHub’s security tab shows finding-counts over time. You can see whether your security posture is improving release-over-release without a custom dashboard.
Fail-the-build gating. Add a --fail-on=error flag to the Studio CLI invocation, and the GitHub Action exits non-zero when a critical or high finding is introduced. The PR is blocked until the finding is fixed or explicitly dismissed.
Dismissal with audit. GitHub Code Scanning lets reviewers dismiss findings as “false positive” or “won’t fix” with a justification. The justification is recorded in the security audit log. This is materially better than tracking exclusions in a YAML file nobody reads.
Cross-tool aggregation. If you also run CodeQL, Snyk, or another SARIF-emitting tool, all findings appear in the same security tab. Engineers see “security findings on this PR” as one unified surface.
Other SARIF consumers worth knowing
GitHub is the most common, but not the only:
- GitLab Security Dashboard consumes SARIF directly. Same field requirements.
- Azure DevOps has a SARIF SAST decorator.
- Sonar consumes SARIF for some quality-gate integrations.
- DefectDojo and similar findings-management platforms have SARIF importers.
- JetBrains Qodana emits SARIF; if you also run Qodana, the dashboards compose.
The format itself is plain JSON, so any tool that can parse JSON can do something useful with Studio’s output. The standardization just means you don’t have to write that JSON parser yourself.
What ships in SentinelDen Studio
The Studio CLI exposes SARIF as a first-class output:
sentinel-cli audit MyApp.ipa --output-format sarif --output findings.sarif
sentinel-cli audit MyApp.ipa --output-format sarif --fail-on error
sentinel-cli audit MyApp.ipa --output-format sarif --rules masvs-l2
Plus three other output formats for different consumers:
--output-format pdffor the typeset audit report (signed PDF).--output-format markdownfor engineering follow-up tickets.--output-format jsonfor the raw machine-readable shape (a superset of SARIF; useful if you want fields SARIF can’t represent).
The CLI is bundled with the macOS app at /Applications/SentinelDen Studio.app/Contents/MacOS/sentinel-cli, so once you have Studio installed your CI runner has the CLI. See SentinelDen Studio for the product page and the integration reference for the full CLI surface.
The CI integration takes maybe 20 minutes the first time. After that, every PR gets an inline security audit and your team stops finding out about issues in the post-release scrum.

Comments
Nothing yet. Say the first thing.
Sign in to join the conversation.