Willpower Is Not a Security Control: Secure Defaults Beat Training, and AI Does Not Fix That
Tanya Janca's DevSecStation episode on secure defaults matches why I built DaloyJS the way I did: security on by default, insecure paths explicit and effortful. Login with JWT is not enough, and telling an AI to build an API does not make the easy path safe.
I listened to Secure Defaults Beat Secure Training on Tanya Janca's DevSecStation podcast (SheHacksPurple, Season 1 Episode 8) and got that mix of relief and irritation. Relief that someone said the quiet part out loud. Irritation that I have been living the same story for twelve years and still watch teams answer it with another slide deck.
Her sticky-note line: the easiest path is often the insecure one. Developers usually care. They are also busy. Training leans on memory and willpower. Defaults shape what people do when nobody is watching. In live systems, defaults win.
That is why DaloyJS looks the way it does. Security starts on. Turning pieces off is explicit, sometimes two flags, and often loud at boot. Annual training is still useful. Tanya teaches it for a living, and she is careful about that: training helps when it explains the why. Alone, under deadline pressure, it loses to copy-paste.
Disclosure: I help build DaloyJS, a contract-first TypeScript backend framework with secure defaults and zero runtime dependencies. This post is a reaction to Tanya's episode and a design note for why the framework behaves this way. Steal the idea even if you never install the package.
A normal developer day
Tanya's story is painfully ordinary. You are juggling tickets. You spin up a service. You copy config from the last repo. You leave the settings because they work. Nothing feels wrong. You did not skip security training on purpose. You followed the path that was already paved.
Insecure defaults almost never feel like a decision. They feel like progress. I have shipped that shape more times than I want to admit, usually in Express or a thin wrapper around it, usually with a valid JWT middleware and a board demo that looked fine:
The annual training PDF said all the right things. The path of least resistance said ship. Willpower is a terrible control when the build is green and the product manager is in the doorway.
Login is not a security program
A lot of teams treat "we have JWT auth" as the finish line. Authentication tells you who called. It still leaves open whether that caller may touch this row, write these fields, or see that hash. I wrote a longer post on that gap (Your JWT Is Valid and Your API Is Still Vulnerable). Alice's token is valid, Bob's id is in the URL, and the handler still returns Bob's data.
That bug survives training because every tutorial shows the same shape: verify the token, then findById(params.id). Scoping the query to the principal, validating the body, and clamping the response stays extra work unless the framework and the template make that the default motion of writing a route.
AI multiplies whatever path you leave open
People assume a coding agent will remember the OWASP list better than a tired human. Sometimes it recites the list. Then it still generates the same handler the training data saw a million times:
Telling an AI to "build a secure API" is still training, only with a shorter attention span. The model optimizes for the demo that compiles. If your framework and scaffold treat unbounded bodies, open CORS, mass assignment, and missing response schemas as normal, the agent will reproduce normal. I covered what DaloyJS already blocks in a vibe-coding workflow in Vibe Coding Security. The design lesson is simpler: AI multiplies whatever default path you leave in the repo. It does not install judgment for free.
Training for the why, defaults for Friday at 5
Tanya ranks the approaches the same way I have seen them fail in production. Annual training alone (slides, quizzes, forgotten by the next sprint) barely moves anything. Training plus "please remember" helps a little and still depends on willpower. The combination that works is training that explains the why, plus defaults that make the secure option automatic, so people know why they should not rip the guard out and the system does not need them to be heroes at 5 p.m. on a Friday.
You can keep tools usable. You just pave the road people will take under pressure, and make that road the safe one.
What defaults look like in a backend framework
When I say DaloyJS is secure by default, I mean the boring stuff is armed when you write new App(): body size limits, request timeouts, prototype-pollution-safe JSON parsing, secure response headers, cross-origin write protection unless you register cors(), production error redaction, and boot guards for footguns like weak secrets, unauthenticated MCP routes without an explicit public flag, or session without CSRF on state changes. The full tour lives in Secure by Default and the docs.
The route shape does the same job. The contract is the gate before the handler. You do not wait for a reviewer to catch a missing comment:
On the third coffee you still do not need to remember unknown body keys, leaking extra response fields, unlimited payloads, or silent missing headers. An AI can still write a bad repository query. Defaults will not replace resource authorization or threat modeling. They remove the class of bugs that only exist because the framework shrugged.
Opt-out should be explicit, and a little annoying
Tanya's practical advice is the design rule I care about most in DaloyJS: make the secure option automatic, and make the insecure option take explicit choice and effort.
In production, secureDefaults: false without acknowledgeInsecureDefaults: true throws at construction. Even outside production, the framework logs every surface that flag disabled. Per-feature opt-outs exist so you can open one window without burning the house down. MCP routes need auth unless you pass public: true. Insecure should not look identical to the happy path in code review.
If a junior (or an agent) can disable your security posture by deleting one line that looks like a style preference, you do not have a control. You have a suggestion.
Do Tanya's one thing, even if you never touch DaloyJS
Her homework is better than any framework pitch: change one default in one active repo. Config that ships insecure "for later." CI without composition analysis. A template that skips validation. A script that assumes secrets live in plaintext. Flip it so secure is automatic and insecure is effortful. Leave a comment so future you does not undo it while cleaning "noise."
You will not fix the company today. You will protect every future change that copies that path. That is also why a framework-level default beats a wiki page titled Security Checklist.
Defaults still leave work on the table
You still need threat modeling when a feature moves money or identity. You still need resource-level authorization (the JWT-valid-but-still-broken problem). You still need encryption and key management for sensitive data at rest, human review on scary migrations and permission changes, and training that teaches why so people do not proudly delete the guard.
Defaults handle the recurring boring failures: the ones that show up because someone was rushing and the template was friendly. AI makes those failures cheaper to produce at volume. Frameworks and scaffolds that keep the insecure path frictionless will lose harder in 2026 than they did in 2019.
Why I keep building it this way
I write backends, not board decks. I got tired of reading the same pentest findings after teams completed the same annual training. Developers cared. The default path was a trap, and everyone was busy.
So DaloyJS starts with contract-first routes, schemas that run before handlers, secure headers and limits without a plugin shopping list, boot guards that fail closed, and opt-outs that leave a paper trail. If that sounds heavy-handed, good. Security that depends on everyone having a perfect day is already broken.
Tanya said it cleaner than I usually do: when you change a default so it is secure, you protect every future change that touches that code. Training can explain why. Systems have to make it stick.
Listen, then flip one default
- DevSecStation: Secure Defaults Beat Secure Training (Tanya Janca / SheHacksPurple)
- shehackspurple.ca
- Related here: Secure by Default (feature tour), JWT valid, API still vulnerable, Vibe coding security
- Docs: secure-by-default · Enforcement and escape hatches
Scaffold with the boring guards already on: