-
-
Showcase
-
Human approval points
-
Builder agent MR, human merged
-
Fix Pipeline agent fix, human merged
-
Brief agent review note
-
Developer agent docs MR, merged
-
SAST findings in Vulnerability Report
-
Security agent fix proposal (closed, not merged)
-
False-positive analysis
-
Monitoring tool proposal
-
Duo agent sessions
-
Seal dry-run passes 14 checks
-
Pipeline history
-
Release v0.6.0
-
Attack Day: 22 of 23 caught
🔏 Notary: Make AI Agents Prove Their Claims
AI agents can say 'my change is tested and safe.' Notary makes them prove it. CI runs the proofs. A signed receipt records the result. A human approves. An independent verifier checks 14 conditions offline and fails closed when anything is missing or changed.
Built for the GitLab Transcend 'Life After Code' hackathon on GitLab Duo Agent Platform and GitLab CI/CD.
Autonomy level: Assisted. Agents propose. A human approves each consequential step.
🔗 Links
⚡ In 30 seconds
Agent claims are only text. Notary makes sure text never decides anything:
- CI runs the proofs and seals the result in a signed receipt (real ed25519 SSH signature).
- Receipts are chained in a tamper-evident ledger.
- A human signs an approval over the exact commit, receipt hash and digest.
- An independent verifier runs 14 offline checks.
- A flipped byte, an expired approval, a bot approving itself or an unknown signer stops the release with a stable code like
N-SBOM-MISMATCH.
🧑⚖️ For Judges: 3 minutes
Need: Python 3.11+ and OpenSSH ssh-keygen (Windows or Linux). The core uses only the standard library. Tools and tests need PyYAML.
git clone https://gitlab.com/gitlab-ai-hackathon/transcend-october-2026/43637140/showcase.git
cd showcase && pip install pytest pyyaml
| Minute | Command | Expected |
|---|---|---|
| 1 | python tools/gate.py |
14/14 checks PASS, overall PASS |
| 2 | python tools/gate.py --attack |
overall FAIL with N-SBOM-MISMATCH, N-CHECKSUM-MISMATCH |
| 3 | python -m pytest tests/test_gate_approval.py -q |
25 cases: 1 honest passes, the rest fail with named codes |
| Optional | python -m pytest -q |
all pass (367 passed, 1 skipped in main pipeline 2993875825) |
The same gate runs in CI on every push (notary-gate).
💡 Inspiration
What if an agent's 'it's tested and safe' is wrong? It could be a mistake, a hidden instruction in an issue or file, or a bot approving its own work. Our answer: the deciding evidence must never be something an agent writes. The pipeline produces it and a human signs it, like a notary who checks the document, not the person's story.
🧩 What it does
| Layer | What happens |
|---|---|
| Claims | Agent writes a machine-readable claim.json. Only structured fields are checked. |
| Proofs | CI re-runs proofs itself (fail-first, tamper, drift, risk). |
| Receipt | Canonical JSON with an SSHSIG (ed25519) signature. Any edit breaks it. |
| Ledger | Hash-chained receipts with a signed tree head. |
| Human approval | A person signs over the exact commit, receipt hash and digest. |
| Verifier of record | 14 offline checks. The judging code never comes from the change. |
| Fail closed | Missing or contradictory evidence stops the pipeline with a named code. |
| Honest limits | Every receipt lists what it does NOT prove. If emptied, check 14 fails. |
The flow
Issue → Planner → Builder → MR + claim file
→ MR pipeline: pytest · claim-check · mr-check · notary-gate · SAST · secrets
FAIL → Fix Pipeline flow proposes a fix → human reviews and merges
PASS → Brief flow posts a review note → human decides
→ SAST finding → Duo resolve / false-positive flows propose → human reviews, corrects, merges
→ Monitoring: read-only pipeline-health check → human reads the report
→ main push: notary-draft (unsigned receipt + hash)
→ Human signs approval (tools/approve.py → approve/* tag)
→ notary-seal: re-check → rebuild → sign → 14-check verify
The 14 verifier checks
| # | Check | Example code |
|---|---|---|
| 1 | Receipt signature valid | N-SIG-INVALID |
| 2 | Claim matches receipt and schema | N-CLAIM-MISMATCH, N-CLAIM-SCHEMA |
| 3 | Top-level verdict and reasons | verdict not PASS |
| 4 | Signer key in allowed_signers |
N-APPROVAL-KEY |
| 5 | Ledger entry belongs to release | N-LEDGER-CHAIN |
| 6 | Ledger chain and tree head intact | N-LEDGER-TRUNCATED |
| 7 | Approval present, listed key | N-APPROVAL-MISSING, N-APPROVAL-SIG |
| 8 | Commit, receipt hash, expiry, nonce | N-APPROVAL-COMMIT, -RECEIPT, -EXPIRED, -REUSED |
| 9 | Author is not approver | N-SOD |
| 10 | SBOM matches receipt | N-SBOM-MISMATCH |
| 11 | Checksums match | N-CHECKSUM-MISMATCH |
| 12 | Deploy identity | N-DEPLOY-IDENTITY |
| 13 | No CI or policy drift | N-CI-DRIFT |
| 14 | not_proven list not empty |
N-PROOF-EMPTY |
The separate human-approval gate has 10 checks. It rejects an unlisted signer, a wrong commit, a missing ledger entry, a digest mismatch, an expired window, a replayed nonce, an author approving their own change, a bot approver, a scope mismatch and a kill-switch freeze. 24 failing cases are tested.
🧑💼 Human approval points (Assisted)
Agents propose. A human approves each consequential step. No flow can merge, sign or approve, and tools/flow_lint.py enforces that in CI.
| Step | Agent proposes | Human decides | Evidence |
|---|---|---|---|
| Plan | Planner posts a plan note ('plan only; no work started') | Proceed or not | Issue #2, session 9951002 |
| Build | Builder opens a small MR with a claim file | Reviews and merges | MR !1 |
| Fix a red pipeline | Fix Pipeline flow opens a fix MR | Reviews and merges | MR !16, session 10065680 |
| Review | Brief posts one note: changes, verdict, risk, not proven | Decides | MR !10, session 10015039 |
| Security fix | Duo SAST resolve flow opens a fix MR for an SSRF finding | Reviewed it, corrected it (the agent added a third-party dependency; we kept its scheme allow-list and stayed on the standard library), then merged | MR !26, session 10117128 |
| Security triage | Duo false-positive flow analyses an XML finding | Reads the analysis (78% false positive) and decides | Session 10117131 |
| Monitoring | Developer agent opens a read-only pipeline-health tool | Reviews and merges | MR !27, session 10117134 |
| Docs | Duo developer agent drafts a docs-only MR | Marks it ready and merges | MR !22 |
| Release seal | notary-draft builds an unsigned receipt; notary-seal-dryrun rehearses the seal |
Signs the approval and pushes a protected approve/* tag |
Rehearsed in CI. See Honest limits. |
🔄 Post-code lifecycle coverage
| Stage | Agent or automation | Status |
|---|---|---|
| Plan | Planner flow | ✅ Ran |
| Code | Builder and Developer flows | ✅ Ran (MR !1, MR !22) |
| Test and pipeline repair | Fix Pipeline flow | ✅ Ran (MR !16) |
| Review | Brief flow | ✅ Ran (MR !10) |
| Security scanning | GitLab SAST and Secret Detection on every pipeline | ✅ Runs |
| Security remediation | Duo resolve and false-positive flows | ✅ Fix merged (MR !26); triage analysis delivered |
| Compliance | Receipts, ledger, 14-check verifier, approval gate | ✅ Dry-run passes; real seal not run |
| Monitoring | Developer agent's read-only pipeline-health tool | ✅ Merged (MR !27), not wired into CI |
| Deployment | Not built (target is local-canary) |
❌ |
🛠️ How we built it
- Python: the core is standard library only. PyYAML for the flow validator and tests. pytest.
- Signing: ed25519 SSHSIG via
ssh-keygen -Y sign, with a pure-Python verifier pinned to RFC 8032 vectors. - Evidence: canonical JSON, CycloneDX SBOM, SHA256SUMS,
allowed_signers. - CI: GitLab CI/CD on shared runners,
python:3.12, SAST and Secret Detection. - Agents: GitLab Duo Agent Platform custom flows (Planner, Builder, Fixer, Brief) plus the built-in Fix Pipeline, Developer, SAST resolve and SAST false-positive flows.
Repository map
showcase/
├── notary/ # core: canon, claim, policy, proofrunner, failfirst, tamper, drift, risk,
│ # receipt, signing, sshsig, ledger, verify, approval, gate_approval
├── tools/ # gate, approve, seal, attack_day, blind_eval, claim_check, mr_check, flow_lint, monitor
├── flows/ # planner, builder, fixer, brief
├── evals/blind/ # 20 attack + 20 honest fixtures
├── tests/ · docs/ · .gitlab-ci.yml · .gitlab/duo/triggers.yml
GitLab Duo Agent Platform flows
| Flow | Trigger | Observed |
|---|---|---|
| Planner | Work item created | ✅ Plan, risk preview, required proofs. Session 9951002, issue #2. |
| Builder | Mention | ✅ Refused the vague issue #2 (session 9954172). Opened MR !1 for issue #3, pipeline 2981713596 green, human merged. |
| Fixer / Fix Pipeline | Pipeline failed | ✅ MR !9: pipeline 2986227230 failed, 2986228926 passed. Fix Pipeline session 10065680 produced MR !16, merged by a human. |
| Brief | Mention / assign / reviewer | ✅ Review note with a 'not proven' list and no verdict. MR !10, session 10015039. |
| Developer | Goal from chat | ✅ Drafted docs MR !22 (session 10081895). Wrote the monitoring tool, MR !27 (session 10117134). Both merged by a human. |
| SAST resolve | Vulnerability Report ID | ✅ Session 10117128 opened MR !26 for an SSRF finding. Merged after human correction. |
| SAST false-positive | Vulnerability Report ID | ✅ Session 10117131 classified an XML finding as 78% likely false positive and gave suppression guidance. |
Safety design: prompts say issue and file text is data, not instructions. Toolsets are minimal. No flow can sign, approve, merge or start another flow.
CI jobs
pytest · claim-check and mr-check (MRs) · notary-gate (gate.py --attack) · notary-draft (every main push) · notary-seal-dryrun (MRs, throw-away keys) · notary-seal (protected approve/* tag) · SAST · Secret Detection
📊 Evidence
| Result | Number | Where |
|---|---|---|
| Test suite | 367 passed, 1 skipped | Main pipeline 2993875825 |
| Attack Day (hostile edits) | 22 of 23 caught | docs/ATTACK_DAY.md |
| Attack Day scenarios | 8 of 8 matched | docs/ATTACK_DAY.md |
| Semi-blind eval, attacks | 18 of 20 caught (90%) | docs/EVAL_PLAN.md |
| Semi-blind eval, honest changes | 20 of 20 passed (0% false blocks) | docs/EVAL_PLAN.md |
| CI seal dry-run | 14 checks + approval gate passed | Pipeline 2991245755, job 17480627740 |
| Secret detection | No leaks found | Pipeline 2986721897 |
| SAST | 3 medium findings in the Vulnerability Report; SSRF fix merged (MR !26); XML findings mitigated by a DOCTYPE/ENTITY pre-check (MR !15) | IDs 419723406, 419723407, 420973893 |
Semi-blind evaluation: an outside AI saw only the README and threat model and wrote 20 attack and 20 honest changes. We built the fixtures from its text. One escape (attack_001) is the documented M02 gap. The other (attack_007, prompt injection) cannot affect verdicts, because code sets verdicts, never text.
🧗 Challenges we ran into
| Challenge | What we did |
|---|---|
ssh-keygen -Y verify hung on Windows |
Wrote a pure-Python SSHSIG verifier |
Pipelines created 0 jobs (wrong Secret-Detection template name, workflow:rules) |
Fixed in MR !7 |
| Pasted flows rejected by the flow schema; assign trigger unavailable | Added a flow_lint rule, used mention triggers |
| Agents that guess | The Builder refused a vague issue. We kept that as a guardrail. |
The SAST agent's first SSRF fix swapped urllib for a third-party requests import, which would break our standard-library-only verifier |
The human reviewer kept the agent's scheme allow-list and restored urllib, then merged. This is the Assisted pattern working. |
| The monitoring agent could not run pytest in its sandbox | It said so in its MR; our CI ran the full suite before merge |
| A seal dry-run failed on an MR because it judges the tip of main, which had CI and policy drift | Retried after main was clean; the drift check worked as designed |
| Verifier gap M02 (UNKNOWN sub-proof inside an overall PASS) | Documented, not quietly patched |
🏆 Accomplishments
- 🛡️ 22 of 23 hostile edits and 8 of 8 scenarios caught with named codes. We published the miss.
- 🧪 90% attack detection with 0% false blocks.
- 🔐 Real cryptography, no mocks.
- 🤖 Duo flows cover plan, build, fix, review, security remediation and monitoring, and every consequential step has a human approval point.
- 👀 The human review caught a real flaw in an agent's security fix before it reached
main. - 📚 Every claim points to a pipeline, job, session or MR ID.
⚠️ Honest limits
- Final seal:
notary-sealneeds a protectedapprove/*tag. The account used for this project has the Developer role, so we rehearsed the full path withnotary-seal-dryrun(14 checks and the approval gate passed) and did not run a real seal. Releases v0.1.0 to v0.6.0 are manual and carry no sealed receipt. - Security scanner: SAST may keep listing the
urlopenline because it still calls it, with the scheme check in front. We did not record a re-scan result. - Monitoring: it observes pipeline history only and is not wired into CI. There is no deployed service to monitor.
- Keys: signing and approval keys are software keys. Hardware-key touch is not verifiable offline.
- Deploy: the target is
local-canary. There is no container image or cloud deployment. - Verifier: the known M02 gap is open.
- Not proven by design: code correctness beyond the listed proofs, that a human read the change, platform honesty, stolen credentials, and free prose.
📘 What we learned
- Agent text is not evidence. Only what the pipeline reproduces counts.
- Fail closed is a design rule.
- Attack your own work. Hostile tests found gaps normal tests missed.
- Publish your misses. M02 and the rehearsed-only seal earn more trust than hiding them.
- Assisted autonomy works. Agents handle plans, drafts, fixes, security proposals and briefs. Humans keep the decisions, and sometimes correct the agent.
🚀 What's next
- Run a real seal with a protected
approve/*tag and publish a Release carrying the sealed receipt, SBOM and checksums. - Close the M02 gap.
- Schedule the pipeline-health check and add a deployed target to monitor.
- Have CI re-validate agent claim files against the schema.
- Wire the typed receipt-hash confirmation into
tools/approve.py. - Hardware-key approvals and cosign keyless signing.
- Container digests, scan results in receipts, and a real deploy target with canary and rollback.
🧰 Built with
Python · pytest · PyYAML · GitLab CI/CD · GitLab Duo Agent Platform · GitLab SAST · GitLab Secret Detection · ed25519 · SSHSIG · OpenSSH · CycloneDX SBOM
📄 License
MIT. See LICENSE in the repository.
Built With
- cyclonedx
- ed25519
- gitlab
- gitlab-ci/cd
- gitlab-duo-agent-platform
- gitlab-sast
- openssh
- pytest
- python
- pyyaml
- sshsig
Log in or sign up for Devpost to join the conversation.