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.
@@ -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