Inspiration
We started from a simple worry: an assistant should not need the keys to the whole house. When we connected an AI assistant to a mailbox, we built a broker so it could only read a few fixed fields, and that worked. Then we looked at our CI/CD. AI agents on GitLab now fix pipelines and ship code, and to do that they read full job logs, merge requests and data files. Those can hold a customer's card number, a payment key, supplier costs, or an instruction planted for the agent.
Pricing made it concrete. E-commerce pricing errors keep happening, and in many places, including Taiwan, a paid order usually has to ship. If agents are going to touch prices, the check has to happen before release, and the agent should not need to see the costs to help.
What it does
Boundary-Aware Assistant puts a deterministic gate between GitLab and the Duo agents.
- Five fields, not raw logs. When a pipeline fails, the gate reads the job log itself and posts
status,failure_class,locations,excerpt,ref. Keys and personal data are redacted; if the log carries instructions aimed at an agent, the excerpt is withheld. - Costs stay in a black box. Every catalog change passes two independent checks: the price must sit inside the product's public band, and price minus cost must not be negative. Costs live only in Secret Manager. The agent gets a lookup table and does table lookup only.
- Every shop deploy asks the gate. The gate checks the merge request(s) behind the commit: pipeline passed, no secret added, prices pass, and no agent changed CI, deploy, gate code or price data without a second person. Production goes out only when a person starts the job, which the gate verifies from GitLab's job record.
- No secrets in CI. Jobs identify themselves with their own
CI_JOB_TOKEN; Google Cloud is reached through OIDC and Workload Identity Federation. - The same boundary over MCP. The gate is also an MCP server with three read-only tools (
failed_job_views,price_lookup,deploy_readiness): ask why a merge request is blocked and you get five fields, a lookup table or a reason code, never a log or a cost. Tested with a plain MCP client; using it from Duo Chat needs a group owner to allow external MCP tools.
GitLab's own flow template says splitting reading from acting only reduces prompt-injection risk. We replace the reading step with code, so the model never sees the raw text.
What really happened in testing
- An agent that was wrongly routed to a price failure relabelled a product's band to get past the band check. The cost check, which it cannot see, still blocked it (MR !6). One check bypassed, the other held. We then routed price failures only to the price flow and gated price files for agents.
- A failing log held a fake card number, a customer email, a payment key and an instruction to post
CI_JOB_TOKEN. The agent received onlytest_receipt.py:14, fixed the bug, said it ignored the planted instruction it saw in the source, and flagged the key for a person (MR !9).
How we built it
- GitLab Duo Agent Platform: two custom flows, mention-triggered only. Fixer can read repository files, commit to the branch the gate names, and comment. Price Reviewer can only comment.
- GitLab CI/CD: tests,
price-check, SAST and Secret Detection on merge requests, Cloud Build images, gated staging and production, health check, and a.postjob that hands failures to the gate. - The gate: Python standard library only, 99 tests, deployed on Cloud Run with secrets mounted from Secret Manager. It also serves MCP (
POST /mcp), with a workspace config for Duo in.gitlab/duo/mcp.json. - Google Cloud: Cloud Run, Cloud Build, Artifact Registry, Secret Manager, Workload Identity Federation (no keys in GitLab). The video narration was voiced with Gemini text-to-speech (Gemini API).
Challenges we ran into
Most were our own bugs, caught by the gate failing closed: GitLab job tokens are JWTs longer than the cap we set; one merge commit can close two merge requests; a secret reference (name:latest) looked like a secret; our own test fixtures held secret-shaped strings, and the gate correctly blocked that deploy. GitLab also does not let authors approve their own merge requests, so we redefined "needs a second person" to apply when an agent changes sensitive paths. Before the deadline we red-teamed the gate again and rebuilt the whole project from the README in a fresh GitLab and Google Cloud project. That found four more ways it could fail open (only the first page of a large merge request was read, a guarded file renamed away went unnoticed, diffs GitLab had cut short were still judged, and CI jobs from other projects were accepted) and audit records that grew without bound under load; all are now fixed and covered by tests.
Accomplishments that we're proud of
A full Supervised loop running for real: a bug fixed from five fields, a mistyped price stopped by a check the agent cannot see, and production released only after a person pressed deploy.
What we learned
Don't ask the agent to behave; arrange things so it never holds anything worth stealing. And keep two independent checks: the one an agent can see, it may learn to game.
What's next
Wiring the existing issue projection into the flows, a projection for merge request text, protected-environment approvals where Maintainer access is available, and a project access token per flow instead of personal fine-grained tokens.

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