Two weeks ago, an attacker compromised 75 GitHub Actions tags in the Trivy project and hijacked $SECRETS environment variables from CI/CD pipelines across thousands of repos.
The attack worked because GitHub Actions secrets are static and permanent. Once set, they sit in $SECRETS forever — readable by every step in every workflow, reusable indefinitely after exfiltration.
The fix isn’t faster rotation. It’s shorter-lived credentials.
The Real Problem: Static Secrets in CI/CD
When you add a secret to GitHub Actions, you’re creating a permanent credential that:
- Never expires. Your OpenAI key from June 2025? Still valid. Still sitting in
$SECRETS. - Has no scope. Every workflow step can read every secret. A compromised
eslintaction sees your production database credentials. - Lives forever after theft. An attacker who exfiltrates the secret has permanent access. You won’t know until you see the bill.
This is the exact same problem we’ve been solving for AI agents — and the fix is the same architecture.
Ephemeral Injection: The 3-Line Fix
- uses: VeneriteKennyv/api-stronghold-inject-secrets@v2
with:
api-user-secret: ${{ secrets.AS_SECRET }}
providers: openai,stripe
ttl: 900
Here’s what happens:
- Job starts. The action creates a 15-minute scoped session.
- Keys are decrypted locally in the GitHub runner — the server never sees them in plaintext.
- Env vars are set and masked in logs.
- Job ends. The session is automatically revoked. The keys are gone.
If an attacker compromises a step mid-run and steals the session token, they get something that expires in minutes. Not months. Not “whenever someone remembers to rotate.”
What Changes (And What Doesn’t)
You still have one static secret in GitHub: AS_SECRET. This is the bootstrap credential — it can only create ephemeral sessions. It cannot read keys directly. If it’s stolen, an attacker can create sessions, but those sessions expire and are scoped to only the providers you configure.
You lose nothing. Your workflow runs exactly the same. $OPENAI_API_KEY is still available as an env var. The only difference is it’s ephemeral instead of eternal.
Audit Your Current Secrets
Before migrating, assess what you have:
api-stronghold-cli audit github-actions --repo your-org/your-repo
NAME CREATED UPDATED AGE STATUS
OPENAI_API_KEY 2025-06-15 2025-06-15 279d STALE
STRIPE_SECRET_KEY 2025-11-01 2025-11-01 140d STALE
AWS_ACCESS_KEY_ID 2026-03-01 2026-03-01 20d OK
DATABASE_URL 2025-08-20 2025-08-20 213d STALE
4 secrets found, 3 stale (>90 days)
Three of those secrets haven’t been rotated in over 90 days. Every one is a permanent credential that an attacker can use indefinitely after exfiltration.
The audit can scan an entire org:
api-stronghold-cli audit github-actions --org my-company --max-age-days 30
The Supply Chain Math
Here’s the threat model:
- You use 20 GitHub Actions in your workflow.
- Each action’s tag (
@v1,@latest) is controlled by the action author. - If any of those 20 maintainers’ accounts are compromised, the attacker can push malicious code that reads
$SECRETS. - Your static secrets are exfiltrated silently. No alerts, no expiry, no scope limits.
With ephemeral injection:
- The attacker gets a session token, not a permanent key.
- The token expires in minutes.
- The session is scoped to specific providers.
- The exfiltration is logged with IP, timestamp, and session ID.
- You can trigger an emergency lockout to kill everything instantly.
Full Migration Workflow
Step 1: Create an API User
Dashboard → Agents → Create Agent. Copy the secret.
Step 2: Add to GitHub
One secret: AS_SECRET = your agent secret. Remove OPENAI_API_KEY, STRIPE_SECRET_KEY, etc.
Step 3: Update Your Workflow
Replace direct env var usage with the inject step:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: VeneriteKennyv/api-stronghold-inject-secrets@v2
with:
api-user-secret: ${{ secrets.AS_SECRET }}
providers: openai,stripe
ttl: 900
- run: npm run deploy
Step 4: Verify
The key-count output tells you how many keys were injected:
- uses: VeneriteKennyv/api-stronghold-inject-secrets@v2
id: secrets
with:
api-user-secret: ${{ secrets.AS_SECRET }}
providers: openai
- run: echo "Injected ${{ steps.secrets.outputs.key-count }} keys"
The Bigger Picture
This is the same architecture we use for AI agents (phantom tokens, scoped proxy sessions) applied to CI/CD. The principle is identical:
The credential you can’t steal is the credential that’s already expired.
GitHub Actions secrets were designed for a world where CI/CD pipelines were trusted. Supply chain attacks proved that world doesn’t exist anymore. The fix is to stop storing permanent credentials in places attackers can reach, and start injecting short-lived ones that self-destruct.
API Stronghold provides zero-knowledge API key management with ephemeral credential injection for AI agents and CI/CD pipelines. Get started free.