Trust what you've proven
Turn untrusted input into values whose meaning has already been checked, then carry that trust forward through the code.
Security starts with everyday coding decisions. The book is built around habits that make safer choices the natural way to write code - before a vulnerability needs to be found and fixed.
Amazon A+ is intentionally compact. This page is where the longer reasoning lives - including code, related talks, workshop paths, and books I recommend reading alongside mine.
Turn untrusted input into values whose meaning has already been checked, then carry that trust forward through the code.
Use constrained types, purpose-built APIs, and secure defaults so the easiest implementation path is also a safer one.
Follow security through the complete feature - input, authorization, files, services, storage, errors, and output.

The same ideas behind the book became an experiment: if a coding agent gets safer APIs, constraints, defaults, and scaffolding, does it produce safer software without being prompted to care about security?
Effective Secure Coding is built around secure habits. Each habit centers on a security property we want to preserve or enforce - and on making that property the natural way to write code.
This way of thinking sometimes leads me away from familiar secure-coding advice. Here is one example: "Use an ORM." That is common advice for preventing SQL injection.
My preferred habit is: Keep query structure and data structurally separate. Once I have that property, I can choose an interface that preserves it while still taking advantage of the database.
String concatenation breaks the boundary by inserting data directly into SQL code:
var sql = "SELECT * FROM Users WHERE Name = '" + name + "'";
An interpolation-aware database API can keep the value parameterized:
db.Users.FromSql(
$"SELECT * FROM Users WHERE Name = {name}");
A SQL DSL can make the separation part of the query structure:
dsl.selectFrom(USER)
.where(USER.NAME.eq(name))
.fetch();
The latter two preserve the property I care about: the interface keeps query structure and data separate.
So why do I prefer keeping SQL visible - through a DSL or parameterized SQL - instead of using an ORM? I want the database to participate in the design.
I approach databases as both a developer and a DBA. I want the database itself enforcing security and correctness where it can.
A FOREIGN KEY can make an invalid relationship impossible to store; a CHECK constraint can reject invalid state; and a UNIQUE constraint can enforce a rule correctly when requests race. Views can provide stable read interfaces while the schema evolves, while stored procedures can expose specific write capabilities without granting general table-update permission.
This application may transfer money. It may not arbitrarily change account balances.
Consider a money transfer. An object-oriented persistence model naturally leads toward loading two account entities, changing both objects, and saving the changes:
var from = accountRepository.findById(fromId).orElseThrow();
var to = accountRepository.findById(toId).orElseThrow();
from.setBalance(from.getBalance().subtract(amount));
to.setBalance(to.getBalance().add(amount));
accountRepository.save(from);
accountRepository.save(to);
The application now has several steps whose transaction and concurrency behavior must be correct. The relational model lets us think about the operation as a change to a set of rows:
UPDATE Account
SET Balance = Balance +
CASE Id
WHEN :from THEN -:amount
WHEN :to THEN :amount
END
WHERE Id IN (:from, :to);
-- Preconditions such as sufficient funds are omitted here for clarity.
With the appropriate preconditions and constraints, one business operation can become one atomic database statement. Keeping SQL visible keeps that design option visible too.
The same habit applies when SQL genuinely needs to be dynamic. The application can receive a constrained database operation instead of arbitrary write permission.
A stored procedure can accept a requested column, verify that the identifier exists in database metadata, safely quote it, keep values parameterized, and execute the resulting operation using narrowly scoped database permissions.
This keeps the dangerous capability behind a safer interface - a pattern that appears throughout Effective Secure Coding.
Because this is a sample of how the book approaches secure coding. Take familiar security advice and ask: What property actually makes this safe? Then ask: Which habit can make that property the natural way to write the code? Then preserve the useful capabilities underneath whenever you can.
Sometimes that leads directly to established best practice. Sometimes it leads somewhere more opinionated - such as my preference for a typed SQL DSL and a deliberately designed database API over an ORM.
The idea is to develop secure-coding habits you can reason from, so you can make good decisions when the framework, language, database, or threat changes.
Values are easy to parameterize. Identifiers such as a user-selected column name are different. A safe pattern is to validate dynamic structure against a trusted source, quote the validated identifier, and keep every value parameterized.
Suppose a UI legitimately allows the user to choose which column to search. SQL parameters cannot turn @Column into an identifier. In SQL Server, the procedure can validate the identifier against sys.columns before it is ever inserted into SQL structure.
CREATE PROCEDURE dbo.FindUser
@Column sysname,
@Value nvarchar(200)
AS
BEGIN
SET NOCOUNT ON;
IF NOT EXISTS (
SELECT 1
FROM sys.columns
WHERE object_id = OBJECT_ID(N'dbo.Users')
AND name = @Column
)
THROW 50000, 'Invalid column', 1;
DECLARE @sql nvarchar(max) =
N'SELECT * FROM dbo.Users WHERE '
+ QUOTENAME(@Column)
+ N' = @Value';
EXEC sys.sp_executesql
@sql,
N'@Value nvarchar(200)',
@Value = @Value;
END;
QUOTENAME() safely delimits the validated identifier, and sp_executesql keeps the value parameterized.An input such as Name]; DROP TABLE Users;-- simply fails the sys.columns lookup. If the operation intentionally permits only a subset of real columns, add that business rule as an additional allowlist.
Static SQL inside a stored procedure can rely on ownership chaining when owners align. Dynamic SQL is compiled as a separate batch, so an application account with no underlying table rights needs a deliberately scoped execution context or a signed module when the procedure must exercise additional permissions. Keep that extra authority as narrow as the procedure's job.
The same philosophy is used in Secure From Scratch workshops: developers build real features, reason about the security properties they need, and practice interfaces and designs that make safer implementation easier from the start.
I recommend reading broadly. These books overlap with Effective Secure Coding in useful ways, and each emphasizes a different part of the secure-development problem. For older books, I call out where the principles still hold but the technology has aged.
Read it for: a security-practitioner's view of what developers need to know - vulnerabilities, attack patterns, defensive controls, and approachable explanations of application security.
How my book differs: Tanya's book comes primarily from the security side and teaches security to developers. Effective Secure Coding starts from the development side - the everyday design and implementation habits that can make safer code the natural result.
Publisher pageRead it for: design-driven security, domain primitives, secure validation, error handling, and security thinking across modern architectures.
How my book differs: this is the closest philosophical relative. My focus is narrower and more developer-at-the-keyboard: habits used while implementing complete features, with repeated emphasis on safe interfaces and natural secure choices.
Publisher pageRead it for: a deep Java web-security reference covering authentication, access control, data protection, injection, file handling, logging, and the secure development lifecycle.
How my book differs: my book is organized around habits for building safer features rather than around vulnerability and defense categories, and it brings those habits into a current Java/Spring and AI-assisted-development context.
Publisher pageRead it for: foundational secure-development thinking across architecture, design, implementation, testing, and operations.
How my book differs: Effective Secure Coding concentrates on contemporary feature-level coding habits and concrete implementation decisions rather than covering the full software lifecycle.
Publisher pageFocused implementation guidance when you need the details of a particular security control.
Open the cheat sheetsA structured set of application-security requirements that complements habit-oriented development with a verification target.
Open ASVSExperiments and libraries exploring how trust transitions, constrained types, and safer APIs can guide both human developers and coding agents.
View on GitHubA typed SQL DSL that keeps the relational model visible while giving application code structured query construction and parameter binding.
Explore jOOQ