
Skill
publish-to-ghas
publish SARIF files to GitHub Advanced Security
Description
Uploads a finalized SARIF file to GitHub Advanced Security (GHAS) code scanning using the Sarif.Multitool publish-to-ghas verb, deriving the GitHub target, commit, and ref from the run's version-control provenance.
SKILL.md
Publish SARIF to GHAS (GitHub code scanning)
Context
GitHub Advanced Security (GHAS) ingests SARIF through the GitHub code-scanning SARIF API
(POST /repos/{owner}/{repo}/code-scanning/sarifs).
The publish-to-ghas verb
uploads a finalized SARIF file to that endpoint: it derives the GitHub owner/repo, the commit_sha,
and the ref from the run's version-control provenance, gzip-compresses and base64-encodes the body
into the JSON payload, and posts it under a caller-supplied bearer token. The verb is the companion of
publish-to-ghazdo for Azure DevOps targets, and shares its
log-level refusal of an unpublishable (--no-repo) log.
Publish a SARIF only after it is finalized — its first run must carry a GitHub repositoryUri,
revisionId, and branch under versionControlProvenance (see emit-sarif
and the emit-finalize verb). The target owner/repo, the commit_sha, and the ref are read from
that provenance, so an unfinalized file cannot be published.
Why finalize matters for GHAS specifically
emit-finalize stamps the metadata GHAS needs to treat a finding as a security alert:
security-severityon each AI rule (curated per-CWE prior, or a neutral medium default for uncurated content). GitHub buckets the alert critical/high/medium/low from this value.tagson each AI rule —["security", "external/cwe/cwe-<n>"]for a CWE rule,["security"]for aNOVEL-rule. GitHub ignoressecurity-severityunless thesecuritytag is present, so an unfinalized file uploads as plain code-scanning results with no security severity.
These tags are emitted only for GitHub-hosted runs; an Azure DevOps-hosted SARIF will not carry them
(publish that one with publish-to-ghazdo instead).
Security model
This is the security-critical part of the skill. Follow it exactly.
- The token never appears on the command line. Place the token in an environment variable and name
that variable with
--token-env-var(defaultGHAS_TOKEN, parallelingpublish-to-ghazdo'sGHAZDO_TOKEN). The verb reads the value from the environment; it is never an argument, and it is never printed — not in dry-run output, not in error messages, and it is redacted from any server response body or exception text. - Always sent as
Authorization: Bearer. A classic or fine-grained GitHub personal access token carryingsecurity_eventswrite (repoalso grants it for private repositories) is sent as a bearer token. Less scope yields HTTP 403. - GitHub hosts only. The target must resolve to
github.comor a<slug>.ghe.comdata-residency host fromversionControlProvenance; the API host isapi.github.comorapi.<slug>.ghe.comrespectively. Adev.azure.com(GHAzDO) target, a legacy host, and credential-bearing repository URLs are rejected. A GitHub SSH/scp clone URL is normalized to its https identity.
Prerequisites
Sarif.Multitool≥ 5.1.0. Recommended invocation:dotnet dnx Sarif.Multitool --yes -- publish-to-ghas ...(zero-install, requires .NET 10+). Fall back todotnet tool install --global Sarif.Multitool, then invoke assarif publish-to-ghas ....- A finalized SARIF whose first run carries a GitHub
repositoryUri,revisionId, and arefs/heads/...branch. - A GitHub personal access token with
security_eventswrite, stored in an environment variable.
Detection
Inputs
| Parameter | Required | Description |
|---|---|---|
{{SARIF_FILE}} | Yes | Path to the finalized .sarif file to upload |
{{TOKEN_ENV_VAR}} | No | Name of the environment variable holding the token. Default GHAS_TOKEN |
Step 1 — Set the token in the environment
Set the variable in the current session only. Do not write the token to a file, a script, or shell history. Use your platform's mechanism for assigning a value without echoing it.
# PowerShell — paste the token when prompted; it is not echoed to history.
$env:GHAS_TOKEN = (Read-Host -AsSecureString | ForEach-Object { [System.Net.NetworkCredential]::new('', $_).Password })
# bash — read without echo.
read -rs GHAS_TOKEN && export GHAS_TOKEN
Step 2 — Validate pre-flight (no network)
GHAS ingestion-blocking defects are detectable offline. A clean validation under the GHAS rule kinds is the offline equivalent of upload acceptance — run it before the round-trip.
dotnet dnx Sarif.Multitool --yes -- validate "{{SARIF_FILE}}" --rule-kind "Sarif;AI;Ghas"
Resolve any error-level findings before publishing. In particular, GHAS will not surface a
security severity unless each AI rule carries the security tag — re-finalize (emit-finalize) the
SARIF rather than hand-editing tags.
Step 3 — Dry run (no network)
Always dry-run first. This resolves the GitHub target, the ref, and the commit_sha from the
SARIF's provenance and prints the request shape without contacting the server and without printing
the token.
dotnet dnx Sarif.Multitool --yes -- publish-to-ghas "{{SARIF_FILE}}" --dry-run
Confirm the reported owner/repo, ref, and commit_sha. If the target is wrong, the SARIF was not
finalized against the intended GitHub repository — re-finalize it; do not hand-edit these values.
Step 4 — Publish
dotnet dnx Sarif.Multitool --yes -- publish-to-ghas "{{SARIF_FILE}}"
To name a different environment variable:
dotnet dnx Sarif.Multitool --yes -- publish-to-ghas "{{SARIF_FILE}}" --token-env-var "{{TOKEN_ENV_VAR}}"
The verb posts to api.github.com (or api.<slug>.ghe.com). Exit code 0 means the server accepted
the upload (HTTP 2xx — GitHub answers 202 Accepted); a non-zero exit code with error: publish failed with HTTP <code> means it did not. The verb prints the analysis response body (with the token
redacted) — its url is the analysis status endpoint, which you can poll with gh api until
"processing_status": "complete".
If dotnet dnx is not available: install the global tool and use the sarif command name:
dotnet tool install --global Sarif.Multitool
sarif publish-to-ghas "{{SARIF_FILE}}"
Edge Cases
- No provenance — The run carries no
versionControlProvenance[0].repositoryUri. The verb fails closed. Finalize the SARIF (emit-finalize) before publishing. - Unpublishable (repo-less) log — Any run carries
properties.unpublishable = true, stamped byemit-finalize --no-repofor a scan outside version control. The verb refuses the whole file up front (publishing ingests every run): a non-version-controlled scan has no repository or commit to anchor alerts to. Publish a log whose runs were all finalized against a checked-out repository (without--no-repo); split a merged log first if only some runs are unpublishable. - Non-GitHub target — The repository is
dev.azure.comor a legacy host. The verb rejects it; usepublish-to-ghazdo. - Missing
revisionIdorbranch— GHAS anchors an analysis to a commit and a fully-qualified ref (refs/heads/...). The verb fails closed if either is absent; re-finalize with both present. commit_shanot in the repo — GitHub rejects an analysis whose commit it cannot find. Publish against a SARIF finalized at a commit that has been pushed.- HTTP 403 — The token lacks
security_events(orrepofor a private repo), or GHAS / code scanning is not enabled on the repository. - HTTP 404 —
owner/repodoes not resolve, or the token cannot see it. - No security severity on alerts — The alerts appear but without critical/high/medium/low. The
rules are missing the
securitytag; re-finalize soemit-finalizestamps it (Step 2 catches this offline).
Validation
After running this skill:
- The token never appeared in any printed command, log line, or error message.
- The GHAS-ruleset validation (
--rule-kind "Sarif;AI;Ghas") was clean before the live publish. - The dry-run
owner/repo+ref+commit_shamatched the intended GitHub repository. - A successful publish returned exit code
0and the analysis reachedprocessing_status: complete. - The resulting alerts carry a security severity (critical/high/medium/low) — confirming the
securitytag andsecurity-severitysurvived finalize.
Escalation
- Multitool not available — Neither
dotnet dnxnor a global install succeeded. Report the gap; the SARIF cannot be published from this environment. - Repeated 4xx / non-
completestatus — After confirming target, ref, commit, and token scope, capture the analysiserrorsbody the verb prints (it contains no secret) and escalate to the repository's Advanced Security administrator.
More from Microsoft
View publisherrushstack-best-practices
manage Rush monorepos with best practices
rushstack
Apr 6EngineeringLocal DevelopmentMicrosoftProject Management +1azure-ai-agents-persistent-dotnet
build AI agents with Azure .NET SDK
skills
Jul 3.NETAgentsAzureLLMazure-ai-anomalydetector-java
build anomaly detection applications with Java
skills
May 13AnalyticsAzureData AnalysisJava +2azure-ai-contentsafety-java
build content moderation applications with Azure AI
skills
Jul 7AI InfrastructureAzureJavaSecurityazure-ai-contentsafety-py
detect harmful content with Azure AI Content Safety
skills
Jul 18AzureComplianceLLMMicrosoft +2azure-ai-language-conversations-py
implement conversational language understanding with Python
skills
Jul 31AnalyticsAzureLLMPython