From 20d1a864386da9af7e29f9e30d9fad41ea577cec Mon Sep 17 00:00:00 2001 From: Philipp Mieden Date: Fri, 10 Oct 2025 11:15:54 +0200 Subject: [PATCH] docs: react security guide update --- foomo/docs/security/react.md | 279 +++++++++++++++++++++++++++++++---- 1 file changed, 252 insertions(+), 27 deletions(-) diff --git a/foomo/docs/security/react.md b/foomo/docs/security/react.md index 5c5d2ab..60e53fc 100644 --- a/foomo/docs/security/react.md +++ b/foomo/docs/security/react.md @@ -131,6 +131,7 @@ Modern applications rely heavily on third-party packages. yarn audit ``` - **Keep Packages Updated**: Use tools like Dependabot to automatically create pull requests for dependency updates. +- **Reduce Dependency Footprint**: Each dependency increases the attack surface. Before adding a new package, consider if the functionality can be achieved with native JavaScript/browser APIs. Modern JavaScript has many built-in capabilities that previously required external libraries. ### API Security @@ -155,6 +156,28 @@ Modern applications rely heavily on third-party packages. const analyticsId = process.env.NEXT_PUBLIC_ANALYTICS_ID; // "public_value" ``` +#### Advanced Secrets Management +Storing secrets in plaintext `.env` files poses a security risk. If an attacker gains access to your environment through a supply chain attack or other means, they can easily exfiltrate these secrets. A more secure approach is to use a secrets management solution. + +These tools store only references in your `.env` file, and the actual secret values are fetched just-in-time, often requiring additional authentication (like Touch ID). + +*Example with references:* +```env +DATABASE_PASSWORD=op://vault/database/password +API_KEY=infisical://project/env/api-key +``` + +You would then use the secret manager's CLI to inject the secrets into your application's environment at runtime. + +*Example of running an application:* +```bash +# Using 1Password CLI +op run -- npm start + +# Using Infisical CLI +infisical run -- npm start +``` + ### Server-Side Rendering (SSR) When using SSR, ensure that any data included in the initial server-rendered page is properly sanitized, especially if it includes user-generated content. This prevents XSS during the client-side hydration phase. @@ -165,6 +188,8 @@ A CSRF attack tricks a victim into submitting a malicious request. It inherits t To prevent CSRF, you should use anti-CSRF tokens. These are unique, secret, and unpredictable values generated by the server and sent to the client. The client includes this token in subsequent requests. The server validates the token before processing the request. +Many modern authentication patterns, such as those using JWTs (JSON Web Tokens) stored in `localStorage`, are inherently stateless and may not be as vulnerable to traditional CSRF attacks that rely on session cookies. However, if you are using session-based authentication, especially with cookies, implementing anti-CSRF tokens is crucial. Setting the `SameSite` attribute on your cookies (e.g., `SameSite=Strict` or `SameSite=Lax`) also provides a strong layer of defense. + ### Other Vulnerabilities - **Zip Slip**: This vulnerability occurs when handling zip files. An attacker can craft a malicious archive that, when extracted, overwrites files in the filesystem. If your application handles file uploads, particularly archives, use a library that is not vulnerable to path traversal to extract files. @@ -172,6 +197,42 @@ To prevent CSRF, you should use anti-CSRF tokens. These are unique, secret, and - **Broken Authentication**: This is a broad category of vulnerabilities that can occur when authentication and session management are not handled correctly. Common issues include weak passwords, session tokens being exposed in URLs, and improper invalidation of sessions after logout. Always use secure, well-vetted authentication libraries and follow best practices for session management. - **Distributed Denial of Service (DDoS)**: While primarily a backend and infrastructure concern, your frontend application can be a target. DDoS attacks aim to make your application unavailable to users by overwhelming it with traffic. Implementing rate limiting on your APIs and using services that provide DDoS protection are common mitigation strategies. +- **Clickjacking**: This attack tricks a user into clicking something different from what they perceive, potentially revealing confidential information or taking control of their computer while clicking on seemingly innocuous objects, including web pages. To prevent this, you can use the `X-Frame-Options` HTTP header. + +## Component-Specific Security + +### Prop Validation +Always validate the props passed to your components, especially if they receive data from an API or user input. While `PropTypes` can be useful in development for checking data types, a more robust solution for validation is to use a library like Zod. This is especially important in Server Actions or API routes where you need to be certain about the shape and content of the data. + +*Example of Zod validation in a Server Action:* +```typescript +import { z } from 'zod'; + +const schema = z.object({ + name: z.string().min(1), + email: z.string().email(), +}); + +export async function updateUser(formData: FormData) { + const parsed = schema.safeParse({ + name: formData.get('name'), + email: formData.get('email'), + }); + + if (!parsed.success) { + // Handle validation error + return { error: parsed.error.format() }; + } + + // ... proceed with valid data ... +} +``` + +### Securing Third-Party Components +Be cautious when using third-party components, as they can introduce security vulnerabilities. +- **Vet the library**: Choose popular, well-maintained libraries with a good security track record. +- **Isolate and sandbox**: If you need to render a third-party component that you don't fully trust, consider rendering it in an `iframe` with the `sandbox` attribute to limit its capabilities. +- **Control props**: Be careful about the data you pass to third-party components. Avoid passing down sensitive information if it's not strictly necessary. ## Next.js Security @@ -262,31 +323,43 @@ Server Actions are functions that execute on the server. They must be secured pr } ``` -## Security Checklist +## Security Headers +In addition to a Content Security Policy (CSP), several other HTTP headers are essential for securing your application. You can set these in your `next.config.js` file. -### 1. Package Manager -It is also recommended to use `pnpm` over `npm` or `yarn`. `pnpm` is more efficient with disk space and is generally faster. From a security perspective, its non-flat `node_modules` structure provides stricter package isolation, reducing the risk of unauthorized package access. Furthermore, `pnpm` offers advanced security features, such as `minimumReleaseAge`, which can delay the installation of new package versions to protect against supply-chain attacks where malicious code is published and quickly downloaded. +```javascript +// next.config.js +const securityHeaders = [ + // Prevents browser from guessing the content type + { + key: 'X-Content-Type-Options', + value: 'nosniff', + }, + // Prevents clickjacking + { + key: 'X-Frame-Options', + value: 'SAMEORIGIN', + }, + // Enforces HTTPS + { + key: 'Strict-Transport-Security', + value: 'max-age=63072000; includeSubDomains; preload', + }, +]; -You can configure this in your `pnpm-workspace.yaml` file: -```yaml -minimumReleaseAge: 1440 # 1440 minutes = 24 hours +module.exports = { + async headers() { + return [ + { + source: '/:path*', + headers: securityHeaders, + }, + ]; + }, +}; ``` -This setting will prevent the installation of any package version that is less than 24 hours old, giving time for malicious packages to be discovered and removed from the registry. -### 2. Dependencies -The first and most important step is to manage your dependencies. This is a significant source of risk, not just in terms of security but also licensing. It is important to keep your dependencies up to date, to make it easier to react when critical security patches are released. Use a tool like Dependabot to get notified about new versions of your dependencies and open pull requests to update them. - -### 3. Data Validation and Sanitization -Data validation and sanitization is crucial, especially for incoming data. This is particularly important for server-side code, which interacts with your database. You should never trust data coming from the client, as it can be easily manipulated. It is recommended to use a data access layer to centralize all your database interactions and perform authentication checks before accessing data. For sensitive information, consider using tools like Arcjet to redact or deny requests containing personal data like emails or phone numbers. - -### 4. Avoiding Code Exposure -Be mindful of exposing sensitive data or code to the client. Avoid hardcoding API keys or other secrets directly in your code. Instead, use environment variables to store them. For server-side code that should never be exposed to the client, you can use the server-only package, which will throw an error if it's accidentally imported into a client component. - -### 5. Centralize Security Functions -A well-organized security strategy involves centralizing key functions. This approach streamlines testing, auditing, and maintenance while reducing the risk of inconsistencies or oversights. Grouping functions by their purpose, for example, creating a data access layer for all database interactions, can improve maintainability and testability. - -### 6. Security Headers -Pay attention to the headers you are setting. A Content Security Policy (CSP) is important to guard your Next.js application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks. +### Content Security Policy (CSP) +A Content Security Policy (CSP) is important to guard your Next.js application against various security threats such as cross-site scripting (XSS), clickjacking, and other code injection attacks. Implementing a strict CSP in a React application requires careful configuration, especially regarding inline scripts. By default, Create React App embeds a small runtime script inline in `index.html`. To create a secure CSP, you must disable this behavior. You can do this by setting the `INLINE_RUNTIME_CHUNK=false` environment variable in your `.env` file or your build script: ```bash @@ -312,15 +385,166 @@ And in your HTML: ``` This ensures that only the scripts with the correct nonce will be executed, blocking any injected scripts that do not have the nonce. -### 7. Code Editor -Your code editor can also help you catch security issues before you commit. Enable linting tools like ESLint to catch syntax errors, style inconsistencies, and a potential security issues early on. Tools like TruffleHog can also be used to detect secrets that may have been accidentally included in your code. -### 9. Third-Party Security Scanners -In addition to the tools and practices mentioned above, it is highly recommended to use third-party security scanners to continuously monitor your code for vulnerabilities. These tools can be integrated into your development workflow and provide real-time feedback. +## Package Manager Security -- **[Snyk](http://snyk.io/code-checker/javascript/)**: Snyk offers a free code checker that can be integrated directly into your IDE. It uses AI to analyze your code for a wide range of security issues, including SQL injection, insecure password handling, and information disclosure. It provides actionable advice to help you fix vulnerabilities quickly. +A significant portion of frontend security revolves around how you manage your dependencies through package managers like `npm`, `yarn`, or `pnpm`. -- **[OWASP Dependency-Check](https://owasp.org/www-project-dependency-check/)**: This is a Software Composition Analysis (SCA) tool that detects publicly disclosed vulnerabilities within a project’s dependencies. It can be run via a command line interface, Maven plugin, or Jenkins plugin, and it generates reports that link to CVE entries. You can find its source code on [GitHub](https://github.com/dependency-check/DependencyCheck). +### Use a Stricter Package Manager + +It is recommended to use `pnpm` over `npm` or `yarn`. `pnpm`'s non-flat `node_modules` structure provides stricter package isolation, reducing the risk of unauthorized package access by dependencies. It is also more efficient with disk space and generally faster. + +### Disable Post-Install Scripts + +A common attack vector is the use of `postinstall` scripts in `package.json`. These scripts execute arbitrary code on your machine when a package is installed. You can mitigate this risk by disabling them. + +- **npm**: You can disable scripts globally or on a per-install basis. + ```bash + # Disable globally (recommended) + npm config set ignore-scripts true + + # Disable for a single installation + npm install --ignore-scripts + ``` +- **pnpm & Bun**: Both `pnpm` (v10+) and `bun` disable post-install scripts by default, offering an "escape hatch" to allow them for trusted dependencies if necessary. + +### Ensure Deterministic Installs in CI/CD + +To ensure that your builds are reproducible and use the exact dependency versions specified in your lock file, use `npm ci` instead of `npm install` in your continuous integration (CI) environments. This also provides a faster and more reliable installation process. + +```bash +npm ci +``` + +### Install with a Cooldown Period + +Attackers sometimes publish a new version of a package with malicious code, which gets quickly downloaded before it's discovered. You can add a "cooldown" period to delay the installation of the very latest package versions. + +- **npm**: Use the `--before` flag to only install packages published before a certain date. + ```bash + # Example: 7-day cooldown + npm install express --before="$(date -v -7d)" + ``` +- **pnpm**: `pnpm` has a built-in feature for this called `minimumReleaseAge`. You can configure it in your `.npmrc` or `pnpm-workspace.yaml` file. + ```yaml + # In .npmrc or pnpm-workspace.yaml + minimumReleaseAge: 1440 # 1440 minutes = 24 hours + ``` + This setting will prevent the installation of any package version that is less than 24 hours old. + +### Avoid Blind Dependency Upgrades + +While keeping dependencies updated is crucial, avoid blindly upgrading to the latest version with commands like `npm update`. This can introduce breaking changes or security vulnerabilities. + +Instead, use tools that provide more control and visibility: +- **Interactive Upgrades**: Use a tool like `npm-check-updates` in interactive mode to review changes before applying them. + ```bash + npx npm-check-updates --interactive + ``` +- **Automated Pull Requests**: Use services like Dependabot or Snyk to create automated pull requests for dependency updates. This allows you to run your test suite and review changes before merging. + + +## Secure Development Environment + +### Work in Dev Containers + +To limit the blast radius of a potential supply chain attack, consider using development containers (dev containers). A dev container provides an isolated, sandboxed environment for your project. If a malicious npm package executes code, it will be confined to the container, unable to access sensitive files or resources on your host machine. + +You can set one up by adding a `.devcontainer/devcontainer.json` file to your project and using an editor that supports them, like VS Code. + +*Example `devcontainer.json`:* +```json +{ + "name": "Node.js Dev Container", + "image": "mcr.microsoft.com/devcontainers/javascript-node:18", + "features": { + "ghcr.io/devcontainers/features/1password:1": {} + }, + "postCreateCommand": "npm ci" +} +``` + +## For Package Maintainers + +If you are a maintainer of npm packages, follow these best practices to secure your own packages and protect your users. + +### Enable Two-Factor Authentication (2FA) + +Protect your npm account from takeover by enabling two-factor authentication. This adds a critical layer of security, preventing unauthorized publishing of malicious versions of your packages. + +```bash +# Enable 2FA for both authentication and publishing +npm profile enable-2fa auth-and-writes +``` + +### Publish with Provenance + +Provenance provides verifiable proof of where and how your package was built. It creates a cryptographic link between your source code repository and the published package, assuring users that the package was not tampered with. + +You can enable this in your GitHub Actions workflow by granting `id-token: write` permissions and using the `--provenance` flag. + +```yaml +# In your GitHub Actions workflow .yml file +permissions: + id-token: write +steps: + - run: npm publish --provenance +``` + +### Publish with OIDC to Avoid Long-Lived Tokens + +Instead of using traditional `NPM_TOKEN` secrets in your CI/CD environment, use trusted publishing with OpenID Connect (OIDC). This method uses short-lived, auto-generated tokens that are scoped to your specific workflow, eliminating the risk of a long-lived token being leaked or stolen. + +When you configure trusted publishing on npmjs.com, you no longer need the `--provenance` flag, as it is enabled automatically. + +```yaml +# In your GitHub Actions workflow .yml file +permissions: + id-token: write +steps: + - run: npm publish +``` + +## Security Checklist + +This checklist provides a summary of actionable steps to secure your React and Next.js application. + +1. **Dependency Management** + - Use `pnpm` as your package manager for better package isolation. + - Disable `postinstall` scripts in your `npm` configuration. + - Use `npm ci` for deterministic installs in CI/CD environments. + - Configure a "cooldown" period (`minimumReleaseAge` in `pnpm`) for new packages. + - Regularly audit dependencies with `npm audit` or `yarn audit`. + - Use automated tools like Dependabot for dependency updates. + - Review dependency upgrades interactively; avoid blind updates. + - Minimize your dependency footprint. + +2. **Code & Data Security** + - Sanitize user input to prevent XSS, especially when using `dangerouslySetInnerHTML`. + - Avoid direct DOM manipulation with `refs` and `innerHTML`. + - Validate URLs to prevent `javascript:` scheme attacks. + - Implement anti-CSRF tokens if using session-based authentication. + - Use `server-only` and `client-only` packages to enforce component boundaries. + - Validate and sanitize all data in Server Actions and API routes (e.g., with Zod). + +3. **Secrets Management** + - Never hardcode secrets in your codebase. + - Use environment variables, prefixing client-exposed variables with `NEXT_PUBLIC_`. + - For higher security, use a secrets management tool (e.g., 1Password CLI, Infisical) to avoid plaintext secrets in `.env` files. + +4. **Infrastructure & Configuration** + - Implement strict Content Security Policy (CSP) and other security headers (`X-Content-Type-Options`, `X-Frame-Options`, etc.). + - Use Dev Containers for an isolated development environment. + - Implement rate limiting on APIs to prevent DDoS attacks. + +5. **For Package Maintainers** + - Enable Two-Factor Authentication (2FA) on your npm account. + - Publish packages with provenance (`--provenance`). + - Use OIDC for publishing to avoid long-lived tokens. + +6. **Tooling & Automation** + - Use third-party security scanners like Snyk or OWASP Dependency-Check. + - Enable security linting rules in your code editor. ## Sources @@ -335,3 +559,4 @@ In addition to the tools and practices mentioned above, it is highly recommended - [OWASP Dependency-Check](https://owasp.org/www-project-dependency-check/) - [OWASP Dependency-Check GitHub Repository](https://github.com/dependency-check/DependencyCheck) - [OWASP France - CSP with React](https://owasp.org/www-chapter-france/event/2020/2020-09-10_csp_with_react.pdf) +- [npm Security Best Practices by Liran Tal](https://github.com/lirantal/npm-security-best-practices)