microsegment.io

segment all the things

A CI/CD runner is not just a machine that builds software.

It is a workload that routinely sits between untrusted input and high-trust systems: source repositories, package registries, artifact stores, cloud APIs, deployment platforms, ticketing systems, and production environments.

That makes it privileged infrastructure, even when the workflow looks administrative rather than security-critical.

Wiz’s disclosure about a GitHub Actions vulnerability in a public Snowflake repository is a clean example. An unauthenticated user could place crafted content in a GitHub issue title. A workflow inserted that title directly into a shell script. Wiz’s autonomous Red Agent used the resulting script injection to execute commands on the runner, extract a Jira credential, and validate read access to internal Snowflake Jira projects.

Snowflake patched the workflow on the day Wiz reported it, revoked and rotated the credential, and found no unauthorized third-party access during the five-day exposure window. This was responsible research, not a production breach.

But the trust path was real:

public issue -> privileged workflow -> runner -> stored credential -> internal SaaS data

The useful question is not whether AI wrote the vulnerable line. Wiz updated its post to say Copilot was a co-author on the pull request and marked the change as clear, but that it is unclear whether the vulnerable change itself was AI-assisted.

The useful question is why a public string could cross so many trust boundaries without being stopped.

The Vulnerability Was Small. The Trust Chain Was Not.

The vulnerable workflow processed a GitHub issue title inside a run: block. By crafting the title, Wiz could break out of the intended command and execute additional shell instructions on the runner.

GitHub’s own secure-use guidance for Actions warns against this pattern. It recommends passing untrusted context through an intermediate environment variable or an action argument instead of interpolating it directly into generated shell code.

That fixes the injection point. It does not answer the architectural question.

Once code execution occurred, the runner held a Jira token and had outbound reachability to both the researcher’s callback listener and Snowflake’s Atlassian tenant. The token granted read access across engineering, security compliance, and bug-bounty projects, according to Wiz.

Each part of that chain may have had a legitimate operational explanation:

  • the workflow needed issue data;
  • the runner needed to execute automation;
  • the job needed a Jira credential;
  • Jira needed to accept API requests;
  • the runner needed some Internet access.

The failure came from combining those permissions in one execution context without a containment boundary around the job.

This is how lateral movement looks in cloud-native operations. It may not use SMB, RDP, or an internal subnet scan. It can move from an event payload into a runner, from the runner into a non-human identity, and from that identity into SaaS data.

Treat Every Trigger As A Trust Decision

Workflow triggers are ingress paths.

An issue title, pull-request body, branch name, commit message, artifact, package manifest, webhook, or chat command can be attacker-controlled. The fact that GitHub, GitLab, Jenkins, or another trusted platform delivered the event does not make the event content trusted.

Classify workflows by trigger and consequence:

  1. Untrusted intake processes public issues, pull requests from forks, external webhooks, and user-supplied artifacts.
  2. Internal validation runs tests against code or artifacts that have passed an initial trust gate.
  3. Privileged delivery signs artifacts, publishes packages, changes infrastructure, or deploys production workloads.
  4. Administrative automation writes to ticketing, identity, security, or management systems.

Do not collapse these classes into one runner pool with shared credentials and shared network access.

The job that parses a public issue should not run in the same trust context as the job that can reach production or internal administration systems. A handoff between them should carry a narrow, validated result, not the original untrusted execution context.

Microsegment The Runner By Job, Not Just By Subnet

Putting all runners in a “CI network” is not meaningful segmentation if every runner can reach the same internal and cloud services.

Policy should follow the job’s purpose.

An untrusted-input runner may need access to GitHub and an isolated artifact store. It should not have routes to Jira, secrets platforms, cloud control planes, production APIs, internal databases, or administrative interfaces.

A deployment runner may need one artifact source and one deployment API. It should not accept public triggers, browse arbitrary Internet destinations, or share a credential set with validation jobs.

A signing job should be smaller still. It should accept only approved artifact digests, reach only the signing service and controlled publication target, and run with no general interactive access.

CISA and NSA software-supply-chain guidance recommends protecting repository, build, and test environments with controls including network segmentation, firewalling, monitoring, encryption, and controlled access. The point is not to create another broad development zone. It is to make every permitted connection defensible.

For each runner class, define:

  • permitted trigger sources;
  • approved repositories and reusable workflows;
  • required inbound and outbound destinations;
  • protocols and ports;
  • identity and token claims;
  • secret availability;
  • maximum job lifetime;
  • logging and artifact-retention requirements;
  • which higher-trust stage, if any, may consume its output.

That is a containment policy, not just a firewall rule.

Egress Is Part Of The Security Boundary

The Snowflake proof of concept exfiltrated a credential through an outbound callback. If a runner can connect to any Internet destination, successful code execution can immediately become data loss.

Many build systems need Internet access, but “the Internet” is not a dependency definition.

Separate common egress needs:

  • source-control APIs;
  • approved package registries;
  • artifact repositories;
  • vulnerability and license databases;
  • cloud identity endpoints;
  • deployment APIs;
  • internal SaaS APIs.

Route these through explicit policy enforcement points such as egress gateways, proxies, private endpoints, or workload-aware firewalls. Deny direct outbound DNS and arbitrary HTTPS where the job does not require them. Log destination, workload identity, repository, workflow, and job identifier together.

Allowlisting domains is not perfect. Shared hosting, redirects, package ecosystems, and compromised approved services remain risks. But a narrow egress policy removes the easiest path from runner compromise to an attacker-controlled listener and makes exceptions visible.

The goal is bounded failure: code execution in one job should not automatically create a general-purpose exfiltration platform.

Replace Stored Secrets With Narrow, Short-Lived Identity

A network boundary limits where a runner can connect. Identity policy limits what it can do when it gets there. CI/CD needs both.

GitHub recommends using OpenID Connect for cloud access instead of storing long-lived cloud credentials. OIDC lets the target platform issue a short-lived token based on claims such as repository, branch, environment, or reusable workflow.

That trust policy must be narrow. “Any workflow in this organization” is not least privilege. Bind access to the exact repository, workflow, ref, environment, and audience that need it. Grant the smallest target-side role and keep the token lifetime short.

For SaaS systems that do not support workload federation:

  • use a dedicated service identity per workflow or function;
  • prefer read-only scopes unless the job must write;
  • restrict the account by source network or private endpoint where supported;
  • issue secrets only after required approval gates;
  • prevent untrusted jobs from referencing privileged environments;
  • rotate automatically and monitor every use outside the expected job context.

Required reviewers for protected environments can stop a workflow job from accessing environment secrets until approval. That is useful for rare privileged operations. It is not a reason to leave broad credentials available to routine automation.

Separate SaaS Trust Paths From Production Trust Paths

The affected credential reached Jira rather than production. That does not make the path low risk.

Engineering and security ticketing can contain vulnerability details, architecture notes, incident timelines, customer context, internal links, and operational procedures. A SaaS token can be reconnaissance infrastructure even when it cannot deploy code.

Map CI/CD access to SaaS systems with the same discipline used for production:

  • Which workflow can request the credential?
  • Which runner class can use it?
  • Which Jira projects, GitHub repositories, Slack channels, or documentation spaces can it read or change?
  • Can it enumerate users, projects, integrations, or attachments?
  • Can it reach the SaaS endpoint from any workload or only the intended runner segment?
  • Will a request from an unexpected job, repository, country, or network be blocked or only logged?

SaaS is part of the enterprise trust graph. Microsegmentation has to include API reachability and service identity, not stop at data-center VLANs.

Build The Failure Test Into The Pipeline

Static scanning matters. GitHub documents code scanning, restricted token permissions, immutable action pinning, protected environments, and careful handling of untrusted input as core Actions defenses.

But a missed finding should not expose every reachable system.

Test the containment architecture directly:

  1. Run a representative untrusted-input job and attempt connections to Jira, cloud metadata, production APIs, secrets stores, internal DNS, and arbitrary Internet listeners.
  2. Confirm that each unnecessary path is blocked and logged.
  3. Inspect the token claims and permissions available to the job.
  4. Verify that artifacts crossing into a higher-trust stage are immutable, identified by digest, and revalidated.
  5. Attempt to trigger privileged workflows from forks, public issues, untrusted artifacts, and unauthorized identities.
  6. Revoke a runner identity and confirm that cached credentials cannot continue to operate.
  7. Compromise one self-hosted runner in a test environment and measure which peers, control planes, and SaaS APIs remain reachable.

NIST SP 800-207 says Zero Trust focuses on protecting resources and workflows rather than granting implicit trust based on network location or ownership. CI/CD is one of the clearest places to apply that principle. A runner is not trusted because the platform created it or because it lives in a build subnet.

Trust should exist for one job, one identity, one set of destinations, and one limited time window.

Contain The Automation Before It Contains You

The durable lesson from the Snowflake disclosure is not “AI code is unsafe” or “GitHub Actions is unsafe.”

The lesson is that automation connects trust domains faster than most teams model them.

A public issue became shell input. The shell had a credential. The credential reached an internal SaaS system. One validation error turned the entire chain into an attack path.

Fix the injection. Scan the workflow. Review AI-assisted changes. Pin dependencies. Protect secrets.

Then assume one of those controls will still fail.

Contain the runner with job-specific network policy, narrow egress, short-lived identity, separated trust stages, and direct failure testing. That is how a vulnerable workflow remains a local incident instead of becoming a route through the rest of the enterprise.

Sources

This article was generated with the assistance of AI and reviewed by the author.