secure-coding-rules 2.0.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +71 -60
- package/package.json +5 -5
- package/src/__tests__/adapters.test.js +201 -0
- package/src/__tests__/loader.test.js +68 -0
- package/src/adapters/agents.js +2 -2
- package/src/adapters/claude.js +42 -3
- package/src/adapters/copilot.js +46 -2
- package/src/i18n.js +271 -0
- package/src/index.js +222 -56
- package/src/loader.js +33 -20
- package/src/prompts.js +79 -77
- package/src/templates/frameworks/express-security.md +130 -0
- package/src/templates/frameworks/nextjs-security.md +149 -0
- package/src/templates/frameworks/react-security.md +120 -0
- package/src/templates/typescript/typescript-security.md +113 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# React Security Rules
|
|
2
|
+
|
|
3
|
+
> Security rules for React and Next.js frontend applications, covering XSS prevention, state management, and secure component patterns.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Never Use `dangerouslySetInnerHTML` Without Sanitization
|
|
8
|
+
- **DO**: Use DOMPurify or a trusted sanitization library to sanitize HTML before rendering. Prefer rendering structured data with JSX instead of raw HTML.
|
|
9
|
+
- **DON'T**: Pass user-supplied or external data directly to `dangerouslySetInnerHTML`. Never assume HTML content from APIs is safe.
|
|
10
|
+
- **WHY**: `dangerouslySetInnerHTML` bypasses React's built-in XSS protection and injects raw HTML into the DOM. Unsanitized input leads directly to stored or reflected XSS attacks.
|
|
11
|
+
|
|
12
|
+
### 2. Prevent Memory Leaks and Race Conditions in useEffect
|
|
13
|
+
- **DO**: Return a cleanup function from `useEffect` to cancel async operations (use `AbortController`), clear timers, and unsubscribe from listeners. Use the cleanup flag pattern for async effects.
|
|
14
|
+
- **DON'T**: Fire async requests in `useEffect` without cancellation logic. Ignoring cleanup causes state updates on unmounted components and race conditions.
|
|
15
|
+
- **WHY**: Uncancelled async operations can update state after unmount, causing errors and potentially processing stale or attacker-manipulated responses from superseded requests.
|
|
16
|
+
|
|
17
|
+
### 3. Never Store Sensitive Data in React State or Context
|
|
18
|
+
- **DO**: Keep tokens in `httpOnly` cookies managed by the server. If client-side storage is unavoidable, use short-lived tokens with secure refresh mechanisms.
|
|
19
|
+
- **DON'T**: Store JWTs, API keys, passwords, or PII in `useState`, `useContext`, Redux, or any client-side state. Never put secrets in `localStorage` or `sessionStorage`.
|
|
20
|
+
- **WHY**: Client-side state is fully accessible via browser DevTools, XSS attacks, and browser extensions. Any data in React state should be considered public to the client.
|
|
21
|
+
|
|
22
|
+
### 4. Never Pass Sensitive Data Through Props
|
|
23
|
+
- **DO**: Fetch sensitive data only where it is needed. Use server-side rendering or secure API calls within the consuming component itself.
|
|
24
|
+
- **DON'T**: Pass tokens, secrets, or full user records through component props chains. Props are visible in React DevTools and can leak through component trees.
|
|
25
|
+
- **WHY**: Prop drilling exposes sensitive data across the component tree. React DevTools displays all props in plaintext, and any parent component re-render can unintentionally log or expose prop values.
|
|
26
|
+
|
|
27
|
+
### 5. Guard Server Components Against Data Leakage
|
|
28
|
+
- **DO**: Separate server-only logic into files with `"server-only"` import guard. Verify that server component data is filtered before passing to client components.
|
|
29
|
+
- **DON'T**: Import server-side utilities or secrets into client components. Never pass database records or internal IDs directly from server to client components without filtering.
|
|
30
|
+
- **WHY**: Next.js server components run on the server but their return values are serialized and sent to the client. Sensitive data in server component output ends up in the client-side HTML or RSC payload.
|
|
31
|
+
|
|
32
|
+
### 6. Use Error Boundaries with React.lazy Code Splitting
|
|
33
|
+
- **DO**: Wrap `React.lazy` components with `<Suspense>` and an `<ErrorBoundary>`. Log errors securely without exposing internal paths or stack traces to users.
|
|
34
|
+
- **DON'T**: Let lazy-loaded component failures crash the entire app or display raw error messages to users.
|
|
35
|
+
- **WHY**: Failed chunk loads (network errors, deploy mismatches) can crash the app. Without error boundaries, users see raw error details that may reveal internal architecture, file paths, or API endpoints.
|
|
36
|
+
|
|
37
|
+
### 7. Sanitize URL Schemes in Dynamic Links and Redirects
|
|
38
|
+
- **DO**: Validate that dynamic `href` values use `https:` or safe schemes. Use an allowlist for URL schemes. Encode URL parameters with `encodeURIComponent`.
|
|
39
|
+
- **DON'T**: Render user-supplied URLs in `<a href>` or `window.location` without validation. Never allow `javascript:`, `data:`, or `vbscript:` schemes.
|
|
40
|
+
- **WHY**: React does not sanitize `href` attributes. A `javascript:` URI in an anchor tag executes arbitrary code when clicked, leading to XSS.
|
|
41
|
+
|
|
42
|
+
## Code Examples
|
|
43
|
+
|
|
44
|
+
### Bad Practice
|
|
45
|
+
```jsx
|
|
46
|
+
// XSS via dangerouslySetInnerHTML
|
|
47
|
+
function Comment({ body }) {
|
|
48
|
+
return <div dangerouslySetInnerHTML={{ __html: body }} />;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Race condition — no cleanup in useEffect
|
|
52
|
+
function UserProfile({ userId }) {
|
|
53
|
+
const [user, setUser] = useState(null);
|
|
54
|
+
useEffect(() => {
|
|
55
|
+
fetch(`/api/users/${userId}`)
|
|
56
|
+
.then((res) => res.json())
|
|
57
|
+
.then((data) => setUser(data)); // Runs even if unmounted
|
|
58
|
+
}, [userId]);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Token stored in React state
|
|
62
|
+
function App() {
|
|
63
|
+
const [token, setToken] = useState(localStorage.getItem("jwt"));
|
|
64
|
+
return <AppContext.Provider value={{ token }}>{/* ... */}</AppContext.Provider>;
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### Good Practice
|
|
69
|
+
```jsx
|
|
70
|
+
import DOMPurify from "dompurify";
|
|
71
|
+
|
|
72
|
+
// Sanitized HTML rendering
|
|
73
|
+
function Comment({ body }) {
|
|
74
|
+
const clean = DOMPurify.sanitize(body);
|
|
75
|
+
return <div dangerouslySetInnerHTML={{ __html: clean }} />;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Proper cleanup with AbortController
|
|
79
|
+
function UserProfile({ userId }) {
|
|
80
|
+
const [user, setUser] = useState(null);
|
|
81
|
+
useEffect(() => {
|
|
82
|
+
const controller = new AbortController();
|
|
83
|
+
fetch(`/api/users/${userId}`, { signal: controller.signal })
|
|
84
|
+
.then((res) => res.json())
|
|
85
|
+
.then((data) => setUser(data))
|
|
86
|
+
.catch((err) => {
|
|
87
|
+
if (err.name !== "AbortError") console.error(err);
|
|
88
|
+
});
|
|
89
|
+
return () => controller.abort();
|
|
90
|
+
}, [userId]);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Error boundary with lazy loading
|
|
94
|
+
const LazyDashboard = React.lazy(() => import("./Dashboard"));
|
|
95
|
+
|
|
96
|
+
function App() {
|
|
97
|
+
return (
|
|
98
|
+
<ErrorBoundary fallback={<p>Something went wrong.</p>}>
|
|
99
|
+
<Suspense fallback={<p>Loading...</p>}>
|
|
100
|
+
<LazyDashboard />
|
|
101
|
+
</Suspense>
|
|
102
|
+
</ErrorBoundary>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Safe URL validation
|
|
107
|
+
function SafeLink({ href, children }) {
|
|
108
|
+
const isSafe = /^https?:\/\//i.test(href);
|
|
109
|
+
return isSafe ? <a href={href}>{children}</a> : <span>{children}</span>;
|
|
110
|
+
}
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
## Quick Checklist
|
|
114
|
+
- [ ] No `dangerouslySetInnerHTML` without DOMPurify sanitization
|
|
115
|
+
- [ ] All `useEffect` async operations have cleanup / `AbortController`
|
|
116
|
+
- [ ] No tokens, passwords, or API keys stored in React state
|
|
117
|
+
- [ ] Sensitive data is not passed through props across component boundaries
|
|
118
|
+
- [ ] Server components use `"server-only"` guard and filter data before client handoff
|
|
119
|
+
- [ ] `React.lazy` components are wrapped with `ErrorBoundary` and `Suspense`
|
|
120
|
+
- [ ] Dynamic URLs are validated against an allowlist of safe schemes
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# TypeScript Security Rules
|
|
2
|
+
|
|
3
|
+
> TypeScript-specific security rules for leveraging the type system to prevent vulnerabilities at compile time and enforce runtime safety boundaries.
|
|
4
|
+
|
|
5
|
+
## Rules
|
|
6
|
+
|
|
7
|
+
### 1. Enable Strict Mode and strictNullChecks
|
|
8
|
+
- **DO**: Set `"strict": true` and `"strictNullChecks": true` in `tsconfig.json`. Treat compiler warnings as errors in CI.
|
|
9
|
+
- **DON'T**: Disable strict checks to "fix" type errors quickly. Loosening strictness hides real bugs.
|
|
10
|
+
- **WHY**: Strict mode catches null/undefined dereferences, implicit `any` usage, and other unsafe patterns at compile time before they become runtime vulnerabilities.
|
|
11
|
+
|
|
12
|
+
### 2. Never Use `as any` or Excessive Type Assertions
|
|
13
|
+
- **DO**: Fix type errors by correcting the underlying types. Use type narrowing (`instanceof`, `in`, discriminated unions) to safely refine types.
|
|
14
|
+
- **DON'T**: Use `as any`, `as unknown as T`, or `@ts-ignore` to silence type errors. Each assertion is an unverified trust boundary.
|
|
15
|
+
- **WHY**: Type assertions bypass the compiler's safety checks. `as any` effectively disables TypeScript's protection, allowing injection, null dereference, and data integrity bugs to slip through.
|
|
16
|
+
|
|
17
|
+
### 3. Use `unknown` Instead of `any` for External Data
|
|
18
|
+
- **DO**: Type all external inputs (API responses, user input, file reads, environment variables) as `unknown` and narrow with validation before use.
|
|
19
|
+
- **DON'T**: Type external data as `any` or trust its shape without validation. Never pass unvalidated external data directly to business logic.
|
|
20
|
+
- **WHY**: External data is the primary attack surface. `unknown` forces explicit validation, preventing injection, prototype pollution, and type confusion attacks.
|
|
21
|
+
|
|
22
|
+
### 4. Validate External Data at Runtime with Schema Libraries
|
|
23
|
+
- **DO**: Use Zod, io-ts, or similar libraries to define schemas and validate all data crossing trust boundaries (API inputs, webhook payloads, config files).
|
|
24
|
+
- **DON'T**: Rely solely on TypeScript interfaces for external data — interfaces are erased at compile time and provide zero runtime protection.
|
|
25
|
+
- **WHY**: TypeScript's type system exists only at compile time. Attackers send raw HTTP requests; runtime validation is the only real defense against malformed or malicious payloads.
|
|
26
|
+
|
|
27
|
+
### 5. Use Generics for Type-Safe API Response Handling
|
|
28
|
+
- **DO**: Define generic API client functions that return validated, typed responses. Pair generics with runtime schema validation.
|
|
29
|
+
- **DON'T**: Cast API responses with `as T` without validation. A generic function that skips validation gives false confidence.
|
|
30
|
+
- **WHY**: Generic + validation patterns ensure that API response handling is both type-safe and runtime-safe, preventing type confusion from unexpected server responses.
|
|
31
|
+
|
|
32
|
+
### 6. Prefer `as const` Assertions Over Enums
|
|
33
|
+
- **DO**: Use `as const` objects or union types for fixed value sets. This provides better type narrowing and tree-shaking.
|
|
34
|
+
- **DON'T**: Use numeric enums, which can be accessed with arbitrary numbers at runtime (`MyEnum[999]` returns `undefined` without error).
|
|
35
|
+
- **WHY**: Numeric enums allow reverse mapping that accepts any number, bypassing intended value restrictions. `as const` objects are immutable and provide exact literal types.
|
|
36
|
+
|
|
37
|
+
### 7. Audit `declare module` and Ambient Type Declarations
|
|
38
|
+
- **DO**: Keep ambient declarations (`declare module`, `declare global`, `.d.ts` files) minimal and review them carefully. Ensure they accurately reflect the runtime API.
|
|
39
|
+
- **DON'T**: Use `declare module` to paper over missing types with permissive definitions (e.g., `declare module '*' { const x: any; export default x; }`).
|
|
40
|
+
- **WHY**: Ambient declarations override the compiler's type checking. Incorrect declarations create a false sense of safety — the compiler trusts them unconditionally, so inaccurate declarations hide real vulnerabilities.
|
|
41
|
+
|
|
42
|
+
## Code Examples
|
|
43
|
+
|
|
44
|
+
### Bad Practice
|
|
45
|
+
```typescript
|
|
46
|
+
// Using 'any' for API response — no safety at all
|
|
47
|
+
async function getUser(id: string): Promise<any> {
|
|
48
|
+
const res = await fetch(`/api/users/${id}`);
|
|
49
|
+
return res.json(); // any — caller has no type safety
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// Type assertion without validation
|
|
53
|
+
interface Config {
|
|
54
|
+
apiKey: string;
|
|
55
|
+
dbUrl: string;
|
|
56
|
+
}
|
|
57
|
+
const config = JSON.parse(rawInput) as Config; // Blindly trusted
|
|
58
|
+
|
|
59
|
+
// Numeric enum allows arbitrary values
|
|
60
|
+
enum Status {
|
|
61
|
+
Active = 0,
|
|
62
|
+
Inactive = 1,
|
|
63
|
+
}
|
|
64
|
+
const s: Status = 999; // No compile error!
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### Good Practice
|
|
68
|
+
```typescript
|
|
69
|
+
import { z } from "zod";
|
|
70
|
+
|
|
71
|
+
// Runtime schema validation with Zod
|
|
72
|
+
const UserSchema = z.object({
|
|
73
|
+
id: z.string().uuid(),
|
|
74
|
+
name: z.string().min(1).max(100),
|
|
75
|
+
email: z.string().email(),
|
|
76
|
+
role: z.enum(["admin", "user", "viewer"]),
|
|
77
|
+
});
|
|
78
|
+
type User = z.infer<typeof UserSchema>;
|
|
79
|
+
|
|
80
|
+
// Type-safe API client with runtime validation
|
|
81
|
+
async function fetchApi<T>(url: string, schema: z.ZodType<T>): Promise<T> {
|
|
82
|
+
const res = await fetch(url);
|
|
83
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
84
|
+
const data: unknown = await res.json();
|
|
85
|
+
return schema.parse(data); // Throws if invalid
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const user = await fetchApi("/api/users/123", UserSchema);
|
|
89
|
+
|
|
90
|
+
// as const instead of enum
|
|
91
|
+
const STATUS = {
|
|
92
|
+
Active: "active",
|
|
93
|
+
Inactive: "inactive",
|
|
94
|
+
} as const;
|
|
95
|
+
type Status = (typeof STATUS)[keyof typeof STATUS]; // "active" | "inactive"
|
|
96
|
+
|
|
97
|
+
// unknown + narrowing for external data
|
|
98
|
+
function processInput(input: unknown): string {
|
|
99
|
+
if (typeof input !== "string") {
|
|
100
|
+
throw new Error("Expected string input");
|
|
101
|
+
}
|
|
102
|
+
return input.trim(); // Now safely narrowed to string
|
|
103
|
+
}
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Quick Checklist
|
|
107
|
+
- [ ] `tsconfig.json` has `"strict": true` enabled
|
|
108
|
+
- [ ] No `as any` or `@ts-ignore` in production code
|
|
109
|
+
- [ ] All external data is typed as `unknown` and validated at runtime
|
|
110
|
+
- [ ] Zod or equivalent schema validation is used at trust boundaries
|
|
111
|
+
- [ ] Generic API functions include runtime validation, not just type assertions
|
|
112
|
+
- [ ] `as const` is used instead of numeric enums for fixed value sets
|
|
113
|
+
- [ ] Ambient type declarations are reviewed and accurately reflect runtime behavior
|