react-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited react-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Security patterns for React applications — XSS prevention, Server Actions authorization, sensitive data protection, and client-side security. These issues cause real breaches; treat each section as mandatory for production code.
React automatically escapes string values rendered in JSX. The primary XSS risk is intentionally bypassing that protection.
Every use requires sanitization. No exceptions.
import DOMPurify from 'dompurify'; // or isomorphic-dompurify for SSR
// BAD: XSS if content contains <script> or event attributes
<div dangerouslySetInnerHTML={{ __html: userContent }} />
// GOOD: sanitize before rendering
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userContent) }} />Sanitize at the point of use, not earlier. Data transforms lose sanitization guarantees.
href values — javascript: URLs are XSS vectors. Use URL parsing and allow-lists for external links.dangerouslySetInnerHTML. Use textContent for text-only updates.Server Actions are public HTTP POST endpoints. They run on the server but are callable by anyone — treat them like API routes.
// app/actions.ts
'use server'
import { auth } from '@/lib/auth';
import { db } from '@/lib/db';
export async function deletePost(postId: string) {
// 1. Verify the user is authenticated
const session = await auth();
if (!session?.user) throw new Error('Unauthorized');
// 2. Verify the user owns THIS resource (prevents IDOR)
const post = await db.post.findUnique({ where: { id: postId } });
if (!post || post.authorId !== session.user.id) {
throw new Error('Forbidden');
}
await db.post.delete({ where: { id: postId } });
}IDOR (Insecure Direct Object Reference): If you skip the ownership check, any authenticated user can delete any post by guessing IDs. Page-level auth does NOT protect actions.
FormData through Zod before using it// lib/dal.ts — SERVER ONLY
import 'server-only';
import { auth } from './auth';
export async function getCurrentUser() {
const session = await auth();
if (!session) return null;
// Return minimal DTO — never the full DB record
return { id: session.user.id, name: session.user.name, role: session.user.role };
}// Any server-only file
import 'server-only'; // Build error if imported from a Client ComponentThe server-only package causes a build-time error if the module is accidentally imported into a Client Component. Use it on every file that accesses secrets, env vars, or the database.
Explicitly mark objects and values as un-passable to Client Components:
import { experimental_taintObjectReference, experimental_taintUniqueValue } from 'react';
// Mark entire object — prevents passing to Client Component
experimental_taintObjectReference('Do not pass user record to client', user);
// Mark specific value — prevents passing token to client
experimental_taintUniqueValue('Do not pass token to client', process, process.env.API_SECRET);NEXT_PUBLIC_* variables are exposed to the browser. Use for non-secret config only.process.env.* variables stay server-side — never read them in Client Components.| Storage | Risk | Recommendation |
|---|---|---|
localStorage | Accessible to any JS on the page — XSS attack steals token | Never for auth tokens |
sessionStorage | Same XSS risk as localStorage | Never for auth tokens |
httpOnly cookie | JS cannot read it — XSS cannot steal it | Use this |
// BAD: XSS can steal the token
localStorage.setItem('auth_token', token);
// GOOD: set from server response with httpOnly flag
// res.cookie('session', token, { httpOnly: true, secure: true, sameSite: 'lax' });// BAD: deep merging user-controlled objects can inject __proto__ keys
import merge from 'lodash.merge';
const config = merge({}, userInput); // if userInput contains __proto__
// GOOD: validate schema before merging
import { z } from 'zod';
const schema = z.object({ theme: z.string(), language: z.string() });
const safe = schema.parse(userInput);
const config = Object.assign({}, defaults, safe);Always validate JSON.parse results against a Zod schema before using user-controlled data.
Patterns that indicate hardcoded secrets in React code (flag in every code review):
apiKey = "...", api_key: "...", API_KEY = "..."token = "Bearer ...", authorization: "...", password = "..."sk-, pk_, ghp_, xoxb-, AKIAThese must be moved to environment variables and accessed via process.env on the server.
// BAD: user PII and tokens visible in browser DevTools
console.log('User:', user); // may include email, address, token
// GOOD: guard dev-only logging
if (process.env.NODE_ENV === 'development') {
console.log('User ID:', user.id); // log only what you need
}~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.