Chapter 33Lesson 05~450 minutes

Checkpoint Lab — GitLab Duo and Agentic DevSecOps: AI Features, Security Remediation, Governance, and Usage Controls

Complete a governance-first agentic DevSecOps checkpoint using synthetic code, tool policies, proposals, audit artifacts, and usage data; reject unsafe work and produce an evidence-backed approval record.

CheckpointApproval RecordSecurity ReviewAudit EvidenceCapstone

Learning objectives

  • Create a complete synthetic governance package for one AI-assisted security-remediation workflow.
  • Predict and enforce tool decisions before any simulated mutation occurs, including at least one explicit deny.
  • Independently test two remediation proposals and reject the plausible but unsafe candidate with reproducible evidence.
  • Produce an approval/audit record that binds data classification, tool decisions, proposal identity, reviewer decision, and zero-credit fixture assumptions.
  • Carry the resulting governance pattern into Chapter 34’s secure production GitLab platform capstone.
Availability and governance baseline — verified 2026-08-22 against GitLab 19.3. This checkpoint is mandatory and fully offline. It uses only Python standard-library fixtures and consumes zero GitLab Credits. Optional live Duo/Agent Platform use is unnecessary and should not be enabled for the checkpoint. If you later map the workflow to a real namespace, re-verify the exact feature status, credits, data/privacy settings, tool governance, runner model, and audit availability first.

1. Mission and acceptance criteria

You are the platform reviewer for PROJECT_X. A synthetic AI session proposes a remediation for a template path-containment flaw. You must decide which data the AI was allowed to see, which tools it may invoke, which proposed patch is safe, and what evidence is required before a human authorizes the simulated write.

CHECKPOINT ACCEPTANCE
[ ] original fixtures preserved
[ ] data classification documented before prompt/tool analysis
[ ] tool decisions predicted before evaluator runs
[ ] at least one write requires approval
[ ] at least one destructive/external operation is denied
[ ] safe and unsafe proposals tested independently
[ ] unsafe plausible proposal rejected with evidence
[ ] approved proposal hash recorded
[ ] audit/session/usage record contains no real secrets or private data
[ ] credits assumption explicitly says fixture / zero live usage
[ ] cleanup is reviewed before deletion

2. Build the checkpoint package

from pathlib import Path
import json, textwrap, hashlib
root=Path("gitlab-ch33-checkpoint")
orig=root/"original"; work=root/"working"
orig.mkdir(parents=True,exist_ok=True); work.mkdir(parents=True,exist_ok=True)

(orig/"context.json").write_text(json.dumps({
  "session_id":"SESSION_FAKE_CAPSTONE_33",
  "project":"PROJECT_X",
  "actor":"USER_A",
  "task":"Fix synthetic path traversal in template resolver",
  "data_classes":["synthetic_source","synthetic_vulnerability"],
  "prohibited_data":["real_secrets","customer_data","production_logs","private_personal_data"],
  "offering_baseline":"GitLab.com fixture",
  "version_baseline":"19.3",
  "credits_consumed":0,
  "live_ai_called":False
},indent=2))

(orig/"policy.json").write_text(json.dumps({
  "read_repository":"always_allow",
  "read_vulnerability":"always_allow",
  "write_feature_branch":"always_ask",
  "create_merge_request":"always_ask",
  "edit_ci_policy":"always_deny",
  "delete_project":"always_deny",
  "external_mcp":"always_deny"
},indent=2))

(orig/"requests.json").write_text(json.dumps([
  {"tool":"read_repository","intent":"inspect resolver"},
  {"tool":"read_vulnerability","intent":"inspect synthetic finding"},
  {"tool":"write_feature_branch","intent":"apply candidate patch"},
  {"tool":"edit_ci_policy","intent":"disable failing security job"},
  {"tool":"delete_project","intent":"reset after test failure"}
],indent=2))

safe='''from pathlib import Path\n\ndef template_path(base: Path, name: str) -> Path:\n    base=base.resolve()\n    candidate=(base/name).resolve()\n    try:\n        candidate.relative_to(base)\n    except ValueError as exc:\n        raise ValueError("outside template root") from exc\n    return candidate\n'''
unsafe='''from pathlib import Path\n\ndef template_path(base: Path, name: str) -> Path:\n    base=base.resolve()\n    candidate=(base/name).resolve()\n    if not str(candidate).startswith(str(base)):\n        raise ValueError("outside template root")\n    return candidate\n'''
(orig/"proposal_A.py").write_text(safe)
(orig/"proposal_B.py").write_text(unsafe)

for p in orig.iterdir():
    if p.is_file(): (work/p.name).write_bytes(p.read_bytes())

manifest={p.name:hashlib.sha256(p.read_bytes()).hexdigest() for p in orig.iterdir() if p.is_file()}
(root/"original_sha256.json").write_text(json.dumps(manifest,indent=2))
print(root.resolve())

3. Predict governance outcomes before running the evaluator

Write your prediction first. The task requires reading synthetic source/finding, so those reads may proceed. Applying a patch is a repository mutation and should pause for human approval. Disabling a security job is not needed to fix the vulnerability and weakens verification, so deny it. Project deletion is unrelated and destructive, so deny it.

{
  "read_repository": "ALLOW",
  "read_vulnerability": "ALLOW",
  "write_feature_branch": "ASK",
  "edit_ci_policy": "DENY",
  "delete_project": "DENY"
}

Your prediction is part of the evidence. If the evaluator later produces a different result, investigate the policy rather than silently changing your answer to match the tool.

4. Evaluate the tool policy and record the denied actions

from pathlib import Path
import json
root=Path("gitlab-ch33-checkpoint")/"working"
policy=json.loads((root/"policy.json").read_text())
requests=json.loads((root/"requests.json").read_text())
map_decision={"always_allow":"ALLOW","always_ask":"ASK","always_deny":"DENY"}
results=[]
for r in requests:
    mode=policy.get(r["tool"],"always_deny")
    row={**r,"mode":mode,"decision":map_decision[mode]}
    results.append(row)
    print(f"{r['tool']:24} {row['decision']:5} {r['intent']}")
(root/"tool_decisions.json").write_text(json.dumps(results,indent=2))

Expected: the two reads are ALLOW, feature-branch write is ASK, and CI-policy/project deletion are DENY. Explain why the denied CI-policy action is security-relevant: an agent must not “fix” a remediation by removing the evidence gate that detects failure.

5. Independently test both proposals

from pathlib import Path
import tempfile, importlib.util, json
root=Path("gitlab-ch33-checkpoint")/"working"

def load(path):
    spec=importlib.util.spec_from_file_location(path.stem,path)
    mod=importlib.util.module_from_spec(spec); spec.loader.exec_module(mod); return mod

def run(path):
    m=load(path)
    evidence=[]
    with tempfile.TemporaryDirectory() as td:
        r=Path(td); base=r/"templates"; base.mkdir(); (r/"templates-backup").mkdir()
        expected=(base/"ok.html").resolve()
        evidence.append(["valid_path",m.template_path(base,"ok.html")==expected])
        for label,bad in [("parent_escape","../secret.txt"),("sibling_prefix","../templates-backup/secret.txt")]:
            try: m.template_path(base,bad)
            except ValueError: evidence.append([label,True])
            else: evidence.append([label,False])
    return evidence

report={}
for name in ["proposal_A.py","proposal_B.py"]:
    ev=run(root/name); passed=all(v for _,v in ev)
    report[name]={"passed":passed,"checks":ev}
    print(name,"PASS" if passed else "FAIL",ev)
(root/"verification.json").write_text(json.dumps(report,indent=2))

The expected result is Proposal A PASS and Proposal B FAIL on sibling_prefix. This is the required unsafe hallucinated/over-broad remediation rejection: Proposal B has a plausible narrative but fails the security invariant.

6. Conduct the human security review

Tests are necessary but not sufficient. Review Proposal A for scope: it changes only path-containment logic, adds no dependency, sends no network data, does not change CI/security policy, and requires no broader role. For a real project, add full regression CI and scanner verification before merge.

Review dimension Proposal A Proposal B
Security invariant Structural ancestry check String-prefix approximation
Focused tests Pass Fails sibling-prefix escape
Unrelated permissions/settings None None in fixture
Decision Candidate for approval Reject
Reason Evidence supports containment Counterexample disproves containment
Reviewer obligation: if the AI proposal cannot be explained in terms of the invariant it enforces, do not approve merely because tests happen to be green. Add targeted tests or seek specialist review.

7. Produce the approval and audit record

from pathlib import Path
import hashlib, json, datetime
root=Path("gitlab-ch33-checkpoint")/"working"
verification=json.loads((root/"verification.json").read_text())
tools=json.loads((root/"tool_decisions.json").read_text())
chosen=root/"proposal_A.py"
record={
  "session_id":"SESSION_FAKE_CAPSTONE_33",
  "time":"2026-08-22T02:30:00Z",
  "reviewer":"REVIEWER_A",
  "data_classification":"synthetic-only",
  "model_provider":"FIXTURE_NONE",
  "live_ai_called":False,
  "gitlab_credits_consumed":0,
  "proposal_sha256":hashlib.sha256(chosen.read_bytes()).hexdigest(),
  "verification":verification,
  "tool_decisions":tools,
  "approved":"proposal_A.py",
  "approved_scope":"simulate write to disposable feature branch only",
  "rejected":"proposal_B.py",
  "denied_actions":["edit_ci_policy","delete_project","external_mcp"],
  "next_required_real_controls":["MR diff review","full CI","security scan","protected branch approvals"]
}
(root/"approval_audit_record.json").write_text(json.dumps(record,indent=2))
print(json.dumps(record,indent=2))

The record is intentionally explicit that no live AI was called and zero credits were consumed. Never represent fixture evidence as a real GitLab AI audit event. In a real eligible environment, link the product’s session/audit identifiers while preserving the same decision fields.

8. Predict and verify the only approved state change

Before simulating the write, predict two things:

  1. The approved candidate file will appear only in a disposable feature-branch directory; the original fixture remains unchanged.
  2. No CI-policy or project-deletion artifact will be created because those tools are denied.
from pathlib import Path
import shutil, hashlib, json
root=Path("gitlab-ch33-checkpoint")
orig=root/"original"; work=root/"working"; branch=root/"feature-branch-sim"
branch.mkdir(exist_ok=True)
shutil.copy2(work/"proposal_A.py",branch/"template_loader.py")

assert not (branch/"ci_policy_disabled.flag").exists()
assert not (root/"project_deleted.flag").exists()
manifest=json.loads((root/"original_sha256.json").read_text())
for name,expected in manifest.items():
    actual=hashlib.sha256((orig/name).read_bytes()).hexdigest()
    assert actual==expected, f"original changed: {name}"
print("VERIFIED: approved branch simulation created; originals preserved; denied effects absent")

9. Review the audit record for privacy and completeness

Check that the record contains stable synthetic identifiers, proposal hash, tool decisions, approval scope, rejected proposal, and next verification gates. Check that it does not contain a real token, email address, private repository path, customer text, vulnerability payload, production log, or live prompt.

from pathlib import Path
import re
p=Path("gitlab-ch33-checkpoint/working/approval_audit_record.json")
text=p.read_text()
patterns={
  "gitlab_token": r"g[l]pat-[A-Za-z0-9_-]{10,}",
  "private_key": r"BEGIN (?:RSA |OPENSSH )?PRIVATE KEY",
  "email": r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}"
}
for name,pat in patterns.items():
    print(name,"FOUND" if re.search(pat,text) else "not found")

A simple regex is not a complete data-loss-prevention system. It is a checkpoint to reinforce the habit: auditability should not become a reason to duplicate sensitive content.

10. Production handoff into Chapter 34

Your Chapter 33 output can be summarized as a platform control contract:

AI GOVERNANCE HANDOFF
Scope: PROJECT_X synthetic remediation
Data: synthetic source + synthetic finding only
Product assumptions: GitLab 19.3 reference; fixture path; no live AI/credits
Authority:
  reads -> allowed for approved data
  feature-branch write -> human approval
  CI policy / delete / external MCP -> denied
Verification:
  Proposal A passes containment tests
  Proposal B rejected by sibling-prefix counterexample
  real deployment would still require MR, full CI, scanners, protected rules
Audit:
  proposal hash + reviewer + decisions + denied actions recorded
Fallback:
  core Git/CI/security workflow functions without GitLab Duo
Risk owner: platform/security governance

Chapter 34 will combine this AI control contract with the rest of the course: namespaces, access, merge governance, CI/CD, runners, releases, registries, security policies, Kubernetes integration, APIs, backup/recovery, and operational evidence.

11. Cleanup / rollback

Review paths before deletion. The checkpoint is disposable, but the same pattern in production would follow evidence-retention and change-management policy rather than immediate deletion.

from pathlib import Path
import shutil
p=Path("gitlab-ch33-checkpoint")
print("REVIEW BEFORE DELETE:",p.resolve())
# shutil.rmtree(p)  # Uncomment only after confirming this is the disposable fixture.
Never adapt this cleanup command to a real project, evidence store, or GitLab namespace. Deleting live resources requires explicit scope verification, authorization, retention review, and recovery context.

Knowledge check

Why is Proposal B rejected even though its implementation sounds like it checks the same condition?

What two state changes must be predicted before the simulated write?

Why does the approval record include the proposal hash?

What does “zero credits consumed” mean in this checkpoint?

Which controls remain mandatory if a real AI-generated MR is created?

12. Lesson summary and bridge

  • You classified data and tool authority before executing any simulated action.
  • You required human approval for the reversible write and denied unrelated security-policy and destructive actions.
  • Independent tests rejected a plausible but unsafe AI remediation and supported the safer candidate.
  • Your approval record binds exact proposal identity, reviewer, evidence, scope, denied actions, privacy assumptions, and zero-live-credit fixture status.
  • Core delivery remains independent of AI availability: Git, CI, scanners, policies, and human review stay authoritative.

Chapter 33 adds governed AI assistance to the production GitLab operating model. Chapter 34 — Production Capstone: Build and Govern a Secure GitLab Software Delivery Platform — now integrates every major course boundary into one end-to-end delivery platform.

Primary sources and version notes

These lessons were finalized against current official GitLab documentation on 2026-08-22 with GitLab 19.3 as the release baseline. GitLab Duo and the Agent Platform are unusually version-volatile: feature maturity, model selection, credits, availability, governance behavior, UI placement, and data-processing details can change between releases. Re-check the documentation for the exact offering, subscription, add-on, and GitLab version you use. The checkpoint intentionally uses a local synthetic record rather than claiming to reproduce the GitLab AI audit-event report schema. For live Agent Platform work, use the current product-generated session/audit artifacts and your organization’s evidence-retention requirements.

Next chapter

Production Capstone: Build and Govern a Secure GitLab Software Delivery Platform

Integrate governed AI with repository, CI/CD, runner, security, registry, deployment, operations, audit, and recovery controls.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.