Systematic secure-coding defaults for web applications grounded in the OWASP Top 10 and ASVS. Covers input validation, output encoding, authentication hardening, authorization enforcement, cryptographic hygiene, dependency management, and security review workflows.
When to use
Starting a new web application or API and need a security baseline
Reviewing an existing codebase for hardening opportunities
Adding user-facing forms, file uploads, or payment flows
Preparing for a security audit or penetration test
Onboarding a team to secure-coding standards
When NOT to use
Pure static marketing sites with zero user input — reach for CSP headers only
Embedded firmware or kernel-level security — reach for platform-specific hardening guides
Threat modeling (use the `security-threat-model` skill instead)
Core concepts
Concept
Description
Defense in depth
Multiple overlapping controls so no single failure is catastrophic
Least privilege
Every component gets the minimum permissions it needs
Fail secure
Errors deny access by default, never fail open
Input validation
Allowlist-first validation at every trust boundary
Output encoding
Context-aware encoding prevents injection in HTML, JS, SQL, URLs
Secure defaults
Ship with the strictest config; relax intentionally
Every external input must pass through a schema before touching business logic.
```typescript
import { z } from "zod";
export const CreateUserSchema = z.object({
email: z
.string()
.email("Invalid email format")
.max(254, "Email too long")
.transform((v) => v.toLowerCase().trim()),
password: z
.string()
.min(12, "Password must be at least 12 characters")
.max(128, "Password too long")
.regex(
/^(?=.[a-z])(?=.[A-Z])(?=.\d)(?=.[^a-zA-Z\d])/,
"Password must include upper, lower, digit, and special character"
),
displayName: z
.string()
.min(1)
.max(64)
.regex(/^[\w\s-]+$/, "Display name contains invalid characters"),
});
export type CreateUserInput = z.infer<typeof CreateUserSchema>;
```sql
-- Users can only read their own data
CREATE POLICY "users_read_own" ON public.profiles
FOR SELECT USING (auth.uid() = user_id);
-- Users can only update their own profile
CREATE POLICY "users_update_own" ON public.profiles
FOR UPDATE USING (auth.uid() = user_id)
WITH CHECK (auth.uid() = user_id);
-- Admins can read all profiles
CREATE POLICY "admins_read_all" ON public.profiles
FOR SELECT USING (
EXISTS (
SELECT 1 FROM public.user_roles
WHERE user_roles.user_id = auth.uid()
AND user_roles.role = 'admin'
)
);
```
Decision tree
User input arrives → validate with Zod schema before any processing
Rendering user content → apply context-appropriate output encoding
Storing passwords → use bcrypt (cost 12+) or argon2id
Accessing data → enforce row-level security + server-side auth checks
Sensitive config → environment variable, never committed to repo
If unsure about a pattern → apply the strictest control and relax intentionally
Edge cases and gotchas
Double encoding — encoding output that was already encoded produces garbled text. Track encoding state through the pipeline.
Zod transforms silently change data — `.transform()` runs after validation. Log the pre/post values during development to catch surprises.
CSP breaking inline scripts — strict CSP blocks inline handlers and eval. Use nonces or move scripts to external files.
Rate limiter bypass via IP spoofing — `X-Forwarded-For` is trivially spoofed. Use Vercel's trusted `x-real-ip` or Cloudflare's `CF-Connecting-IP`.
Lock file drift — `pnpm install` without `--frozen-lockfile` in CI can pull unaudited versions. Always freeze in CI.
RLS not enabled — Supabase tables have no RLS by default. Every table with user data must have explicit policies; use `ALTER TABLE ... ENABLE ROW LEVEL SECURITY`.
JWT expiry mismatch — short-lived access tokens with long-lived refresh tokens need separate validation paths.
Evaluation criteria
All user input validated with typed Zod schemas before processing
No raw SQL string interpolation anywhere in the codebase
Security headers applied to all responses (HSTS, CSP, X-Content-Type-Options)
Passwords hashed with bcrypt (cost ≥ 12) or argon2id
RLS enabled on every Supabase table containing user data
Dependency audit passes with no high/critical findings
Rate limiting applied to authentication and write endpoints
Secrets stored in environment variables, not in code or config files
Error responses do not leak stack traces, SQL errors, or internal paths
Security logging captures auth failures, permission denials, and validation errors
Research-backed changes
The biggest delta is Sign in. Fold the concrete changes into the operating notes, then discard the fluff.
Scan GitHub Security Advisories for critical npm CVEs. Check Snyk blog for dependency vulnerability trends. Monitor PortSwigger for new web-attack techniques. Update the secure-coding checklist, dependency-audit workflow, and incident-response patterns.
Latest refresh trace
Reasoning steps, source results, and the diff that landed.
Apr 18, 2026 · 9:28 AM
triggerAutomation
editoropenai/gpt-5-mini
duration115.5s
statussuccess
sources discovered+1
Revision: v12
This update incorporates confirmed details from the Snyk advisory on the Axios npm compromise (affected releases axios@1.14.1 and axios@0.30.4, malicious dependency plain-crypto-js@4.2.1 and C2 indicators), affirms OWASP Top 10:2025 guidance on supply-chain failures (A03), tightens header-sanitization and IMDS egress-block recommendations, and cites PortSwigger 2025 research for emergent attack patterns.
Added explicit Snyk SNYK-JS-AXIOS-15850650 references and IoCs; rewrote supply-chain controls and incident playbook with operational commands; added header-sanitization and egress-blocking guidance tied to CVE-2026-40175; cited PortSwigger Top 10:2025 and OWASP Top 10:2025; preserved existing template-engine and dev-server hardening advice.
- Secure defaults: ship with the strictest config; relax intentionally
−- HTTP header sanitization: validate/sanitize header values before writing to sockets to prevent CRLF/SMUGGLING/SSRF gadget chains (see Axios advisory, CVE-2026-40175)
+- HTTP header sanitization: validate/sanitize header values before writing to sockets to prevent CRLF/SMUGGLING/SSRF gadget chains
| A10 | Mishandling of Exceptional Conditions | Safe error-handling, no sensitive info in error messages |
−References: OWASP Top 10:2025 (https://owasp.org/Top10/2025/)
+References: OWASP Top 10:2025 —https://owasp.org/Top10/2025/
## Workflow
@@ −55 +55 @@
### Step 2: Harden HTTP headers
−Apply strict header validation and common security headers. In addition to HSTS, CSP, X-Content-Type-Options, Referrer-Policy, explicitly validate header values before writing them to sockets to prevent CRLF/SMUGGLING payloads and header-injection gadget chains. Example (conceptual):
+Apply strict header validation and common security headers. In addition to HSTS, CSP, X-Content-Type-Options, Referrer-Policy, explicitly validate header values before writing them to sockets to prevent CRLF/SMUGGLING payloads and header-injection gadget chains.
+Practical rules:
- Reject header values containing CR or LF characters: if (/\r|\n/.test(value)) reject
- Normalize header names and canonicalize values before merging
- When merging user-supplied config into request headers, sanitize or drop unexpected keys
−
−(See Axios advisory CVE-2026-40175 for a concrete gadget chain that escalates prototype pollution into cloud metadata exfiltration and IMDSv2 bypass via unsanitised header values in Axios. Upgrade to patched versions and sanitize headers.)
−Why update: OWASP Top 10:2025 elevated supply-chain issues to A03 and recent incidents demonstrate active exploitation of publishing workflows, maintainer account compromises, and lifecycle-script abuse(Axioscompromise is a recentexample).
+Why update: OWASP Top 10:2025 elevated supply-chain issues to A03 and recent incidents demonstrate active exploitation of publishing workflows, maintainer account compromises, and lifecycle-script abuse. The March 31,2026Axiosincident is a concrete example: malicious versions axios@1.14.1 and axios@0.30.4 were briefly published and contained a postinstall hook that pulled a hidden dependency (plain-crypto-js@4.2.1) which executed a cross-platform RAT during installation. The malicious releases were removed from npm within hours; see Snyk advisory for IOCsandindicators.
OWASP Security Best Practices now combines 5 tracked sources with 1 trusted upstream skill packs. Instead of waiting on a single fixed link, it tracks canonical feeds, discovers new docs from index-like surfaces, and folds those deltas into sandbox-usable guidance.
High-trust, high-stakes guidance with immediate value across almost every repo.
Discovery process
1. Track canonical signals
Monitor 4 feed-like sources for release notes, changelog entries, and durable upstream deltas.
2. Discover net-new docs and leads
Scan 1 discovery-oriented sources such as docs indexes and sitemaps, then rank extracted links against explicit query hints instead of trusting nav order.
3. Transplant from trusted upstreams
Fold implementation patterns from Security Best Practices so the skill inherits a real operating model instead of boilerplate prose.
4. Keep the sandbox honest
Ship prompts, MCP recommendations, and automation language that can actually be executed in Loop's sandbox instead of abstract advice theater.
Query hints
critical npm advisorysupply chainauthentication bypassrcesnyk blogsecurityvulnerabilitiesportswigger research
+Summary: OWASP Security Best Practices agent run was interrupted: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Pur
+What changed: Agent crashed mid-run after 0 search(es). (agent error: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Purchase credits at htt)
+Body changed: no
−Generated:2026-04-01T12:27:05.323Z
+Editor:openai/gpt-5-mini
−Summary: OWASP Security Best Practices now tracks Sign in and 3 other fresh signals.
−What changed: The biggest delta is Sign in. Fold the concrete changes into the operating notes, then discard the fluff.
−Body changed: yes
−Editor: heuristic-fallback
−Changed sections: Research-backed changes
Experiments:
+- Re-run after the issue is resolved.
+- Add a higher-signal source.
−- Addonenewtacticbasedon Sign in.
+- Checkgatewaycreditsorratelimits.
−- Rewrite one stale section with today's source language.
−- Turn the top signal into a reusable agent prompt.
Signals:
+- Better Auth Has Two-Factor Authentication Bypass via Premature Session Caching (session.cookieCache) (GitHub Security Advisories)
- Sign in (GitHub Security Advisories)
- Sign up (GitHub Security Advisories)
−- @tinacms/graphql's`FilesystemBridge`PathValidationCanBe Bypassed via SymlinksorJunctions (GitHub Security Advisories)
+- SignalKServer:ArbitraryPrototypeRead via `from`FieldBypass (GitHub Security Advisories)
−- Parse Server has a LiveQuery protected-field guard bypass via array-like logical operator value (GitHub Security Advisories)
Update history3▶
Apr 3, 20264 sources
OWASP Security Best Practices agent run was interrupted: Free credits temporarily have rate limits in place due to abuse. We are working on a resolution. Try again later, or pay for credits which continue to have unrestricted access. Pur
Apr 1, 20264 sources
OWASP Security Best Practices now tracks Sign in and 3 other fresh signals.