Security tests show evidence; the reviewer decides
Guidance comes in when needed and results help the reviewer. Plus the day a model invented a whole process.
When an agent changes code related to login or user input (what people type in), I want a security list ready. I also want it for changes to automatic checks. Only then. Keeping everything loaded in every session uses space in the information the model can use. Most of the time, the work has nothing to do with security.
Five lists to consult when needed
I use five skills (sets of instructions for a task).
security-reviewlooks for security flaws in code. It follows guidance from OWASP (an organization that publishes security advice).owasp-audit-zapmaps findings to the OWASP Top 10 (a list of ten common risks). It also runs ZAP tests (using a tool to find flaws) in three modes: baseline (a quick scan), full, and authenticated (with login).injection-preventioncovers four risks: SQL injection, XSS, command injection, and SSRF. SQL injection changes a database query. XSS shows malicious code in a browser. Command injection makes a server run an action. SSRF makes the system visit another address. The skill covers FastAPI and React (tools for building systems and interfaces).secrets-managementuses gitleaks and trufflehog (tools that look for exposed secrets). It also covers changing secrets and incident response.gha-security-reviewchecks how someone could exploit an automatic GitHub Actions process.
Each list comes in when work touches the area it covers. Change a database query, use
injection-prevention. Change an automatic check, use gha-security-review. These
instructions do not stay in the agent's memory between tasks.
Choosing when to load a list is part of the work. A PR is a request to review and
include code changes. Using owasp-audit-zap on a PR that only changes a button label
wastes context. Skipping gha-security-review on a PR that changes an automatic process
means accepting the risk without checking.
A skill does not review for me. It reminds the agent what to look at while writing code. That already changes what appears in a PR. A scanner tests the system automatically. It acts later, when the code is already in the PR. Neither the skill nor the scanner approves the PR.
The scanner shows evidence
ADR-061 (a record of an architecture decision) was accepted on September 5. It says DAST provides evidence for the reviewer. DAST is a security test on a running system. It does not give a verdict. A strix test runs on every PR, and a ZAP baseline scan runs every night. The reviewer still decides. The plan for this process went through a review council of 5 rounds, which found 80 issues.
It also set a fail-closed rule. If the scanner itself breaks or does not run, that does not count as a pass. A test that never started also shows no alerts. Treating that absence as a pass would confuse a clean test with one that never happened.
The division is simple. The scanner shows what it found. The reviewer reads those results alongside the PR changes and decides whether the request passes. If the scanner could approve, I would be trusting a number without knowing what it checked.
An alert may be wrong. A PR with no alerts may still have a problem the test cannot reach. Either way, someone needs to read the result with context (information about the project). That is the reviewer's job.
The day a model invented the whole process
The dispatch prompt sent only the task title to a coding model. This happened on
September 5, the ADR date. The model filled in the rest and wrote a
.github/workflows/dast.yml file from scratch. Two PRs were closed.
Given a title and no specification, the model did what it tends to do: fill in the missing parts. The fix was in the task-routing tool. The instruction needs to carry the specification, not only the title. In my view, a whole process nobody asked for is worse than an error. An error draws attention; the process can look legitimate.
Secrets, without details
The agents do not read .env files. Any command that needs a secret goes through a
separate local intermediary, CIPHER. It runs the command without printing the
credential. Only I can reveal a secret. I am not going into more detail on purpose.
