Secure developmentOct 24, 202610 min read

Practical Security Practices for Your Codebase

A practical security baseline for any codebase: audit and update dependencies, add automated analysis, and protect every input and server-side action.

Tymoteusz GłuchDeveloper & Researcher
Dependency audits, SAST checks, CI, and package updates

Start with the security basics

Good application security is not one scanner or a final checklist before release. It is a set of small practices applied continuously: know what code and dependencies you ship, validate data at trust boundaries, restrict access, and make it easy to notice and fix problems.

Start with the controls that reduce common, preventable risks. Keep dependencies current, protect secrets, validate input on the server, enforce authorization for every sensitive operation, and run automated checks in CI. More advanced tools are useful, but they work best when these fundamentals are already in place.

  • Automate dependency and source-code checks.
  • Validate untrusted data where it enters the server.
  • Check authorization for every action and resource.
  • Keep secrets out of client bundles, logs, and repositories.

Audit dependencies and update them regularly

Third-party packages are part of your application's attack surface. Run your package manager's vulnerability audit regularly and whenever dependencies change. For npm, `npm audit` compares the dependency tree with published security advisories. It is useful for identifying known affected versions, but it does not inspect your application source code and cannot prove that a package is safe.

Use a lockfile and install reproducibly in CI so that local and production builds use the reviewed dependency graph. Schedule routine updates—weekly or monthly is a useful starting point—and enable automated update pull requests where practical. Review changelogs, test the upgrade, and merge security fixes promptly rather than allowing a large backlog to accumulate.

Do not blindly apply forceful upgrades. A command such as `npm audit fix --force` can introduce breaking major-version changes. Understand which dependency is affected, whether a patched version exists, and what behavior changes before accepting an upgrade. Remove unused packages and prefer well-maintained dependencies with a clear release and security-response history.

  • Run audits in CI and review new findings instead of relying on a one-time scan.
  • Update direct and transitive dependencies in small, tested batches.
  • Commit the lockfile and use reproducible installs for deployments.
  • Prioritize reachable, exploitable, and high-severity issues, while tracking accepted exceptions.

Add SAST and secret scanning to CI

Static application security testing (SAST) analyzes source code for risky patterns without running the application. It can catch issues such as unsafe data flows, injection risks, or insecure API usage early in a pull request. Add a scanner that matches your language and framework, run it automatically, and make important findings visible to the team.

SAST complements, rather than replaces, dependency auditing, code review, tests, and runtime protections. Scanners can produce false positives and can miss context-specific bugs. Start by fixing high-confidence findings, document any accepted exceptions, and tune noisy rules so that developers continue to pay attention to the results.

Add secret scanning as well. It can detect accidentally committed credentials before they spread. If a real secret is exposed, deleting the line is not enough: revoke or rotate the credential, review how it could have been used, and remove it from repository history when appropriate.

Treat every input as untrusted

Input validation is essential even when the user interacts through your own frontend. Browser-side checks improve the experience, but they are under the caller's control and can be bypassed. A request can be sent directly to an endpoint or server action, so validate types, formats, sizes, and allowed values again on the server before using the data.

Prefer schemas and allow-lists over trying to remove a few suspicious characters. Set length and range limits, reject unexpected fields when practical, and normalize values consistently. Validation answers whether input has the expected shape; safe output encoding and parameterized queries are still needed to prevent cross-site scripting and injection.

Avoid building SQL, shell commands, file paths, or HTML by concatenating raw user input. Use parameterized database queries and framework escaping, and do not pass user-controlled strings to a shell. Apply limits to uploads and expensive operations so that malformed or oversized input cannot exhaust memory, CPU, or storage.

Secure server actions and API endpoints

A server action is still a server entry point, even if it is called from a form or button in your frontend. Do not assume that hiding a button or checking a value in a React component protects the operation. A caller may submit a crafted request without using the page at all.

For each action, validate its arguments on the server, authenticate the caller, and check that the caller is authorized to perform that exact operation on that exact record. Authentication answers who the caller is; authorization answers whether they may change this resource. Enforce ownership and role checks before reading or modifying protected data, and default to denying access when a check is missing.

Also consider rate limits, replay or duplicate submissions, and the cost of the requested work. Use the framework's supported request protections, but do not treat them as a replacement for authorization. Return only the data the caller is allowed to see, and avoid exposing internal errors or secrets in responses.

  • Validate server-action and API input even if the same form validates it in the browser.
  • Check authentication and resource-level authorization inside the server-side operation.
  • Apply rate limits or quotas to expensive or sensitive actions.

Protect access, data, and secrets

Use least privilege throughout the application. Give users, services, database accounts, and CI jobs only the permissions they need. Check access for each sensitive operation instead of trusting an earlier page load, and prefer explicit deny-by-default rules.

Keep credentials on the server and out of source control. In Next.js, variables prefixed with `NEXT_PUBLIC_` are intended to be exposed to the browser, so never use that prefix for private keys or tokens. Store secrets in an approved secret manager or protected deployment settings, restrict who can read them, rotate them when exposed, and make sure logs do not print them.

Use secure session settings and transport protections appropriate to your framework, including HTTPS and secure, HTTP-only cookies for session identifiers. Encrypt sensitive data where required, and collect only what the application needs. Security logs should help investigate important actions without copying passwords, tokens, or unnecessary personal data.

Make the delivery pipeline part of the defense

A secure development workflow should make safe changes easy to merge and risky changes harder to ship unnoticed. Protect the default branch, require review for sensitive code, run tests and security checks before merging, and keep deployment credentials separate from ordinary build jobs.

Limit CI token permissions and avoid exposing secrets to untrusted pull-request code. Pin or review third-party CI actions, protect release credentials, and make sure a failed critical security check is visible to maintainers. A pipeline is itself privileged infrastructure, so apply the same least-privilege and update practices to it as to the application.

  • Require tests and security checks before deployment.
  • Give CI jobs only the permissions they need.
  • Do not expose production secrets to untrusted pull requests.
  • Keep a clear process for triaging findings and releasing urgent fixes.

Build a routine your team can sustain

Security improves when it is part of ordinary engineering work. Assign ownership for dependency updates, review scanner findings regularly, and agree on how quickly the team responds to critical issues. Add tests for security bugs that have been fixed so the same mistake is less likely to return.

No checklist eliminates every risk, and no tool can certify an application as secure. A practical baseline—audits and regular package updates, SAST and secret scanning, server-side validation, authorization checks, least privilege, and a protected delivery pipeline—reduces common weaknesses and makes remaining problems easier to discover and address.