Inspiration
AI coding agents can now open merge requests, fix failing pipelines, and push changes on their own. The usual way to keep them safe is a system prompt that says "don't touch CI" or "don't delete tests". But a prompt is a request, not a control. Prompt injection in an issue, a file, or a tool output can talk an agent out of it. We wanted guardrails that hold even when the agent is wrong, confused, or compromised.
Airlock's idea: autonomous agents that can ship, but cannot break the rules. The rules live in the CI/CD pipeline, where they are deterministic and auditable, not in the prompt.
What it does
Airlock adds programmatic boundaries around AI agents on the GitLab Duo Agent Platform:
- Dual-agent triage (
airlock_triage): A read-only reader agent treats issue text as untrusted data and emits strict JSON (type, severity, summary, acceptance criteria, suspicious-instruction flags). A separate writer agent has onlycreate_issue_noteand sees only that JSON, never the raw issue. - Control-plane policy gate:
policy_gate.pyblocks any MR that touches.gitlab-ci.yml,tools/,policy/,deploy/,CODEOWNERS, or deletes tests, unless a human appliescontrol-plane-approved. It diffs with--no-renamesto stop rename evasion, and checks the label events API so labels applied by agent accounts are ignored. MRs over 10 files or 300 lines needsize-approved. - Verify-twice security gate:
sast_gate.pyfails closed. It requires the SAST report to exist and blocks on High/Critical findings, so passing unit tests alone can't let an unfixed vulnerability through. - Historical regression guardrail:
regression_gate.pykeeps an incident memory (BOLA/IDOR, unsafeeval, path traversal), matches MR changes against past failure surfaces, and reproduces the bug in CI to block a merge until it's verifiably fixed. - State and readiness integrity:
state_guard.pyrequires 100% of checklist checks before an agent can claim "ready", locks terminal states, and rejects stale updates with version tokens and a 5-minute TTL. - Bounded self-healing: Fix-pipeline attempts are capped at 3, then the flow escalates to a human (
fix_budget.py). - Human release gate: Staging publishes automatically.
publish-productionis a manual job. - Governance scorecard: Every run produces a ledger and a markdown scorecard of gate outcomes.
Verified evidence
These are live GitLab pipelines, not mockups:
- MR !2 blocked red with
POLICY VIOLATIONfor tampering with.gitlab-ci.yml - MR !3 blocked red for a deleted test, caught by
--no-renames(rename evasion defense) - MR !1 clean pass on all MR jobs
- Main branch pipeline #2993995494: wheel built, unit tests passed, SAST and secret detection ran, staging published, smoke test passed, scorecard generated, production left pending human approval
The full table with links is in the repo README.
How we built it
Python (standard library plus pytest) for the gate scripts, GitLab CI/CD for enforcement, GitLab SAST (Semgrep) and Secret Detection for scanning, the GitLab REST API for label provenance, the Generic Package Registry for staging/smoke testing, and a small interactive dashboard and CLI playground for exploring the guardrails. The Duo flow definitions implement the reader/writer triage split. The test suite has 53 unit tests.
Challenges
- Rename evasion: a plain diff lets an agent "rename" a protected file out of the policy.
--no-renamesfixed it. - Agents applying their own approval labels: a label alone proves nothing, so we verify who applied it through label events and fail closed if the token is missing.
- Running SAST on MR pipelines without wasting CI minutes on duplicate branch pipelines.
- Staying honest about limits: a gate is a guardrail, not a security boundary. An MR pipeline runs its own CI file. We document this and the mitigations (protected branches, CODEOWNERS, an external CI config).
What we learned
- Constraints beat guidance. Telling an agent what it cannot do and enforcing it outside the model works better than asking nicely.
- Fail closed by default: missing report, missing token, or missing label should mean "block".
- The most useful part of a threat model is the residual-risk column.
Known limitations and what's next
We would rather state these than hide them:
- The Plan/Create flow sessions and the Fix CI/CD Pipeline loop are built but have not yet been captured as live verified runs.
- The label-provenance check has not yet been exercised on a live MR.
- The 3-attempt cap relies on commit-pattern matching, so a CI-managed counter would be stronger.
- Next: protect the gate job itself with an external CI config and CODEOWNERS, constrain the free-text fields passed from reader to writer, and flag weakened test assertions, not just deleted tests.
Links
- GitLab project: https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43727879/showcase
- Live demo: https://airlock-guardrails.vercel.app/#simulator
- GitHub mirror: https://github.com/vikassharma270907-alt/airlock-guardrails
Architecture for regression_gate.py and state_guard.py was adapted from the Time Machine CI and ReturnReady Guardian Transcend showcase projects.
Built With
- gitlab
- gitlab-ci-cd
- gitlab-duo-agent-platform
- gitlab-package-registry
- gitlab-rest-api
- gitlab-sast
- pytest
- python
- secret-detection
- semgrep
- vercel
Log in or sign up for Devpost to join the conversation.