Inspiration

A leaked production key is usually fixed by a person, days later, and deleting the line does not make the key safe again. Most "AI DevSecOps" demos answer this by letting a model decide. But a confident model that skipped a check looks exactly like one that ran it. We wanted the opposite: an agentic flow with no person in the loop, where no model has authority to sign, release or merge anything.

What it does

AegisMind is a hands-off DevSecOps control plane built on the GitLab Duo Agent Platform.

  1. A hotfix hardcodes the ledger key on main. Sentinel, our scanner, turns the pipeline red.
  2. One issue comment mentions the AegisMind Hands-off flow. Nobody acts after that.
  3. A conductor agent delegates to a planner, an analyst and a clerk. The analyst runs a deterministic policy kernel: it scans, applies the single repair it is allowed to make, runs the service's tests, scans again and decides. The model only carries the result.
  4. The clerk posts the kernel's own note, word for word, and opens a one-line merge request.
  5. A guard job reruns the kernel on main and accepts only the exact tree the kernel approved.
  6. GitLab does not let agents merge, and our agent did not try to get around it. A lander with no judgment of its own merges only what the guard accepted. The issue closes itself and main is green again.

Every decision is sealed, replayed from its inputs and signed keyless with Sigstore by the pipeline, with a PDF record an auditor can file. A repaired secret is still treated as leaked: the note says to rotate it, by fingerprint, never by value.

Live run (issue #6): from the mention to a guard-accepted merge request on a green pipeline took

$$t_{\text{mention} \rightarrow \text{ready}} = 2\ \text{min}\ 34\ \text{s}$$

with zero human approvals.

How we built it

  • GitLab Duo Agent Platform: a custom flow (a supervisor with three subagents), registered through the AI Catalog GraphQL API by python3 -m aegis.duo enable and triggered by a mention.
  • Policy kernel in standard-library Python: Plan, Secure, Verify, Create, Package, Configure, Monitor, Govern and Release, all recorded in one sealed audit.json.
  • Sentinel twice: a Python reference and a Rust engine built from the same rule catalog, held to identical findings by a parity test. GitLab Secret Detection runs beside it under the same policy.
  • GitLab CI/CD as a DAG: replay of every audit, keyless signing through GitLab's OIDC token, the guard job on agent merge requests, a CycloneDX SBOM, a CI/CD catalog component, and GitLab Pages for a live dashboard and decision ledger.
  • A red-team suite, run on every push, that attacks the kernel itself.

Challenges we ran into

  • GitLab does not let a flow merge. The flow's token was refused through both REST and GraphQL. Our analyst reported the refusal and stopped, as instructed. We kept that boundary and moved the merge into a lander that can only merge what the guard already accepted.
  • GitLab's flow compiler is stricter than its docs. It refused our flow three times, and Duo's file reader refuses gitignored paths. Each lesson became a rule in aegis.flowcheck, so our checker now fails before GitLab does.
  • A secret only in history. A full-history scan kept reporting the repaired key and would have left main red forever. Now a secret still in the tree blocks, and one only in history is flagged for rotation.
  • Our own red team found two real flaws before they shipped: a symbolic link that pulled a host file into the package, and numbered credential names that evaded detection.

Accomplishments that we're proud of

  • The full hands-off loop ran live on GitLab: a red main, one mention, a kernel decision, a guard-checked merge with no person, and a green main again.
  • A prompt injection was held live. The issue and the code both ordered a signed release; the kernel said quarantine and no merge request was opened.
  • In one live session, the publishing agent refused to publish a decision it could not verify.
  • Measured, not estimated: the Rust gate scans $17.4\times$ faster than the Python reference with $89.5\,\%$ less CPU. Merge request pipelines went from 213 to 157 runner seconds, which is

$$\frac{213 - 157}{213} \approx 26\,\%$$

less runner time. 230 Python tests and 17 Rust tests run on every push.

What we learned

Agentic does not have to mean unaccountable. Once the model only carries the result, every decision can be recomputed from its inputs, and the flow can be trusted without a person in the loop. The safest agent is the one that stops when it cannot prove what it is about to do.

What's next

  • Run the lander on its pipeline schedule, as soon as the project allows pipeline variables. Its first pass in the live run was started by a command.
  • More allowlisted repairs, each with one scenario that proves it and one that proves its boundary.
  • The Cloud Run deployment code, with keyless deploys and automatic rollback, is written and tested but not deployed. We did not have a billing-enabled Google Cloud project.

Built With

Share this project:

Updates

Submission history