Security, secrets and dependencies
The specific security mistakes agents make, why credentials must never enter a prompt, and how to review an added package.
Credentials
- Never paste a key, token or password into a prompt. Transcripts are files on disk and are sometimes shared.
- Point the agent at
.env.example, never.env, and refer to variables by name. - Use environment variables at runtime so no secret is ever written into source.
- Keep secrets out of client-side code - anything in a browser bundle is public, however it was written.
- If a value did reach a transcript or a commit, rotate it. Removing the text does not undo the exposure.
Bad prompt:
"Connect to postgres://admin:[email protected]:5432/prod and add the index"
Better:
"Add a migration for an index on orders.created_at.
Use the connection from DATABASE_URL, which is already in the environment.
Do not print or log the connection string."⚠️
The dangerous instruction is 'get it working'. An agent asked to make something connect will reach for whatever credential is reachable on the machine. What fixed the problem locally can be a production credential in a config file in a diff. Name the variable and forbid the rest.
The mistakes agents actually make
| Mistake | Where it appears | Fix |
|---|---|---|
| SQL built by string concatenation | Repository and migration code | Parameterised queries, always |
| Authorisation checked in the UI | Route handlers | Check on the server, per request |
| Missing ownership check | Fetch-by-id endpoints | Scope the query to the current user |
| Verbose errors returned to clients | Error handlers | Log detail, return a code |
| Secret in a client bundle | Front-end config | Move to a server route |
| CORS set to wildcard with credentials | Server config | Allow only known origins |
| Password compared with == | Auth code | A constant-time comparison |
| No rate limit on login | Auth routes | Rate limit and lockout |
// generated, and wrong: authorisation is implied by the route name
app.get("/orders/:id", async (req, res) => {
res.json(await db.order.findUnique({ where: { id: req.params.id } }));
});
// correct: the query itself is scoped to the caller
app.get("/orders/:id", async (req, res) => {
const order = await db.order.findFirst({
where: { id: req.params.id, userId: req.user.id },
});
if (!order) return res.status(404).json({ error: { code: "NOT_FOUND" } });
res.json(order);
});The pattern to check in every generated handler: does the query constrain the row to the current user, or does it fetch by identifier and trust that only the right people call it?
Added dependencies
git diff package.json package-lock.json
# is this package real, and is it the one you meant?
npm view <package> repository homepage maintainers time.modified
# what will it pull in?
npm ls --all | wc -l
npm audit --omit=dev- Check the name character by character against the real package. A one-letter difference is the whole attack.
- Look at who publishes it, when it was last updated, and how many maintainers there are.
- Prefer a package with few transitive dependencies; each one is code you now depend on.
- Ask whether the dependency is needed at all - a date formatter is usually twenty lines.
- Pin versions and commit the lockfile, so the code that runs today is the code that runs tomorrow.
"Before adding any dependency, tell me what it is for and what it pulls
in. If it can be done in twenty lines of code we already have, do that
instead."That instruction alone removes a large share of generated package additions, because most of them exist to avoid writing code the agent could easily have written.
FAQ
Why are agents so prone to authorisation bugs?
Authorisation is a property of the whole request path, and it is invisible in the local view of one function. Generated code reproduces the common shape of a fetch-by-id endpoint, which is exactly the shape that omits the ownership check. Review it explicitly in every handler.
Is it safe to let an agent run my test suite?
Generally yes, if the tests are yours and the environment is not production. Be careful with anything that runs migrations, seeds data or calls a real external service - a test run should not be able to reach production credentials or a production database.
Related
Reviewing generated code like an owner Shipping, documenting and maintaining an agent-built project
Last refreshed 2026-09-18.