Skip to main content
← Back to All Posts

Using GitHub Actions OIDC for AI Agents Without Long-Lived Cloud Keys

April 18, 2026•10 min read•AI Security
Using GitHub Actions OIDC for AI Agents Without Long-Lived Cloud Keys

Most teams that bolt AI agents into CI eventually make the same bad shortcut. They give the workflow a long-lived cloud key, store it in GitHub Secrets, and hope review plus branch protection is enough.

That works right up until the wrong workflow runs, a forked change finds a path around the guardrails, or nobody remembers where else that key was reused.

For agentic systems, that trade is worse. A model can chain many actions quickly, so a leaked credential can do the same. GitHub Actions OIDC is the cleaner pattern: the workflow proves who it is at runtime, gets a short-lived cloud role, and the privilege disappears when the run ends.

Why this matters

If an AI workflow can open PRs, run evaluations, push artifacts, or trigger deployments, then credential shape matters as much as prompt shape. Static keys create silent blast radius, weak reviewability, and miserable rotation habits. OIDC moves the trust boundary into cloud IAM, where you can actually constrain repo, branch, workflow, and environment.

Architecture or workflow overview

flowchart LR
    A[Pull request or deploy workflow] --> B[GitHub Actions runner]
    B --> C[Request OIDC token]
    C --> D[Cloud STS / workload identity]
    D --> E[Short-lived role credentials]
    E --> F[Agent job: eval, deploy, migrate]
    F --> G[Logs, artifacts, approvals]

The key design choice is that the agent never starts with a stored cloud secret. It earns a short-lived role from the cloud control plane after GitHub asserts the workflow identity.

Recommended trust shape

  1. Give only the credentialed job id-token: write.
  2. Match on repo plus workflow path, not repo alone.
  3. Split eval and deploy into separate roles.
  4. Put production behind environments, approvals, or both.
  5. Log caller identity before any write step.

Implementation details

Minimal GitHub Actions workflow

This setup is a good fit for AI-driven deploy or eval jobs because the credential only exists after GitHub attests the workflow identity.

name: ai-eval-and-deploy

on:
  workflow_dispatch:
  push:
    branches: [master]

permissions:
  contents: read
  id-token: write

jobs:
  eval:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-ai-eval
          aws-region: us-east-1
      - name: Run agent evaluation suite
        run: |
          python scripts/run_eval.py \
            --dataset evals/release.json \
            --output artifacts/eval-report.json

  deploy:
    needs: eval
    environment: production
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Configure deploy role via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::123456789012:role/github-ai-deploy
          aws-region: us-east-1
      - name: Deploy bounded release
        run: ./scripts/deploy.sh

Two details are doing most of the work here. The deploy job is separated from eval, and production is attached to an environment so a human approval gate can exist outside the model loop.

AWS trust policy that actually constrains the workflow

A lot of OIDC tutorials prove login, but not safety. This is the part that makes the role meaningfully bounded.

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
      },
      "Action": "sts:AssumeRoleWithWebIdentity",
      "Condition": {
        "StringEquals": {
          "token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
        },
        "StringLike": {
          "token.actions.githubusercontent.com:sub": [
            "repo:negiadventures/negiadventures.github.io:ref:refs/heads/master",
            "repo:negiadventures/negiadventures.github.io:environment:production"
          ],
          "token.actions.githubusercontent.com:job_workflow_ref": "negiadventures/negiadventures.github.io/.github/workflows/ai-eval-and-deploy.yml@refs/heads/master"
        }
      }
    }
  ]
}

If you skip workflow-path matching, any other workflow in the repo may inherit the same cloud power. That is the failure I see most often.

Optional session policy trim

Even with a decent role, trimming permissions per run keeps the agent inside a smaller box.

aws sts assume-role \
  --role-arn arn:aws:iam::123456789012:role/github-ai-deploy \
  --role-session-name pr-ship-1842 \
  --policy '{
    "Version":"2012-10-17",
    "Statement":[{
      "Effect":"Allow",
      "Action":["s3:PutObject","cloudfront:CreateInvalidation"],
      "Resource":[
        "arn:aws:s3:::my-site-bucket/*",
        "arn:aws:cloudfront::123456789012:distribution/E123ABC456DEF"
      ]
    }]
  }'

This matters when the same base role is used for multiple release paths, but a given run only needs one narrow write shape.

Terminal proof that the run is using ephemeral credentials

$ aws sts get-caller-identity
{
  "UserId": "AROAXXXXXXXX:GitHubActions",
  "Account": "123456789012",
  "Arn": "arn:aws:sts::123456789012:assumed-role/github-ai-deploy/GitHubActions"
}

$ aws configure list
      Name                    Value             Type    Location
      ----                    -----             ----    --------
   access_key     ****************ABCD temporary-credentials-file
   secret_key     ****************wxyz temporary-credentials-file
       region                us-east-1         env    AWS_REGION

If the caller identity looks like a normal IAM user, you are not getting the benefit you think you are.

Tradeoffs and what went wrong

ApproachSecurity postureOperational painBest fitWhat I would not do
Static cloud keys in GitHub SecretsWeak, long-lived blast radiusEasy at first, painful laterLegacy pipelines you are retiringNew agent workflows
GitHub OIDC to cloud roleStrong, short-lived, auditableMedium setup, low ongoing painDeploys, eval jobs, bounded writesWildcard trust policies
Self-hosted runner machine identityMixed, depends on host hardeningHigh ops burdenInternal-only specialized networksPublic repos or lightly managed hosts
Best practices
  • Use separate roles for eval, deploy, and read-only inspection jobs.
  • Bind trust to the workflow path as well as the repo.
  • Prefer environment-gated production roles.
  • Keep cloud IAM permissions narrower than the workflow permissions.
Pitfalls
  • pull_request_target can quietly widen your threat model.
  • Reusable workflows deserve their own trust review.
  • Self-hosted runners can turn a credential issue into a host issue.
  • Artifact upload and production deploy should not share the same role by default.

Practical checklist

  • No long-lived cloud keys stored in GitHub for this workflow
  • Workflow requests id-token: write only where needed
  • Trust policy checks repo, branch or environment, and workflow path
  • Eval and deploy jobs use separate roles
  • Production path requires environment approval or equivalent guardrail
  • Logs prove the run used an assumed role session
  • Session policies or narrow IAM policies limit write scope

Conclusion

If an AI agent can touch cloud systems, give it credentials that expire fast and only exist when the workflow proves who it is. GitHub Actions OIDC is one of the few changes that improves both security and day-two operations instead of making you choose.

GitHub ActionsOIDCAI AgentsCloud SecurityCI/CD

← Back to all posts