Inspiration
"Life After Code" means agents write and change code, and humans review it. The scariest change an agent can make is a quiet one, like deleting a single guard call in a release pipeline while "simplifying" it. The diff looks harmless and the unit tests still pass. Yet a privileged release action can now run with an injected, stale or forged approval.
Code review checks what changed. Nobody checks who is still allowed to trigger the privileged action after the change. We wanted that question answered automatically, on every merge request, before merge.
What it does
RelayGuard audits the authorization path of a multi-agent workflow (planner → guard → privileged release) on every merge request.
- A five-agent GitLab Duo flow investigates the MR, with strictly separated duties:
- Analyst: the only agent that receives the untrusted MR text and trigger as input.
- Planner: chooses which attack scenarios apply.
- Auditor: checks every claim against real CI evidence.
- Fixer: the only agent that can change code (create branch, commit, MR). It never sees the MR text and may only restore the guard from a trusted fixture, never invent a fix.
- Reporter: its only tool is posting the evidence note on the MR.
- CI decides, not the agents. The blocking
relayguard-auditjob replays four attacks against the candidate code:- A1: injected instruction.
- A2: stale v2.3 approval.
- A3: forged signing key.
- A4: staging approval reused for production.
It also runs one legitimate signed deploy (L1). If any attack scenario reaches the simulated privileged deploy action, the job fails and the merge is blocked. Nothing is deployed for real.
- Humans approve. Agents never merge. A corrective MR only gets to "Ready to merge", and a person makes the call.
Who needs this: teams that let coding agents modify workflows able to deploy software, publish packages or perform other privileged actions, where a small code change can silently weaken authorization.
In the demo:
- MR !5 removes the guard. All four attack scenarios reach the simulated deploy action, so
relayguard-auditreports STATUS: FAILED and the merge is blocked. - MR !6, opened by the Fixer, restores the guard. All four attack scenarios are blocked at the guard, the legitimate signed deploy still succeeds, and all three jobs pass.
How we built it
- GitLab Duo Agent Platform custom flow:
- Five chained agents defined in the flow registry v1 YAML (
.gitlab/duo/flows/relayguard.yaml). - Agents pass data to each other via
context:<agent>.final_answer. - Triggered by a mention or by assigning the service account as reviewer.
- Least privilege is enforced through each agent's toolset in the YAML. Analyst, Planner and Auditor have read-only tools. Only the Fixer has
create_branch/create_commit/create_merge_request. The Reporter only hascreate_merge_request_note.
- Five chained agents defined in the flow registry v1 YAML (
- GitLab CI/CD: three jobs,
pytest,relayguard-benchandrelayguard-audit. The gate code, harness and scenario catalogue are installed from the base revision usinggit worktree. Only the candidate's workflow code is executed, so an MR cannot edit the gate logic that judges it. Any change to.gitlab-ci.ymlor.gitlab/is flagged as NEEDS_REVIEW, which fails the job. - Trusted broker (Python):
- The candidate workflow runs in a child process and can only request privileged actions over pipes.
- The parent broker keeps the Ed25519 signing key in memory and never exposes it. The child only ever receives the public verification key. The broker re-verifies every privileged request against that public key, runs the trusted guard and deployment adapter, and is the only writer of the audit trace.
- Protocol violations are recorded as evidence.
- Baseline-aware static analysis (
graph_diff):- Compares base vs head and reports topology changes and untrusted → privileged reachability.
- Flags tamper patterns.
- Holds changes to tests, CI, policy or trust roots as NEEDS_REVIEW.
- Seeded benchmark: 8 unsafe variants, 6 benign variants and 2 evidence-tampering variants, run in CI on every pipeline.
Results
On our internal, seeded benchmark (not an industry benchmark). It is reproducible with relayguard bench and runs as a CI job on every pipeline:
| Metric | Result |
|---|---|
| Unsafe variants that slipped through | 0 / 8 |
| Benign variants falsely blocked | 0 / 6 |
| Legitimate deploys succeeding (one per benign variant) | 6 / 6 |
| Evidence-tampering variants blocked | 2 / 2 |
| Tests passing | 64 |
| Time per variant | ~0.33 s |
Challenges we ran into
- "AI can't grade its own homework." Early versions let the code under review write its own trace. We moved all privileged work and trace writing into a parent broker the child process can never touch.
- The gate judging its own MR. If a merge request can edit the auditor, it can pass itself. We now install the gate from the base revision, and treat changes to tests/CI/policy as needing human review. Fully preventing an MR from deleting the CI job itself needs GitLab-side enforcement (see limitations).
- Duo flow schema limits. Custom flows reject some fields, such as top-level names and response schemas. We kept agent outputs plain text and enforced structure in CI instead.
- Least privilege across agents. Only the Analyst receives untrusted MR text as input, and only the Fixer has code-write tools. This limits how far a prompt injection can travel.
Accomplishments that we're proud of
- A real, reproducible GitLab demo: an unsafe MR blocked by CI, and a corrective MR from an agent, with live pipelines you can open.
- Zero unsafe variants passed and zero false positives on our benchmark, while legitimate signed deploys keep working.
- A clear supervision model: AI investigates. CI decides. Humans approve.
What we learned
- The agent's verdict should be evidence, not authority. Deterministic CI gates should hold the final say.
- Trust boundaries matter more between agents than inside them. Most risk sits in the handoffs.
- Small diffs can carry the biggest authorization changes. Reviewing paths beats reviewing lines.
What's next for RelayGuard
- Support more privileged tools beyond deploy (secrets, package publishing, infrastructure changes).
- Package the gate as a reusable CI/CD component that any GitLab project can include.
- Generate attack scenarios from the project's own workflow graph instead of a fixed catalog.
- Run it on real open-source agent workflows and publish the results.
Known limitations
- The deployment adapter is simulated.
- The benchmark is seeded and internal.
- Compromise of the parent process at the OS level is out of scope.
- The pipeline definition lives in the repo, so an MR could edit
.gitlab-ci.ymlto remove the audit job. In production this should be locked down with GitLab controls: a protected default branch, "Pipelines must succeed", and a pipeline execution / compliance policy that injectsrelayguard-auditfrom outside the project. The demo project does not configure these. - Planner, Fixer and Auditor can still read repository files, which are candidate content. Only the MR description and trigger text are isolated to the Analyst.

Log in or sign up for Devpost to join the conversation.