An agent will hand you a SQL query stitched together from string concatenation, an endpoint with no authorization check, or an API key pasted into a config file, and it will describe every one of them as done. Correct, consistent code is not the same as secure code. The two get conflated because the output looks finished either way.
Security is a deep, specialized field of its own, and no short list substitutes for threat modeling or for people who do this for a living. But there are a handful of practices that fit naturally into a spec-driven workflow and meaningfully raise the security floor of anything you build with an agent. Here are five.
1. Put security requirements in the spec, as acceptance criteria. Most insecure code comes from security being assumed instead of stated. Write it down where it cannot be missed: rejects unauthenticated requests, uses parameterized queries for every database call, never returns another user's records. When acceptance criteria become tests, those expectations get checked on every run instead of hoped for.
2. Encode the non-negotiables in your constitution. Your always-loaded project rules are the right home for the things that must never be violated: validate input at the boundary, pull secrets from the environment and never from source, perform an explicit authorization check on every endpoint, pin your dependencies. An agent anchored to those rules carries them into every feature without being reminded, the same way it carries any other convention.
3. Keep secrets out of the context window, the repository, and the model's
output. This risk is specific to AI-assisted development and easy to stumble into.
Agents read and write files, and you paste context into prompts, so a key dropped into a prompt,
a committed .env, or a secret echoed into a log is a direct exposure. Keep
credentials in environment variables or a secret manager, never hard-code them, never paste them
into a session, and run a secret scanner over your commits so a slip is caught before it
ships.
4. Validate and authorize at every trust boundary, and verify the agent actually did. The common vulnerability classes, injection, broken access control, server-side request forgery, unsafe deserialization, all live where untrusted input crosses into a trusted operation. Require parameterized queries, output encoding, and an authorization check on every protected route, then confirm the agent reused your existing validation and authorization paths instead of quietly writing a weaker version of its own.
5. Treat every new dependency as a security decision. Agents add packages freely, and the supply chain is a real attack surface: typosquatted names, abandoned libraries, and known vulnerabilities all arrive through a casual install. Require a justification for any new dependency, pin versions, run dependency and static-analysis scanning in your pipeline, and keep what you depend on patched. A dependency you did not choose deliberately is one you cannot vouch for.
None of this makes an application secure on its own. It raises the floor, and it puts security inside the method you are already using instead of bolting it on at the end. Real security work, threat models, reviews by specialists, and defenses matched to your specific risks, goes well beyond any five-item list and deserves that dedicated attention.