Secure Coding Practices for Developers

intermediate
11 min

Secure Coding Practices for Developers

Security is mostly won or lost in the small decisions you make while writing ordinary code: how you handle input, errors, secrets and dependencies. This lesson distills the daily habits that prevent the majority of vulnerabilities, independent of language or framework.

Validate Input at the Boundary

Every piece of external data, from users, APIs, files, headers or message queues, is untrusted until validated. Validate at the boundary of your system, as early as possible:

  • Prefer allowlists (what is permitted) over blocklists (what is forbidden); attackers are more creative than your blocklist.
  • Validate type, length, range and format. Use schema validators (Zod, Joi, JSON Schema, Pydantic) so rules are declarative and testable.
  • Canonicalize before validating: decode URL encoding and normalize paths first, or ..%2F sneaks past your .. check.
javascript
// Declarative input validation with Zod const { z } = require('zod'); const CreateUser = z.object({ email: z.string().email().max(254), name: z.string().min(1).max(100).regex(/^[\p{L} '-]+$/u), age: z.number().int().min(13).max(120), }); app.post('/users', (req, res) => { const parsed = CreateUser.safeParse(req.body); if (!parsed.success) return res.status(400).json({ error: 'Invalid input' }); createUser(parsed.data); // only validated, typed data proceeds res.status(201).end(); });

Fail Securely

Errors should never leave the system in a permissive state or leak internals. Return generic messages to clients, log details server-side, and make the default outcome of any failed check a denial. A try/catch that swallows an authorization error and continues is a vulnerability, not resilience.

Keep Secrets Out of Code

API keys, database passwords and signing keys do not belong in source files or git history. Use environment variables at minimum, a secrets manager (Vault, AWS Secrets Manager, Doppler) for real systems. Add secret-scanning (gitleaks, GitHub secret scanning) to CI so an accidental commit is caught before it ships, and rotate any secret that ever leaks; deleting the commit is not enough.

Handle Dependencies Deliberately

Modern apps are mostly other people's code. Practices that keep that safe:

  • Lock versions with lockfiles and integrity hashes.
  • Run automated vulnerability scanning (npm audit, pip-audit, Dependabot) in CI and actually triage the results.
  • Prefer fewer, well-maintained packages; every dependency is attack surface, as supply-chain attacks on popular packages keep proving.

Use Safe APIs by Default

Whole vulnerability classes disappear when you choose the safe API shape:

  • Parameterized queries instead of string-built SQL.
  • execFile with an argument array instead of exec with a concatenated shell string.
  • textContent and framework templating instead of innerHTML.
  • Path joins that are checked against a base directory instead of raw user-supplied paths, preventing path traversal.
  • Constant-time comparison functions for tokens and signatures.

Least Privilege Everywhere

Run processes as non-root users, scope API tokens to the minimum permissions and shortest lifetime, give the app's database account only the statements it needs, and separate duties between services. When something is compromised, least privilege is what decides between an incident and a catastrophe.

Make Review and Testing Security-Aware

  • Add security checks to code review: any new input path, any raw SQL, any use of dangerous sinks gets extra eyes.
  • Run static analysis (Semgrep, CodeQL, ESLint security plugins) in CI to catch known bad patterns mechanically.
  • Write abuse tests alongside unit tests: oversized inputs, wrong users accessing each other's resources, replayed tokens.
  • Keep a lightweight threat model per feature: what are we protecting, from whom, and what is the worst that happens here?

Key Takeaways

  • Validate all external input with allowlist schemas at the system boundary.
  • Fail closed: errors deny by default and never leak internals.
  • Secrets live in managers, not in code or git history; scan and rotate.
  • Choose inherently safe APIs and lock down dependencies.
  • Bake security into review, static analysis and tests rather than saving it for audits.

Next lesson: Network Security Basics: Firewalls, VPNs and Ports, zooming out from code to the network around it.

Secure Coding Practices for Developers - Cyber Security | CodeYourCraft | CodeYourCraft