Inspiration

A leaked API key keeps working until someone revokes it. Small teams without a security engineer can detect a leak, but the hardest part comes after: changing the credential without breaking the application that uses it, proving the old key is dead, and cleaning up the repository. In January 2024, an exposed GitHub token let an attacker copy roughly 5,000 New York Times repositories (BleepingComputer). In May 2026, Money Forward had to invalidate and reissue keys hardcoded in source code (Money Forward). GitGuardian found that 64% of secrets valid in 2022 were still exploitable four years later (State of Secrets Sprawl 2026).

Most tools stop at detection. Mistakeys closes the loop.

What it does

When the leak scan fails in GitLab CI, a three-agent GitLab Duo flow takes over with no human in the loop:

  1. Gatekeeper confirms the failure is the exact leak-scan job (it ignores unrelated failures) and reads the fingerprint-only leak report.
  2. Rotator calls typed operations on the Mistakeys rotator: issue a replacement, update the running consumer, verify the replacement returns HTTP 200, activate it, revoke the leaked key, and verify the leaked key now returns HTTP 401.
  3. Reporter commits the cleanup directly to the isolated demo branch, verifies the commit by reading the branch head, and files a fingerprint-only incident report.

The cleanup push runs a normal pipeline that goes green. Re-running the failed pipeline returns already_handled with created: false: no duplicate incident, branch or commit.

Live status page: mistakeys.ajkadri.dev shows the active key fingerprint and the full chain of real rotations, each with an old/new key overlap under 0.02 seconds.

Built to the GitLab Security Standard

GitLab published its Security Standard v1.0 on October 6, 2026: "Autonomy isn't given. It's earned, one stage at a time." It calls for credentials that are "Centralized, least privilege, rotated, and revocable, with a defined scope and lifetime for every credential" (GitLab Security Standard). Mistakeys maps to all five stages:

Stage What Mistakeys does
Authorize Short-lived GitLab OIDC ID token, verified against GitLab JWKS (issuer, audience, expiry, project path, RS256). No stored CI secret.
Isolate The gatekeeper ignores non-leak failures. The reporter may change one fixture file on demo/* branches only; it never touches main, CI config, or merges.
Verify The new key must return 200 on the real consumer before activation; the old key must return 401 after revocation. Revoked keys can never be reactivated.
Release The cleanup commit triggers a normal pipeline that goes green.
Respond A fingerprint-only incident issue linked to the triggering pipeline; idempotent retries.

Proof

All evidence uses fingerprints only. No raw key appears in any issue, session or report.

How we built it

  • GitLab Duo Agent Platform: a custom three-component flow (flows/mistakeys-recovery.yaml) triggered by the failed pipeline event, using gitlab_api_get, create_commit, create_issue and create_issue_note.
  • Identity: .gitlab/duo/agent-config.yml issues a GitLab OIDC ID token to the flow, with a network policy that only allows the rotator's domain. The rotator verifies it with jose against GitLab's JWKS.
  • Rotator, API and consumer: TypeScript (Node 22, Hono), in Docker on a VPS behind Caddy. The rotator is never exposed publicly; only typed operations exist, no general credential commands.
  • CI/CD: tests, GitLab Secret Detection, a pinned Gitleaks scan with a custom rule (full reachable history on main, push range on other branches), image builds tagged by commit, and a protected production deploy over a forced-command SSH key with health verification and rollback.
  • DevSecOps stages covered: Plan, Create, Verify, Package, Secure, Release, Configure, Monitor, Govern.

Challenges we ran into

  • Flows can't read CI/CD variables, so the flow authenticates with a GitLab OIDC ID token instead, and the rotator needed outbound access only for GitLab's JWKS.
  • There is no merge tool for flows, so the reporter commits the cleanup directly to the isolated demo branch instead of opening an MR.
  • create_commit returns no SHA, so the reporter now reads the branch head and verifies the commit title before recording the SHA.
  • Proving two things at once: the app never broke (200 throughout) and the leaked key is really dead (401).

Accomplishments that we're proud of

Repeated live rotations of a real credential, each with sub-0.02-second overlap and zero downtime for the consumer, fully hands-off from a failed pipeline to a green cleanup pipeline and an audit-ready incident, without ever exposing the secret to the agent's output.

What we learned

Rotation is only trustworthy when the replacement is verified before the old key is revoked, and when the agent leaves enough evidence for a human to audit without ever seeing the secret. Agent autonomy is safest when it is earned through narrow, typed, verifiable steps.

What's next for Mistakeys

Provider adapters behind the same typed interface: GitLab project and deploy tokens, cloud provider access keys, and database passwords. orders-api currently stands in for any application key whose consumer can be updated and verified. Cleanup removes the key from the branch's current file; the revoked value stays in Git history by design.

Built With

Share this project:

Updates

Submission history