--- title: "Secure Coding Practices Across Languages" description: "Language-agnostic secure coding fundamentals: input validation, output encoding, secrets management, least privilege, dependency and SCA scanning, and secure-by-default design applicable across Python, JavaScript, Rust, Go, and other stacks." language: null framework: null category: security_general tags: - security - input-validation - secrets-management - authentication - authorization - owasp - dependency-management - best-practices keywords: - secure coding practices - input validation vs output encoding - secrets management environment variables vault - least privilege principle software - dependency scanning sca sbom - secure defaults software design last_updated: 2026-07-07 difficulty: intermediate related: - ./owasp_top_10.md - ./vulnerability_scanning.md - ./osint_reconnaissance.md - ./penetration_testing_methodology.md - ../principles/solid.md - ../../languages/python/frameworks/fastapi/best_practices.md - ../devops/docker_best_practices.md - ../devops/ci_cd_patterns.md - ../devops/autonomous_least_privilege_environments.md - ../../tools/git/best_practices.md - ../../tools/docker/dockerfile_best_practices.md - ../architecture/microservices.md - ../testing/integration_testing.md search_priority: high status: published --- # Secure Coding Practices Across Languages ## Introduction / Overview Secure coding is the practice of writing software that resists misuse by design, not by relying on a perimeter (firewall, WAF, network segmentation) to catch what the code itself should never allow. In 2026, with software supply chains, cloud-native deployments, and AI-assisted code generation all expanding the volume and speed of code shipped, secure-by-default habits at the code level are the highest-leverage control available to an individual engineer. This document is deliberately language-agnostic: the principles (validate at the boundary, encode at the sink, never trust client input, least privilege everywhere) apply identically whether the implementation is Python, JavaScript/TypeScript, Rust, Go, or Java. For the specific, current catalog of web application risk categories these practices defend against, see [OWASP Top 10](./owasp_top_10.md). **When to use this guidance**: Any code that accepts external input (HTTP requests, file uploads, message queue payloads, CLI arguments, environment configuration), handles credentials or secrets, or has a software supply chain (i.e., virtually all modern software). ## Core Concepts - **Trust boundary**: The point where data crosses from an untrusted source (user input, third-party API, another service) into your system's control. All validation must happen at or before this boundary - never assume upstream validation was sufficient. - **Validate on input, encode on output**: These are two distinct operations often conflated. Validation rejects or normalizes data that does not conform to an expected shape; encoding transforms data for the specific context it will be rendered/interpreted in (HTML, SQL, shell, JSON) to neutralize its ability to be interpreted as code in that context. - **Least privilege**: Every component, service account, and process should hold the minimum permissions required to perform its function, and nothing more, minimizing the impact of any single compromise. - **Secure defaults**: A system's default configuration should be the safe one; developers should have to explicitly opt into weaker security, not the reverse. - **Defense in depth**: No single control is assumed sufficient. Input validation, parameterized queries, least-privilege database accounts, and output encoding are layered so that a failure in one layer does not automatically become an exploit. ## Best Practices ### 1. Validate All External Input at the Trust Boundary **Rationale**: Input validation is the single highest-leverage control against injection, deserialization, and logic-abuse vulnerabilities. Validation must happen server-side; client-side validation is a UX convenience only. ```python from pydantic import BaseModel, ConfigDict, Field class TransferRequest(BaseModel): model_config = ConfigDict(extra="forbid") source_account_id: int = Field(gt=0) destination_account_id: int = Field(gt=0) amount_cents: int = Field(gt=0, le=10_000_000) memo: str = Field(max_length=280, pattern=r"^[\w\s.,-]*$") ``` **Why this works well in production**: - `extra="forbid"` rejects unexpected fields outright rather than silently discarding them, closing off mass-assignment style attacks. - Explicit numeric bounds (`gt`, `le`) prevent both logic errors (negative transfers) and resource-exhaustion abuse (absurdly large values). ### 2. Encode Output for the Specific Sink It Targets **Rationale**: The same string requires different encoding depending on where it is rendered - HTML body, HTML attribute, JavaScript context, SQL query, shell command, or URL. Applying the wrong encoding (or none) is the root cause of most injection classes (see [OWASP Top 10 - Injection](./owasp_top_10.md#a05-injection)). ```javascript // Correct: framework auto-escapes on render (React, template engines with autoescape on) const el =
{userSuppliedComment}
; // React escapes by default // Dangerous: bypasses encoding entirely const el = ; ``` **Why this works well in production**: Modern frameworks (React, Django templates, Jinja2 with autoescape, Vue) encode by default; the security-critical discipline is recognizing and minimizing the explicit "raw"/"unsafe" escape hatches (`dangerouslySetInnerHTML`, `{% autoescape off %}`, `v-html`) and treating every use of one as requiring explicit sanitization review. ### 3. Never Hard-Code Secrets - Centralize Secrets Management **Rationale**: Secrets committed to source control persist in history indefinitely (even after deletion) and are the most common root cause of credential-leak incidents. ```python # Wrong DATABASE_PASSWORD = "hunter2" # Right: load from environment, backed by a secrets manager in production import os from pydantic_settings import BaseSettings class Settings(BaseSettings): database_password: str model_config = {"env_file": ".env"} settings = Settings() ``` **Why this works well in production**: - `.env` files are used only for local development and are excluded via `.gitignore`; production injects secrets via the platform's secret store (AWS Secrets Manager, HashiCorp Vault, Kubernetes Secrets backed by an external provider) at runtime. - Centralizing secret loading through one `Settings` object makes rotation and auditing tractable - grep the codebase for a single access pattern instead of scattered `os.environ` calls. ### 4. Apply Least Privilege to Every Credential and Process **Rationale**: A leaked or compromised credential should grant the smallest possible blast radius. This applies to database users, cloud IAM roles, container runtime permissions, and CI pipeline tokens alike. ```sql -- Application database user: scoped to exactly what the app needs GRANT SELECT, INSERT, UPDATE ON accounts, transactions TO app_user; REVOKE ALL ON schema_migrations FROM app_user; ``` **Why this works well in production**: If the application-tier credential leaks, the attacker cannot alter migration history, drop tables, or read unrelated schemas - containment is enforced at the data layer itself, not just application logic. The same logic applies to container users (see [Docker Best Practices](../devops/docker_best_practices.md#4-run-as-a-non-root-user)) and CI pipeline OIDC-scoped credentials (see [CI/CD Patterns](../devops/ci_cd_patterns.md)). This principle is not limited to credentials and database users - it applies equally to the tooling and execution environment itself (installers, build systems, runtime users), which is the specific focus of [Autonomous, Least-Privilege, Portable Execution Environments](../devops/autonomous_least_privilege_environments.md). ### 5. Use Parameterized Queries and ORMs - Never String-Concatenate Untrusted Data Into Interpreters **Rationale**: SQL injection, command injection, and template injection all share the same root cause: untrusted data concatenated into a string that is then interpreted as code. ```python # Wrong: vulnerable to SQL injection cursor.execute(f"SELECT * FROM users WHERE email = '{email}'") # Right: parameterized, driver escapes the value correctly for its context cursor.execute("SELECT * FROM users WHERE email = %s", (email,)) ``` **Why this works well in production**: The database driver treats `email` strictly as data, never as part of the query grammar, regardless of its content - this eliminates the entire injection class rather than attempting to filter dangerous characters, which is reliably incomplete. ### 6. Scan Dependencies Continuously, Not Just at Onboarding Time **Rationale**: Transitive dependencies accumulate new CVEs continuously after a project is first built; a clean scan at project creation says nothing about risk six months later. ```yaml # CI pipeline step - fails the build on known-exploitable high-severity issues - name: Software composition analysis run: pip-audit --strict --vulnerability-service osv ``` **Why this works well in production**: Wiring SCA (Software Composition Analysis) into every pipeline run (see [CI/CD Patterns](../devops/ci_cd_patterns.md)) turns dependency risk from a periodic manual audit into a continuously enforced gate, directly addressing the *Software Supply Chain Failures* category in the [OWASP Top 10](./owasp_top_10.md). ### 7. Design Secure Defaults Into Every Configuration Surface **Rationale**: Defaults are what most deployments actually run, because most operators never change them. A framework or library that ships an insecure default (verbose debug output enabled, permissive CORS, disabled TLS verification) will have that insecurity reproduced across every downstream deployment that never overrides it. ```python class Settings(BaseSettings): debug: bool = False # never default to True cors_allowed_origins: list[str] = [] # empty, explicit allow-list required tls_verify: bool = True # never silently disable certificate checks ``` **Why this works well in production**: An engineer under deadline pressure who never touches these settings still ends up with a secure configuration; insecurity requires an explicit, reviewable opt-out rather than being the silent default. ## Common Pitfalls & Anti-Patterns ### Pitfall: Relying on Client-Side Validation as the Only Check **Problem**: Client-side validation (HTML5 `required`, JavaScript checks) can be trivially bypassed by calling the API directly with a tool like `curl` or a modified request. **Recommended approach**: Treat client-side validation as UX only. Every constraint must be re-enforced server-side, as shown in Best Practice #1. ### Pitfall: Blocklisting Dangerous Patterns Instead of Allowlisting Valid Ones **Problem**: Attempting to filter out "bad" characters or patterns (`