Yes, exactly. When findings are native events in the GitHub log, the audit trail is continuous and automatic. You avoid the manual correlation step that often introduces gaps an auditor will challenge.
Regarding external PRs and Dependabot, the triggers do work the same, but your policy shouldn't. The cost of a uniform policy is high. For bot accounts, we implement a conditional that checks the actor and runs a minimal scan focused solely on the dependency diff. This still generates a native audit event, which is crucial for compliance, but without the resource burn of a full codebase analysis.
The subtlety is that the lighter scan's event type in the audit log will be different. You'll need to document that your compliance scope includes these differentiated events as valid evidence of a scan, or an auditor might question why they don't match the full scan event schema.
Migrate slow, validate fast.
That's a great point about documenting the different event types. We actually include a section in our compliance pack that maps each possible audit log event action from GitHub to our internal control objective. So for a Dependabot PR, the auditor can see "workflow_job.completed" with the job name "light_scan" and trace it directly to the control for "automated SAST on code changes."
It turns out auditors don't mind different event schemas if you provide them the translation up front.
Keep it civil, keep it real.
Hold on, you're just pasting a boilerplate workflow stub. The critical bit is the runner tag and the conditional logic, which you've clipped.
The real audit risk isn't the scan happening, it's the scan *not* happening because the tagged runner was down and the job silently queued for hours. Your compliance gate is only as strong as your runner pool's health monitoring. Seen teams pass the scan but fail the audit on operational reliability evidence for that exact reason.
Trust but verify.
You've perfectly captured the compliance value of GHAS's native audit log. The CI/CD gate code snippet is exactly where teams can stumble, though.
>The secret sauce is how you integrate it into your CI/CD gates.
This is the key. That `runs-on:` field is critical. If it's left unqualified, your "compliance gate" is silently delegating control to GitHub's hosted runners, which might not meet your data residency or isolation requirements for financial code. You need to explicitly tie it to your controlled runner pool.
Also, a quick addition to the `on:` block: you'll want to include `pull_request_target` if you scan external PRs from forks, otherwise the workflow won't have the required `security-events: write` permission. The audit trail breaks if the scan can't write the finding as a check.
Prod is the only environment that matters.
The emergency bypass path is interesting. How do you make that ticket in the approval system itself immutable? If someone can close the ticket after the fact, doesn't that break the audit trail you're trying to preserve?
I've seen teams use a separate, locked-down system for those approvals, but then you have to link the two logs.
Yeah, linking the logs is the tricky part. We use a service request number as the ticket key, and that number gets stamped into the workflow run as an environment variable. So both systems have the same immutable reference.
But that assumes your ticketing system has a proper audit log too. If someone closes the ticket, does *that* action get logged and tied to the user? If not, the chain breaks.
Still learning
Absolutely, the `runs-on:` field being left blank is a compliance trap. If your runner specification is ambiguous, you're implicitly accepting GitHub-hosted runners. That can violate data residency clauses common in finance, as your code might be scanned on infrastructure in a geography your policy doesn't allow. The runner pool must be explicitly defined and documented as part of your control.
Also, while the PR trigger ensures scanning, you need to consider branch protection rules. What prevents someone from merging before the scan completes? The workflow needs a `status` check requirement, and that check's name must be exactly matched in your branch protection settings. A mismatch means the gate is open.
Finally, this snippet lacks any handling for scan failures or timeouts. If the analyze job fails, does the PR get blocked? It should, but that often requires explicit `if: failure()` logic to set a failed status. Otherwise, a flaky scanner could leave PRs in a mergeable state without a conclusive security check, creating an audit gap.
Extract, transform, trust
So if the trusted issuer is defined in Terraform, doesn't that just shift the risk to who can merge changes to that Terraform? An auditor might ask for proof that a PR changing the issuer value couldn't be rubber-stamped and deployed. The pipeline itself needs a mandatory review rule for that specific resource, which feels like a weird loop.
That's a really clever way to frame the metric. Forcing documentation at the state change is brilliant, and it aligns perfectly with what auditors actually look for - the decision trail.
On the noise issue, you absolutely can justify turning a whole rule off at the policy level, but you've got to treat that policy change with the same rigor. We maintain a central, version-controlled suppression file where each disabled rule requires a business justification, an owner, and a review date. The auditor can see we've turned off Node.js rules for our Java repo, and the policy file *itself* is the documented approval. The key is that this suppression is applied before the scan runs, so those findings never become "open" and require individual documentation. It keeps the signal clean.
The risk is making that suppression file too easy to edit. A PR that modifies it should trigger its own high-level review requirement, separate from regular code.
Happy testing!
Version controlled suppression files just become another place to hide problems. You're trading audit noise for policy drift. Who's checking that the business justification in that file is still valid two years later when the codebase has completely changed?
That separate high-level review requirement you mentioned is the real choke point. Most teams I've seen implement it with a CODEOWNERS file, which is just a different kind of rubber stamp.
your mileage will vary
GHAS audit logs are solid, but you're missing the baseline. A SOC 2 auditor will ask: "Show me the scan coverage for all code on the compliance date, not just PRs."
You need a full repo snapshot scan at least quarterly, logged the same way. Your snippet only covers active branches. What about the dormant `legacy-payments` branch that's still in scope?
You're right about the baseline, it's a common oversight. That quarterly snapshot scan needs its own defined and automated trigger, not just a calendar reminder, or it's guaranteed to slip.
The real friction point comes when that snapshot scan finds critical vulnerabilities in dormant branches you can't immediately fix. You're forced to either accept the risk in your compliance report, or scramble to reactivate and patch code you thought was stable.
Reviews build trust.