# How to build a secure SaaS solo with Claude - a cookbook
I've seen a lot of questions about how to secure a "vibe coded" SaaS, so I thought I'd share my experience of building one solo.
For background, I started out as a software engineer in the late 1990's before moving into security and am currently a CISO — I've not written production code in probably 20-25 years, but have taken numerous large projects in UK regulated services through security reviews to production.
I started Feb 2026 building my SaaS and last week took it through its second round of pen testing, with nothing but low findings — and to be clear, 100% of all code is Claude generated.
This post is the scaffolding that made that possible.
The assurance model (and why reading the code at scale doesnt work)
Here's the part that usually gets the "so you vibe-coded it and hoped" reaction: on a programme this size, regardless of who wrote it, the decision to go-live is made by a system owner, based on multiple points of assurance, this approval is usually from someone non-technical.
On any large delivery I've given commercial assurance for, nobody reads all the code — you can't, at scale. Assurance comes from the controls around how the code is built, and from independent testing of the result. That principle doesn't change because the author is an AI rather than a team of contractors. My role here was sponsor and architect, not reviewer.
So this isn't a post about how I checked the code line by line. It's how I built assurance into a codebase. Everything below is a layer of that: constrain what the AI can do, enforce it mechanically, prove nothing broke, then have someone independent try to break it.
1. Start by being really clear on your functional — and non-functional requirements
Claude is very good at building what you tell it — but you have to be explicit.
I spent the first 10 days or so simply working on specs for what I wanted the product to do — without any thought about implementation. If you're from a tech background, invest time here, as it's probably the bit you're inclined to skip.
Spend a lot of time thinking about your permissions model — who should be able to see what, and how is that specified? This is the most critical part of your design and the part that is hardest to retrofit. (It comes back below when talking about boundaries and regression testing, because it's the thing the mechanical controls exist to protect.)
2. Lay down some core principles for the build
Here are a few of mine, verbatim, that are particularly relevant to a stable and secure outcome:
- P2. Boring technology is preferred. Avoid novelty unless it delivers a clear, necessary capability.
- P9. Security is default, not a phase. Encryption in transit and at rest, least privilege, secrets outside code — assumed, not negotiated per feature.
- P13. Design for maintainability. Clear explicit code over clever abstractions; standard patterns over novel ones.
The enforcing line: "Any decision that knowingly violates a principle should state this explicitly," and Architecture Decision Record documents (ADRs) must reference the principles. So the AI can't quietly make an architectural choice that cuts against the grain — it has to be written down and justified against these.
3. Structure your repo so everything has a natural home — and give Claude an index
Key parts of mine are:
src/
backend/
frontend/
db/migrations/ numbered .up.sql / .down.sql pairs
docs/
spec/ what it should do (authoritative) (both commercial and functional)
arch/
decisions/ why it's built that way (50+ ADRs)
technical/ how it works as-built (TA docs)
code_style/ how code is written (CS-001..012)
project-log/
code-prompts/ authoritative implementation prompts (agent executes these)
prompts/ my scratch thinking (agent must NOT auto-read)
One package per domain, one handler file per domain, and four separate documentation layers with an explicit authority order: spec outranks ADRs, which outrank the as-built docs, which outrank code-style — so when two documents disagree, there's always a tie-breaker. That ordering matters more than it looks.
4. Spend your time on working through designs
Individual items of work fall into the prompts area, but anything durable should be in the docs/arch area. These areas will grow, so also have an index file for your design decisions and architecture patterns — every session has to build context, so make it easy for Claude to find and load just the files it needs.
50+ Architecture Decision Records — the why, dated, with alternatives considered. Real examples: two-role PostgreSQL for RLS enforcement; append-only history snapshots for audit; magic-link auth as a first-class IdP type; pragmatic TDD. This isn't ceremony — it's how a later AI session (which shares no memory with the last one) or me-in-three-months avoids relitigating a settled call.
I find chat is better for design discussions and Claude Code much better for implementation — I use the chat to generate my execution prompts, which then get passed into Claude Code.
Complex features get reasoned out in a long planning chat first — options, trade-offs, edge cases. I quite often spend 1-2 days on these conversations, coming back and making revisions. I always ask for a challenge and analysis on trade-offs for any topic I'm not fully familiar with.
The output is a written, phase-gated prompt with explicit exit criteria, dropped into code-prompts/. The agent completes one phase, runs make check, confirms the criteria, then moves on — and writes a completion note at the end. The scratch directory where I think out loud is explicitly off-limits to it. Design and execution are different jobs done by different "people."
5. Set clear boundaries for your Claude Code execution environment
A root CLAUDE.md, loaded into every Claude Code session. Seven non-negotiables, near-verbatim:
- Tenant DB access must go through a row-level-scoped querier — with a build-time check.
- Never weaken endpoint auth, auth middleware, or tier/permission checks without explicit approval that session.
- Core security checks live in a single model (the permission cache) — any changes need explicit session-level sign-off.
- All errors use a
MODULE/function/NNNcode — so an error message can always be traced to a specific line. - Migration discipline: numbered, paired up/down, bump the expected schema version.
make checkis the key quality gate that runs multiple hygiene checks — it must run between phases in a build.- Every behavioural change ships with tests; anything touching RLS, auth, or permissions needs integration tests against a real DB, because mocks can't exercise the security layer.
A prompt is allowed to override a rule — but the agent has to surface the conflict and get confirmation first — this stops "helpful" drift.
Auth itself is fully externalised in passing here: the platform never stores or checks a password — identity is delegated to an external IdP over standard protocols. One large attack surface I simply don't own.
6. Make regression the first line of assurance
If nobody reads all the code, the first question for any change becomes: did it break something that worked yesterday? Tests answer that before I do. This is the floor — and it has to be automatic.
Every behavioural change ships with its tests, and the full suite runs in the gate on every build, so a regression surfaces in the session that caused it rather than three weeks later. The security-critical paths — tenant isolation, auth, permissions — are covered by integration tests against a real database, because a mock can't exercise the boundary that actually matters. "Nothing that worked before is broken" isn't a nice-to-have in this model; it's the first thing the assurance stack has to guarantee.
I might not read all the code - but I sure as hell review the test cases, and any failed test (or even tests that prove flakey due to timing issues) become the priority one item to fix.
7. Use automated quality checks on every build
make check runs: gofmt → go vet → staticcheck → errcheck → gocritic → the architecture check → govulncheck (dependency CVEs) → go build → the full test suite.
I periodically also add gosec for a more complete analysis.
Nothing is "done" until it's green, and it runs between every phase of work — so regressions surface in the session that caused them, not three weeks later.
The worst bug in a multi-tenant system is a query that breaks tenant isolation — which is exactly the permissions model from §1, now enforced in code. So my build cycle includes a custom tool that performs a static architecture check: every *sql.DB handle in the codebase must be named rawDB/RawDB, which makes every legitimate raw-pool use greppable in one command and forces everything else through the tenant-scoped path. A raw handle needs an inline, justified waiver:
//rq:RLS reason: pre-auth flow, no tenant context yet exists. See ADR-049.
rawDB *sql.DB,
It runs in the build gate and on every developer machine. Underneath, Postgres uses two roles — one to migrate, a non-superuser to run — so isolation is enforced at the database layer, not just in app code. If the agent (or I) reach for a shortcut, the build fails.
8. Run multiple passes of security testing
The first pass is a request for a security review by Claude of its own work — run about once per week. A second pass uses a more specialised tool (I was recommended 'Shannon', which still uses Claude as the backend reasoning engine but provides a lot of structure).
Finally, I've paid for a full-scope pen test. The first pass turned up findings — including high and medium severity — as any real assessment will, but was a lot cleaner than many commercial projects with significant teams that I've sat in steer co's for. I remediated each one the same way I build everything: a remediation spec per issue, tests added, re-run the gate. The retest closed everything above low severity.
This last step is the one that matters most under my model: if you're not reading the code, independent testing of the result is what the whole approach rests on. I'm deliberately not claiming a spotless first pass — findings were found. The point is they were found by an independent assessor and closed with the same discipline as the rest of the build.
So what's the TLDR
- None of the above is AI-specific. It's ordinary good engineering. What's changed is that AI makes it cheap enough for one person to maintain all of it.
- The risk isn't "AI writes bad code." It's AI writing plausible code that nothing independently checks. Every control here exists so that something always does — the gate, the tests, the architecture rules, and ultimately an external assessor — even though I don't.
- The AI is a force multiplier for someone who knows what "correct" looks like. It is not a substitute for that knowledge. I could design these guardrails because I know where this class of system breaks.
Happy to go deeper on any recipe — the RLS enforcement, the prompt workflow, the pen test loop.