Working Code Is Not Safe Code
AI agents make it easy to create a working service.
They do not make it obvious whether that service is safe enough to deploy.
That gap is where security work starts.
Not movie hacking. Not enterprise ceremony. The practical version: secrets, dependencies, request limits, headers, lockfiles, input validation, build paths, and deployment assumptions.
The lesson from my latest security pass was simple:
Working code is not automatically safe code.
What AI Makes Easy to Miss
Generated code usually optimizes for the prompt in front of it.
- make the feature work
- fix the build
- add the endpoint
- wire the integration
- move on
Security depends on the wider system.
Where do secrets live? Which packages are installed during deploy? Does rendered Markdown become executable HTML? Can a slow client hold a server open? Does CI trust whatever SSH host key it sees first?
Those questions rarely show up in a first prompt.
AI-assisted code can be functional while still missing critical security controls. The model gives you what you asked for. It does not automatically know the operating context.
What I Changed
The fixes were boring. That is the point.
On static sites:
- committed lockfiles
- deploys with
npm ci - pinned supported runtime versions
- security headers
textContentinstead ofinnerHTMLfor form responses
On build scripts:
- no production
npx - local binaries
- validated path inputs before resolving directories
On server routes:
- sanitized Markdown
- safer request parsing
- email normalization
- request size limits
- simple rate limiting
- fail-closed behavior when required secrets are missing
On redirect and edge code:
- server timeouts
- capped request headers
- stricter redirect target validation
- pinned SSH host keys instead of runtime
ssh-keyscan
None of this is advanced security research.
Small services usually fail in boring ways: drifting dependencies, unsafe HTML rendering, oversized requests, loose redirects, dynamic deploy trust, or secrets in the wrong place.
AI makes those misses easier because it lowers the cost of adding one more thing.
My Minimum Security Floor
Before a small AI-built service goes public, I want this baseline:
- Deterministic installs. Commit lockfiles. Use
npm ci. - Pinned runtimes. Know what runs in production.
- No runtime magic. Avoid production
npx. - Patched dependencies. Run the boring scanners and tests.
- Hostile input by default. Treat forms, route params, Markdown, API responses, and generated HTML as untrusted.
- Request limits. Add body limits, rate limits, timeouts, and header caps.
- Browser guardrails. Use CSP,
nosniff, frame restrictions, and referrer policy. - Boring secrets. No secrets in repos or frontend bundles.
- Pinned deploy trust. Do not collect SSH host keys dynamically in CI.
- Narrow agent access. Give agents only the files, tools, and credentials needed for the task.
This checklist is not complete. It is a floor.
But a floor matters when the new default is "ship before lunch."
The goal is not to stop building quickly. The goal is to make the fast path safer than the lazy path.