
MCP Security Scanning: Audit Your AI Agent's Tools
A compromised GitHub Action called tj-actions/changed-files. A poisoned Nx dependency. A trojaned trivy-action. These weren't theoretical supply chain attacks. They happened in 2025 and early 2026, and the pattern across all of them was the same: attackers targeted CI/CD automation itself, exploiting the gap between what security teams thought their pipelines were doing and what they actually did.
The root cause isn't careless engineers. It's an architectural problem. CI/CD security controls live inside individual workflow YAML files, scattered across dozens or hundreds of repositories. Each file makes its own decisions about which events to trigger on, which secrets to access, and who can kick off a run. There's no central place to say "no workflow in this organization should ever use pull_request_target" and have that actually enforced.
That's changing. GitHub's 2026 Actions security roadmap formalizes a shift toward policy-driven execution governance, and the underlying concept applies to any CI/CD platform. If you manage pipelines across more than a handful of repositories, this matters.
Consider a typical mid-size engineering org: 40 repositories, each with 2-5 workflow files. That's potentially 200 YAML files, each independently configuring permissions, event triggers, and secret access. A platform engineer trying to audit "which workflows can be triggered by external contributors" has to read every single one.
This creates three concrete failure modes:
pull_request and pull_request_target is a few characters in YAML but a completely different trust boundary. More on that shortly.The Kubernetes world solved a similar problem years ago with admission controllers. You don't let every Deployment YAML decide its own security context. You define cluster-wide policies (via OPA Gatekeeper, Kyverno, or built-in Pod Security Standards) that reject non-compliant resources before they're admitted. CI/CD needs the same pattern.
GitHub's 2026 Actions security roadmap introduces workflow execution protections built on the existing ruleset framework. Instead of encoding security decisions into each workflow file, you define organization-wide (or enterprise-wide) policies that control two dimensions: who can trigger workflows, and which events are permitted.
Actor rules specify who can trigger workflow runs. You can restrict execution to specific users, roles (like repository admins), or trusted automation identities like GitHub Apps and Dependabot. An organization could, for example, restrict workflow_dispatch execution to maintainers only, preventing contributors with write access from manually triggering deployment or release workflows.
Event rules define which GitHub Actions event triggers are allowed. You could prohibit pull_request_target entirely across your org and only allow pull_request, ensuring workflows triggered by external contributions never get access to repository secrets or write permissions.
These protections scale across repositories without touching individual YAML files. Enterprises apply them using rulesets and repository custom properties, reducing both operational risk and the governance overhead that comes from chasing down misconfigurations repo by repo.
GitHub targets public preview for these workflow execution protections within 3-6 months, with general availability at 6 months.
To see why centralized event rules matter, look at the Pwn Request attack class documented by GitHub Security Lab.
The standard pull_request trigger runs workflows in a sandboxed context: no write permissions, no access to secrets. That's the safe default. But pull_request_target was introduced for legitimate use cases like labeling PRs or commenting, and it grants write access plus secrets. The problem comes when a workflow uses pull_request_target and also checks out the PR's code.
When that happens, an attacker can submit a malicious PR from a fork. The workflow checks out the attacker's code and runs it with full write permissions and secret access. The attacker's code can modify build scripts, inject packages with malicious postinstall hooks, or simply read the repository token from disk (the default checkout action persists credentials). A few characters of YAML misconfiguration, and the entire repository is compromised.
Here's what that vulnerable pattern looks like:
# INSECURE — do not use this pattern
on: pull_request_target
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
ref: ${{ github.event.pull_request.head.sha }}
- run: npm install && npm build
- uses: some/action@v2
with:
token: ${{ secrets.DEPLOY_TOKEN }}The npm install step runs arbitrary code from the PR author's fork with access to DEPLOY_TOKEN. Game over.
Now imagine an organization-level event rule that blocks pull_request_target across all repositories. That single policy would have prevented every Pwn Request variant from executing. No per-repo audit needed. No reliance on individual developers understanding the trust boundary difference between two similarly named event triggers.
Centralized policy evaluation for infrastructure decisions has existed in multiple forms. If you've worked with any of these, the CI/CD equivalent will feel familiar:
The common thread: policy is evaluated at a chokepoint, before the action happens, and the policy definition lives separately from the thing being controlled. Your Kubernetes Deployment YAML doesn't contain its own admission policy. Your Terraform plan doesn't embed its own Sentinel rules. Your CI/CD workflow shouldn't embed its own security governance either.
Here's where GitHub's approach gets something right that many security rollouts get wrong.
Workflow execution rules support an evaluate mode. When enabled, rules aren't enforced, but every workflow run that would have been blocked is surfaced in policy insights. You can see exactly which workflows across your organization would break before you flip the switch.
This matters because CI/CD pipelines are production infrastructure. Breaking a deployment workflow at 3pm on a Friday because a new security policy wasn't tested is, arguably, a worse outcome than the vulnerability it was supposed to prevent. The evaluate-then-enforce pattern lets security teams build confidence incrementally:
This is the same strategy behind Kubernetes Pod Security Standards' "warn" and "audit" modes, and behind Sentinel's advisory enforcement level. It works because it separates the question "what would this policy do?" from "should this policy block things right now?"
GitHub's built-in ruleset protections are coming for GitHub-hosted runners, but many teams run self-hosted or third-party managed runners. Those teams need this governance pattern even more, because they lack the platform-level controls entirely.
There are three practical mechanisms for implementing policy-driven execution on self-hosted infrastructure:
GitHub Actions runners support a ACTIONS_RUNNER_HOOK_JOB_STARTED environment variable that points to a script executed before every job. This script receives the full job context, including the workflow name, event trigger, repository, and actor. A policy-checking pre-job hook can inspect these fields against a centralized policy definition (a JSON file, an OPA endpoint, a database) and exit with a non-zero code to abort the job.
#!/bin/bash
# pre-job-policy-check.sh
EVENT_NAME=$(jq -r '.github_event_name' "$GITHUB_EVENT_PATH" 2>/dev/null)
# Block pull_request_target events on all self-hosted runners
if [[ "$EVENT_NAME" == "pull_request_target" ]]; then
echo "Policy violation: pull_request_target is not allowed on self-hosted runners."
exit 1
fi
# Optionally call an OPA endpoint for richer policy evaluation
DECISION=$(curl -s -X POST https://opa.internal/v1/data/cicd/allow \
-H 'Content-Type: application/json' \
-d "{\"input\": {\"event\": \"$EVENT_NAME\", \"repo\": \"$GITHUB_REPOSITORY\"}}")
if [[ $(echo "$DECISION" | jq -r '.result') != "true" ]]; then
echo "Policy violation: OPA denied this workflow execution."
exit 1
fiFor platforms that provision runners on demand (via webhook listeners that respond to workflow_job events), the webhook handler itself is a natural policy checkpoint. Before provisioning a runner, the handler can evaluate the incoming event payload against policy rules and simply not provision a runner if the request violates policy. The workflow run times out waiting for a runner that never arrives, with a clear audit trail explaining why.
GitHub already supports runner groups that restrict which repositories or workflows can target specific runner pools. Combined with runner labels and required workflow configurations, this gives organizations a coarse but effective policy layer: production deployment workflows only run on runners in the "production" group, which only accepts runs from specific repositories and workflows.
None of these mechanisms are as clean as GitHub's upcoming native protections, but they're available right now and they work. The key insight is the same regardless of implementation: separate policy definition from workflow definition, and evaluate policy at a chokepoint before execution begins.
If you were starting from scratch, here are the policies that cover the most common attack vectors:
pull_request_target org-wide. Unless a repository has a documented, reviewed exemption. This alone eliminates an entire class of privilege escalation attacks.workflow_dispatch to maintainers. Prevents write-access contributors from manually triggering deployment or release workflows.Deploy every one of these in evaluate mode first. Look at what they'd block. Fix the workflows that need fixing. Then enforce.
GitHub's 2026 roadmap covers more than just execution policy. There's dependency locking (think go.sum for Actions), scoped secrets that bind credentials to specific execution contexts, an Actions Data Stream for near-real-time execution telemetry, and a native egress firewall operating at Layer 7 outside the runner VM.
Taken together, these features represent a philosophical shift: treating CI/CD runners as first-class security domains rather than disposable compute. Runners execute untrusted code, handle sensitive credentials, and interact with production systems. They deserve the same observability, access control, and network segmentation that production servers get.
Whether you're on GitHub-hosted runners waiting for these features, or on self-hosted infrastructure building your own policy layer, the direction is the same. Stop scattering security logic across workflow files. Define it centrally, evaluate it before enforcing, and treat CI/CD governance with the same rigor you'd apply to production infrastructure.
Your CI/CD pipeline has the keys to your production environment. It's past time it had a security model that reflects that.
Tags
Recommended for you
What's next in your stack.
GET TENKI