Inspiration

I felt this problem before I tried to solve it. While learning DevSecOps from a long practical book, I set up scanning, a pipeline and image signing by hand on a cloud server. Many steps failed at least once. One setting refused to switch on, without any warning, because a system command ignored an option I had given it. A server would not start because the cloud had no capacity left. Variables I had set disappeared after a restart. Each error cost me time, and the messages rarely told me what to do next.

Then I noticed something else. Most of the work that comes after writing code follows rules that are easy to write down: scan, group the findings, fix what is safe, and hand the rest to a person. Where I felt lost, a machine could help, as long as a person keeps the final decision. The people who need this most are students and small teams, who have no security engineer.

What it does

Harbormaster takes over the work that comes after the code is written, on GitLab, using the GitLab Duo Agent Platform.

The pipeline. Every merge request runs a real GitLab pipeline. It scans for leaked secrets, unsafe code, risky settings and vulnerable packages. It builds a container image, lists what is inside it, and signs it. Flawed code is never signed. Even when tests fail, the scanners still run, so nobody has to dig through logs to see the whole picture.

The findings. A normalizer merges every scanner report into one list, groups duplicates into distinct fixes, and labels each one using a written rulebook: the agents may fix it, a person must decide, or it is only recorded. The pipeline log ends with a START HERE section in plain words that says what each item means and who should decide.

The flow. My custom flow, Harbormaster Fix, runs on GitLab Duo Agent Platform. A person mentions it on a merge request, and three agents work in order. Triage reads the scan results and the rulebook. Fix makes only the allowed changes, on its own branch, and opens a separate fix merge request that never targets main. Report posts one calm comment saying what was fixed, what was left for a person, and what could not be tested. A policy guard job then checks the fix against the rulebook.

The demo. A small pledge and receipt tracker for community fundraisers, flawed on purpose in a demo merge request. It had an old package with known problems, a fake key written into the code, a database query built from user text, and a container that ran as root.

Supervision level: Supervised. A person triggers the flow, reads its report, and decides what to merge. Nothing pushes to main.

Results, measured on GitLab

Before the fix and after the fix request the flow opened:

  • Findings: 42 down to 10
  • High severity findings: 19 down to 1
  • Distinct fixes needed: 11 down to 7
  • Failing tests: 4 down to 1
  • Test coverage: 74 percent up to 100 percent

The one failing test and the one high severity finding that remain are the SQL injection, which the rulebook correctly gave to a person. The flow also fixed an old package, a Dockerfile that ran as root, missing tests, and a key written into the code. It said plainly that the key must still be rotated by a human, because removing it from the code does not make it safe. It also said what it could not test in its own sandbox, and the pipeline measured the result.

To see it, open merge request !5 (the flawed change and the flow's comment) and merge request !10 (the fix) in the repository.

How we built it

  • A FastAPI service with server rendered pages, tests and a Dockerfile
  • GitLab CI/CD running Gitleaks, Semgrep, Checkov, Trivy, Syft, Grype and Cosign, with image signing
  • A normalizer, a risk policy and a policy guard written in Python, all with tests
  • Two custom flows on GitLab Duo Agent Platform: a small test flow that summarizes an issue, and the three agent Harbormaster Fix flow
  • Claude Code helped me write the code and documentation, and I reviewed the results of every run

Challenges we ran into

  • Two scanner images had no shell, which GitLab's runner needs, so the first scan jobs failed.
  • Signing failed because the job never logged in to the registry.
  • When tests failed, GitLab skipped every scanner. I changed the pipeline so scans always run, while signing and deploying never run on flawed code.
  • A build check stopped the job before it saved the image, so the scanners had nothing to read.
  • My flow would not start at first, because its default image had no pip.
  • The policy guard crashed on GitLab until it used the right comparison point, and then it could not read the real description format.

Each time I read the log, fixed the cause and ran it again.

Accomplishments that we are proud of

A custom flow that opens a fix request, reports honestly, and is checked by the pipeline and not by its own word. The agent never marks its own work as proven, and the numbers above come from real GitLab runs.

What we learned

Agents are only as trustworthy as the rules around them. Writing the rulebook, the guard and the plain explanations before any agent ran made every later step easier to check.

What is not built yet

  • Release, deploy and monitor flows. The scripts for release notes, health checks and rollback decisions exist in the repository, but they are not flows yet.
  • A real deploy target. Deployment is still a stub, because my cloud billing never cleared.
  • The policy guard's pass means within policy, not proven correct. It also says what it could not check.

What is next

Release, deploy and monitor flows, a real deploy target, GitLab's own Code Review flow as a review stage, and making Harbormaster work for any project with one settings file.

Built With

  • checkov
  • claude-code
  • cosign
  • docker
  • fastapi
  • gitlab-ci/cd
  • gitlab-duo-agent-platform
  • gitleaks
  • grype
  • jinja
  • python
  • semgrep
  • syft
  • trivy
Share this project:

Updates

Submission history