skillstore-cli 1.0.0
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 +95 -0
- package/data/bundles/devflow-complete.json +19 -0
- package/data/free-skills/devflow-agile/manifest.json +19 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/retro.md +23 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/review.md +21 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/sprint.md +30 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile/standup.md +20 -0
- package/data/free-skills/devflow-agile/plugin/commands/agile.md +35 -0
- package/data/free-skills/devflow-agile/plugin/commands/devflow.md +42 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/SKILL.md +93 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/assets/sample-output.md +182 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/clean-architecture.md +361 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/clean-code-guide.md +207 -0
- package/data/free-skills/devflow-agile/plugin/skills/developer/references/debugging-methodology.md +191 -0
- package/data/free-skills/devflow-agile/template/agents/agile-coach.md +76 -0
- package/data/free-skills/devflow-agile/template/workflows/agile-sprint-workflow.md +81 -0
- package/data/free-skills/devflow-bootstrap/manifest.json +8 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/bootstrap/auto.md +31 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/bootstrap.md +38 -0
- package/data/free-skills/devflow-bootstrap/plugin/commands/devflow.md +20 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/SKILL.md +56 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/assets/sample-output.md +216 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/references/architecture-decisions.md +254 -0
- package/data/free-skills/devflow-bootstrap/plugin/skills/project-scaffold/references/stack-templates.md +400 -0
- package/data/free-skills/devflow-bootstrap/template/agents/bootstrap-specialist.md +56 -0
- package/data/free-skills/devflow-bootstrap/template/workflows/bootstrap-workflow.md +70 -0
- package/data/free-skills/devflow-docs/manifest.json +8 -0
- package/data/free-skills/devflow-docs/plugin/commands/devflow.md +20 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs/generate.md +17 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs/parse.md +19 -0
- package/data/free-skills/devflow-docs/plugin/commands/docs.md +26 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/SKILL.md +59 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/assets/sample-output.md +114 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/references/extraction-techniques.md +115 -0
- package/data/free-skills/devflow-docs/plugin/skills/pdf-processor/references/ocr-strategies.md +167 -0
- package/data/free-skills/devflow-docs/template/agents/docs-specialist.md +35 -0
- package/data/free-skills/devflow-docs/template/workflows/docs-workflow.md +70 -0
- package/data/free-skills/devflow-postproject/manifest.json +13 -0
- package/data/free-skills/devflow-postproject/plugin/commands/devflow.md +34 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/handover.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/retro.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject/support.md +21 -0
- package/data/free-skills/devflow-postproject/plugin/commands/postproject.md +32 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/SKILL.md +70 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/assets/sample-output.md +79 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/facilitation-techniques.md +178 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/lessons-learned-template.md +118 -0
- package/data/free-skills/devflow-postproject/plugin/skills/retrospective/references/retro-techniques.md +100 -0
- package/data/free-skills/devflow-postproject/template/agents/transition-manager.md +71 -0
- package/data/free-skills/devflow-postproject/template/workflows/transition-workflow.md +72 -0
- package/data/free-skills/devflow-presale/manifest.json +15 -0
- package/data/free-skills/devflow-presale/plugin/commands/devflow.md +47 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/analyze.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/estimate.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/price.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale/propose.md +30 -0
- package/data/free-skills/devflow-presale/plugin/commands/presale.md +42 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/SKILL.md +63 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/assets/sample-output.md +129 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/references/extraction-framework.md +140 -0
- package/data/free-skills/devflow-presale/plugin/skills/requirement-analysis/references/output-template.md +132 -0
- package/data/free-skills/devflow-presale/template/agents/presale-lead.md +83 -0
- package/data/free-skills/devflow-presale/template/agents/proposal-reviewer.md +63 -0
- package/data/free-skills/devflow-presale/template/workflows/presale-workflow.md +70 -0
- package/data/registry/categories.json +7 -0
- package/data/registry/packages.json +184 -0
- package/data/shared/framework/agents/brainstormer.md +74 -0
- package/data/shared/framework/agents/code-reviewer.md +87 -0
- package/data/shared/framework/agents/debugger.md +84 -0
- package/data/shared/framework/agents/docs-manager.md +55 -0
- package/data/shared/framework/agents/git-manager.md +59 -0
- package/data/shared/framework/agents/planner.md +68 -0
- package/data/shared/framework/agents/researcher.md +66 -0
- package/data/shared/framework/agents/tester.md +65 -0
- package/data/shared/framework/commands/cook/auto.md +27 -0
- package/data/shared/framework/commands/cook.md +45 -0
- package/data/shared/framework/commands/fix/ci.md +21 -0
- package/data/shared/framework/commands/fix/test.md +26 -0
- package/data/shared/framework/commands/fix/types.md +29 -0
- package/data/shared/framework/commands/fix.md +26 -0
- package/data/shared/framework/commands/git/cm.md +37 -0
- package/data/shared/framework/commands/git/pr.md +40 -0
- package/data/shared/framework/config/CLAUDE.md.template +26 -0
- package/data/shared/framework/config/settings.json +41 -0
- package/data/shared/framework/config/skillstore.config.json +29 -0
- package/data/shared/framework/hooks/discord-notify.sh +85 -0
- package/data/shared/framework/hooks/docs-sync.sh +53 -0
- package/data/shared/framework/hooks/modularization-hook.js +103 -0
- package/data/shared/framework/hooks/notification.js +94 -0
- package/data/shared/framework/hooks/quality-gate.js +109 -0
- package/data/shared/framework/hooks/scout-block.js +77 -0
- package/data/shared/framework/hooks/telegram-notify.sh +77 -0
- package/data/shared/framework/protocols/error-recovery.md +80 -0
- package/data/shared/framework/protocols/orchestration-protocol.md +112 -0
- package/data/shared/framework/quality/review-protocol.md +76 -0
- package/data/shared/framework/quality/verification-protocol.md +66 -0
- package/data/shared/framework/rules/development-rules.md +75 -0
- package/data/shared/framework/skills/backend-development/SKILL.md +77 -0
- package/data/shared/framework/skills/backend-development/assets/sample-output.md +175 -0
- package/data/shared/framework/skills/backend-development/references/advanced-patterns.md +180 -0
- package/data/shared/framework/skills/backend-development/references/api-design-guide.md +160 -0
- package/data/shared/framework/skills/backend-development/references/architecture-patterns.md +183 -0
- package/data/shared/framework/skills/backend-development/references/observability-resilience.md +155 -0
- package/data/shared/framework/skills/backend-development/references/troubleshooting.md +199 -0
- package/data/shared/framework/skills/codebase-analysis/SKILL.md +72 -0
- package/data/shared/framework/skills/codebase-analysis/assets/sample-output.md +263 -0
- package/data/shared/framework/skills/codebase-analysis/references/analysis-techniques.md +241 -0
- package/data/shared/framework/skills/codebase-analysis/references/dependency-mapping.md +280 -0
- package/data/shared/framework/skills/codebase-analysis/references/tech-debt-assessment.md +208 -0
- package/data/shared/framework/skills/databases/SKILL.md +72 -0
- package/data/shared/framework/skills/databases/assets/sample-output.md +212 -0
- package/data/shared/framework/skills/databases/references/advanced-data-patterns.md +259 -0
- package/data/shared/framework/skills/databases/references/query-optimization.md +214 -0
- package/data/shared/framework/skills/databases/references/schema-design.md +159 -0
- package/data/shared/framework/skills/databases/references/troubleshooting.md +214 -0
- package/data/shared/framework/skills/debugging-investigation/SKILL.md +84 -0
- package/data/shared/framework/skills/debugging-investigation/assets/sample-output.md +314 -0
- package/data/shared/framework/skills/debugging-investigation/references/systematic-debugging.md +197 -0
- package/data/shared/framework/skills/debugging-investigation/references/tool-specific-guides.md +202 -0
- package/data/shared/framework/skills/debugging-investigation/references/troubleshooting-patterns.md +196 -0
- package/data/shared/framework/skills/frontend-development/SKILL.md +67 -0
- package/data/shared/framework/skills/frontend-development/assets/sample-output.md +110 -0
- package/data/shared/framework/skills/frontend-development/references/component-patterns.md +112 -0
- package/data/shared/framework/skills/frontend-development/references/performance-guide.md +169 -0
- package/data/shared/framework/skills/frontend-development/references/routing-forms-realtime.md +374 -0
- package/data/shared/framework/skills/frontend-development/references/ssr-rsc-patterns.md +284 -0
- package/data/shared/framework/skills/frontend-development/references/troubleshooting.md +154 -0
- package/data/shared/framework/skills/mobile-development/SKILL.md +67 -0
- package/data/shared/framework/skills/mobile-development/assets/sample-output.md +382 -0
- package/data/shared/framework/skills/mobile-development/references/mobile-patterns.md +681 -0
- package/data/shared/framework/skills/mobile-development/references/mobile-performance.md +524 -0
- package/data/shared/framework/skills/mobile-development/references/troubleshooting.md +158 -0
- package/data/shared/framework/skills/security-audit/SKILL.md +83 -0
- package/data/shared/framework/skills/security-audit/assets/sample-output.md +451 -0
- package/data/shared/framework/skills/security-audit/references/owasp-checklist.md +580 -0
- package/data/shared/framework/skills/security-audit/references/secure-coding-patterns.md +433 -0
- package/data/shared/framework/skills/security-audit/references/vulnerability-remediation.md +331 -0
- package/data/shared/framework/skills/ui-generation/SKILL.md +70 -0
- package/data/shared/framework/skills/ui-generation/assets/sample-output.md +139 -0
- package/data/shared/framework/skills/ui-generation/references/accessibility-responsive.md +127 -0
- package/data/shared/framework/skills/ui-generation/references/compound-components.md +252 -0
- package/data/shared/framework/skills/ui-generation/references/generation-patterns.md +110 -0
- package/data/shared/framework/skills/ui-generation/references/storybook-design-system.md +278 -0
- package/data/shared/framework/skills/ui-generation/references/troubleshooting.md +198 -0
- package/data/shared/framework/workflows/documentation-management.md +58 -0
- package/data/shared/framework/workflows/primary-workflow.md +88 -0
- package/dist/commands/activate.d.ts +3 -0
- package/dist/commands/activate.d.ts.map +1 -0
- package/dist/commands/activate.js +34 -0
- package/dist/commands/activate.js.map +1 -0
- package/dist/commands/bundle.d.ts +3 -0
- package/dist/commands/bundle.d.ts.map +1 -0
- package/dist/commands/bundle.js +64 -0
- package/dist/commands/bundle.js.map +1 -0
- package/dist/commands/install.d.ts +3 -0
- package/dist/commands/install.d.ts.map +1 -0
- package/dist/commands/install.js +99 -0
- package/dist/commands/install.js.map +1 -0
- package/dist/commands/list.d.ts +3 -0
- package/dist/commands/list.d.ts.map +1 -0
- package/dist/commands/list.js +37 -0
- package/dist/commands/list.js.map +1 -0
- package/dist/commands/search.d.ts +3 -0
- package/dist/commands/search.d.ts.map +1 -0
- package/dist/commands/search.js +30 -0
- package/dist/commands/search.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +35 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +3 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +68 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/download/cache.d.ts +3 -0
- package/dist/download/cache.d.ts.map +1 -0
- package/dist/download/cache.js +18 -0
- package/dist/download/cache.js.map +1 -0
- package/dist/download/client.d.ts +2 -0
- package/dist/download/client.d.ts.map +1 -0
- package/dist/download/client.js +58 -0
- package/dist/download/client.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/installer/file-copier.d.ts +6 -0
- package/dist/installer/file-copier.d.ts.map +1 -0
- package/dist/installer/file-copier.js +32 -0
- package/dist/installer/file-copier.js.map +1 -0
- package/dist/installer/plugin-installer.d.ts +12 -0
- package/dist/installer/plugin-installer.d.ts.map +1 -0
- package/dist/installer/plugin-installer.js +33 -0
- package/dist/installer/plugin-installer.js.map +1 -0
- package/dist/installer/template-installer.d.ts +12 -0
- package/dist/installer/template-installer.d.ts.map +1 -0
- package/dist/installer/template-installer.js +45 -0
- package/dist/installer/template-installer.js.map +1 -0
- package/dist/license/crypto.d.ts +16 -0
- package/dist/license/crypto.d.ts.map +1 -0
- package/dist/license/crypto.js +50 -0
- package/dist/license/crypto.js.map +1 -0
- package/dist/license/license-store.d.ts +19 -0
- package/dist/license/license-store.d.ts.map +1 -0
- package/dist/license/license-store.js +99 -0
- package/dist/license/license-store.js.map +1 -0
- package/dist/license/validator.d.ts +32 -0
- package/dist/license/validator.d.ts.map +1 -0
- package/dist/license/validator.js +81 -0
- package/dist/license/validator.js.map +1 -0
- package/dist/registry/loader.d.ts +30 -0
- package/dist/registry/loader.d.ts.map +1 -0
- package/dist/registry/loader.js +22 -0
- package/dist/registry/loader.js.map +1 -0
- package/dist/registry/search-engine.d.ts +9 -0
- package/dist/registry/search-engine.d.ts.map +1 -0
- package/dist/registry/search-engine.js +30 -0
- package/dist/registry/search-engine.js.map +1 -0
- package/dist/utils/config.d.ts +14 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +28 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/logger.d.ts +9 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +22 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/paths.d.ts +20 -0
- package/dist/utils/paths.d.ts.map +1 -0
- package/dist/utils/paths.js +79 -0
- package/dist/utils/paths.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# Secure Coding Patterns
|
|
2
|
+
|
|
3
|
+
Reusable security patterns for common development scenarios. Each pattern includes rationale, implementation code, and common mistakes to avoid.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Input Validation
|
|
8
|
+
|
|
9
|
+
### Allowlist Approach
|
|
10
|
+
Always validate against what is expected, not what is forbidden. Denylists are inherently incomplete.
|
|
11
|
+
|
|
12
|
+
```javascript
|
|
13
|
+
// GOOD: Allowlist — only accept known-valid values
|
|
14
|
+
const VALID_SORT_FIELDS = ['name', 'created_at', 'price', 'rating'];
|
|
15
|
+
const VALID_SORT_ORDERS = ['asc', 'desc'];
|
|
16
|
+
|
|
17
|
+
function validateSortParams(field, order) {
|
|
18
|
+
if (!VALID_SORT_FIELDS.includes(field)) throw new Error('Invalid sort field');
|
|
19
|
+
if (!VALID_SORT_ORDERS.includes(order)) throw new Error('Invalid sort order');
|
|
20
|
+
return { field, order };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// BAD: Denylist — will always miss something
|
|
24
|
+
function validateSortField(field) {
|
|
25
|
+
if (field.includes(';') || field.includes('--')) throw new Error('Invalid');
|
|
26
|
+
return field; // Still injectable via many other vectors
|
|
27
|
+
}
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
### Schema Validation (Zod)
|
|
31
|
+
```typescript
|
|
32
|
+
import { z } from 'zod';
|
|
33
|
+
|
|
34
|
+
const CreateUserSchema = z.object({
|
|
35
|
+
email: z.string().email().max(254),
|
|
36
|
+
password: z.string().min(12).max(128),
|
|
37
|
+
name: z.string().min(1).max(100).regex(/^[\p{L}\p{N}\s'-]+$/u),
|
|
38
|
+
role: z.enum(['user', 'editor']), // Never allow 'admin' via API
|
|
39
|
+
age: z.number().int().min(13).max(150).optional(),
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
// In route handler
|
|
43
|
+
app.post('/api/users', async (req, res) => {
|
|
44
|
+
const result = CreateUserSchema.safeParse(req.body);
|
|
45
|
+
if (!result.success) {
|
|
46
|
+
return res.status(400).json({ errors: result.error.flatten().fieldErrors });
|
|
47
|
+
}
|
|
48
|
+
const validatedData = result.data;
|
|
49
|
+
// Proceed with validated data only
|
|
50
|
+
});
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Sanitization vs. Validation
|
|
54
|
+
- **Validation:** Reject invalid input entirely (preferred)
|
|
55
|
+
- **Sanitization:** Transform input to a safe form (use only when you must accept rich content)
|
|
56
|
+
- Rule: Validate first, sanitize only as a secondary measure for specific fields (e.g., HTML content)
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## Output Encoding
|
|
61
|
+
|
|
62
|
+
Context-specific encoding is critical. There is no universal "escape" function.
|
|
63
|
+
|
|
64
|
+
### Encoding by Context
|
|
65
|
+
|
|
66
|
+
| Context | Encoding | Example |
|
|
67
|
+
|---|---|---|
|
|
68
|
+
| HTML body | HTML entity encode | `<` → `<` |
|
|
69
|
+
| HTML attribute | HTML entity encode + quote | `"` → `"` |
|
|
70
|
+
| JavaScript string | JavaScript Unicode escape | `'` → `\u0027` |
|
|
71
|
+
| URL parameter | Percent encode | ` ` → `%20` |
|
|
72
|
+
| CSS value | CSS hex escape | `(` → `\28` |
|
|
73
|
+
|
|
74
|
+
```javascript
|
|
75
|
+
// React/JSX: Auto-escapes by default — safe
|
|
76
|
+
const UserGreeting = ({ name }) => <h1>Hello, {name}</h1>;
|
|
77
|
+
|
|
78
|
+
// Dangerous: Bypasses React's auto-escaping
|
|
79
|
+
const UnsafeContent = ({ html }) => (
|
|
80
|
+
<div dangerouslySetInnerHTML={{ __html: html }} /> // XSS if html is untrusted
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// Safe: Sanitize before rendering raw HTML
|
|
84
|
+
import DOMPurify from 'dompurify';
|
|
85
|
+
const SafeContent = ({ html }) => (
|
|
86
|
+
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(html, {
|
|
87
|
+
ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a', 'p', 'br', 'ul', 'ol', 'li'],
|
|
88
|
+
ALLOWED_ATTR: ['href', 'target', 'rel'],
|
|
89
|
+
}) }} />
|
|
90
|
+
);
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
---
|
|
94
|
+
|
|
95
|
+
## Parameterized Queries
|
|
96
|
+
|
|
97
|
+
Never concatenate user input into SQL queries. Use parameterized statements in every language.
|
|
98
|
+
|
|
99
|
+
### Node.js (pg — PostgreSQL)
|
|
100
|
+
```javascript
|
|
101
|
+
// Parameterized query — $1, $2 placeholders
|
|
102
|
+
const result = await pool.query(
|
|
103
|
+
'SELECT * FROM users WHERE email = $1 AND status = $2',
|
|
104
|
+
[email, 'active']
|
|
105
|
+
);
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Python (psycopg2 — PostgreSQL)
|
|
109
|
+
```python
|
|
110
|
+
# %s placeholders with tuple parameter
|
|
111
|
+
cursor.execute(
|
|
112
|
+
"SELECT * FROM users WHERE email = %s AND status = %s",
|
|
113
|
+
(email, "active")
|
|
114
|
+
)
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Java (JDBC — Prepared Statement)
|
|
118
|
+
```java
|
|
119
|
+
PreparedStatement stmt = connection.prepareStatement(
|
|
120
|
+
"SELECT * FROM users WHERE email = ? AND status = ?"
|
|
121
|
+
);
|
|
122
|
+
stmt.setString(1, email);
|
|
123
|
+
stmt.setString(2, "active");
|
|
124
|
+
ResultSet rs = stmt.executeQuery();
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### ORM Safety Note
|
|
128
|
+
ORMs generally produce parameterized queries, but raw query methods are still vulnerable:
|
|
129
|
+
```javascript
|
|
130
|
+
// Sequelize — SAFE
|
|
131
|
+
await User.findAll({ where: { email: userInput } });
|
|
132
|
+
|
|
133
|
+
// Sequelize — VULNERABLE (raw query with interpolation)
|
|
134
|
+
await sequelize.query(`SELECT * FROM users WHERE email = '${userInput}'`);
|
|
135
|
+
|
|
136
|
+
// Sequelize — SAFE (raw query with bind)
|
|
137
|
+
await sequelize.query('SELECT * FROM users WHERE email = $1', {
|
|
138
|
+
bind: [userInput], type: QueryTypes.SELECT
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
---
|
|
143
|
+
|
|
144
|
+
## CSRF Protection
|
|
145
|
+
|
|
146
|
+
### SameSite Cookies (Primary Defense)
|
|
147
|
+
```javascript
|
|
148
|
+
// SameSite=Lax: Cookie not sent on cross-origin POST (prevents CSRF for state-changing requests)
|
|
149
|
+
res.cookie('session', token, {
|
|
150
|
+
httpOnly: true,
|
|
151
|
+
secure: true,
|
|
152
|
+
sameSite: 'lax',
|
|
153
|
+
});
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
### CSRF Tokens (Defense in Depth)
|
|
157
|
+
```javascript
|
|
158
|
+
const csrf = require('csurf');
|
|
159
|
+
const csrfProtection = csrf({ cookie: { httpOnly: true, sameSite: 'strict' } });
|
|
160
|
+
|
|
161
|
+
// Include token in forms
|
|
162
|
+
app.get('/form', csrfProtection, (req, res) => {
|
|
163
|
+
res.render('form', { csrfToken: req.csrfToken() });
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
// Validate token on submission
|
|
167
|
+
app.post('/submit', csrfProtection, (req, res) => {
|
|
168
|
+
// csurf middleware automatically validates _csrf field
|
|
169
|
+
});
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Double-Submit Cookie Pattern
|
|
173
|
+
```javascript
|
|
174
|
+
// For SPAs where server-rendered tokens are impractical
|
|
175
|
+
// 1. Set a random CSRF token in a non-HttpOnly cookie
|
|
176
|
+
// 2. JavaScript reads the cookie and sends it as a header
|
|
177
|
+
// 3. Server validates cookie value matches header value
|
|
178
|
+
|
|
179
|
+
app.use((req, res, next) => {
|
|
180
|
+
if (['POST', 'PUT', 'DELETE', 'PATCH'].includes(req.method)) {
|
|
181
|
+
const cookieToken = req.cookies['csrf-token'];
|
|
182
|
+
const headerToken = req.headers['x-csrf-token'];
|
|
183
|
+
if (!cookieToken || cookieToken !== headerToken) {
|
|
184
|
+
return res.status(403).json({ error: 'CSRF validation failed' });
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
next();
|
|
188
|
+
});
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
---
|
|
192
|
+
|
|
193
|
+
## Secure Session Management
|
|
194
|
+
|
|
195
|
+
### Cookie Flags
|
|
196
|
+
| Flag | Purpose | Setting |
|
|
197
|
+
|---|---|---|
|
|
198
|
+
| `HttpOnly` | Prevents JavaScript access | Always `true` |
|
|
199
|
+
| `Secure` | HTTPS-only transmission | Always `true` in production |
|
|
200
|
+
| `SameSite` | Cross-origin request control | `Lax` (default) or `Strict` |
|
|
201
|
+
| `Path` | Cookie scope | `/` (or narrower) |
|
|
202
|
+
| `Max-Age` | Expiration | 3600 (1h) for sessions |
|
|
203
|
+
|
|
204
|
+
### Session Rotation on Authentication State Change
|
|
205
|
+
```javascript
|
|
206
|
+
// Regenerate session ID on: login, logout, privilege change
|
|
207
|
+
function regenerateSession(req) {
|
|
208
|
+
return new Promise((resolve, reject) => {
|
|
209
|
+
const data = { ...req.session };
|
|
210
|
+
req.session.regenerate((err) => {
|
|
211
|
+
if (err) return reject(err);
|
|
212
|
+
Object.assign(req.session, data);
|
|
213
|
+
resolve();
|
|
214
|
+
});
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
// On login
|
|
219
|
+
await regenerateSession(req);
|
|
220
|
+
req.session.userId = user.id;
|
|
221
|
+
|
|
222
|
+
// On logout
|
|
223
|
+
req.session.destroy((err) => {
|
|
224
|
+
res.clearCookie('__Host-sid');
|
|
225
|
+
res.json({ success: true });
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
## Password Handling
|
|
232
|
+
|
|
233
|
+
### Algorithm Hierarchy
|
|
234
|
+
1. **Argon2id** (preferred) — memory-hard, resistant to GPU/ASIC attacks
|
|
235
|
+
2. **bcrypt** (acceptable) — widely supported, cost factor >= 12
|
|
236
|
+
3. **PBKDF2** (minimum) — only if Argon2/bcrypt unavailable, iterations >= 600,000
|
|
237
|
+
|
|
238
|
+
### Password Policy Implementation
|
|
239
|
+
```javascript
|
|
240
|
+
const zxcvbn = require('zxcvbn');
|
|
241
|
+
|
|
242
|
+
function validatePassword(password) {
|
|
243
|
+
const errors = [];
|
|
244
|
+
|
|
245
|
+
if (password.length < 12) errors.push('Minimum 12 characters');
|
|
246
|
+
if (password.length > 128) errors.push('Maximum 128 characters');
|
|
247
|
+
|
|
248
|
+
// Strength check (zxcvbn score 0-4, require >= 3)
|
|
249
|
+
const strength = zxcvbn(password);
|
|
250
|
+
if (strength.score < 3) {
|
|
251
|
+
errors.push(`Password is too weak: ${strength.feedback.warning || 'Try a longer or more complex password'}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return { valid: errors.length === 0, errors };
|
|
255
|
+
}
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## API Key Management
|
|
261
|
+
|
|
262
|
+
### Environment Variables (Minimum)
|
|
263
|
+
```javascript
|
|
264
|
+
// Access via environment — never hardcode
|
|
265
|
+
const apiKey = process.env.STRIPE_SECRET_KEY;
|
|
266
|
+
if (!apiKey) throw new Error('STRIPE_SECRET_KEY is not configured');
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
### Secrets Manager Pattern
|
|
270
|
+
```javascript
|
|
271
|
+
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');
|
|
272
|
+
|
|
273
|
+
async function getSecret(secretName) {
|
|
274
|
+
const client = new SecretsManagerClient({ region: 'us-east-1' });
|
|
275
|
+
const response = await client.send(new GetSecretValueCommand({ SecretId: secretName }));
|
|
276
|
+
return JSON.parse(response.SecretString);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Cache secrets to avoid repeated calls
|
|
280
|
+
let cachedSecrets = null;
|
|
281
|
+
async function getSecrets() {
|
|
282
|
+
if (!cachedSecrets) {
|
|
283
|
+
cachedSecrets = await getSecret('app/production/keys');
|
|
284
|
+
// Refresh cache every hour
|
|
285
|
+
setTimeout(() => { cachedSecrets = null; }, 3600000);
|
|
286
|
+
}
|
|
287
|
+
return cachedSecrets;
|
|
288
|
+
}
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### Key Rotation Pattern
|
|
292
|
+
- Generate new key → deploy to consumers → mark old key deprecated → monitor for old key usage → revoke old key
|
|
293
|
+
- Automate with a minimum 90-day rotation cycle
|
|
294
|
+
- Never log API keys; mask in any output (`sk_live_...abc1`)
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Content Security Policy (CSP)
|
|
299
|
+
|
|
300
|
+
### Directive Reference
|
|
301
|
+
| Directive | Controls | Recommended |
|
|
302
|
+
|---|---|---|
|
|
303
|
+
| `default-src` | Fallback for all types | `'self'` |
|
|
304
|
+
| `script-src` | JavaScript sources | `'self'` + nonce |
|
|
305
|
+
| `style-src` | CSS sources | `'self'` + nonce or `'unsafe-inline'` |
|
|
306
|
+
| `img-src` | Image sources | `'self' data: https:` |
|
|
307
|
+
| `connect-src` | XHR, fetch, WebSocket | `'self'` + API domains |
|
|
308
|
+
| `frame-src` | Iframes | `'none'` |
|
|
309
|
+
| `object-src` | Plugins (Flash, Java) | `'none'` |
|
|
310
|
+
| `base-uri` | `<base>` tag | `'self'` |
|
|
311
|
+
|
|
312
|
+
### Nonce-Based CSP
|
|
313
|
+
```javascript
|
|
314
|
+
const crypto = require('crypto');
|
|
315
|
+
|
|
316
|
+
app.use((req, res, next) => {
|
|
317
|
+
const nonce = crypto.randomBytes(16).toString('base64');
|
|
318
|
+
res.locals.cspNonce = nonce;
|
|
319
|
+
res.setHeader('Content-Security-Policy',
|
|
320
|
+
`default-src 'self'; script-src 'self' 'nonce-${nonce}'; style-src 'self' 'nonce-${nonce}'; object-src 'none'; base-uri 'self';`
|
|
321
|
+
);
|
|
322
|
+
next();
|
|
323
|
+
});
|
|
324
|
+
|
|
325
|
+
// In templates: <script nonce="<%= cspNonce %>">...</script>
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## CORS Configuration
|
|
331
|
+
|
|
332
|
+
```javascript
|
|
333
|
+
const cors = require('cors');
|
|
334
|
+
|
|
335
|
+
app.use(cors({
|
|
336
|
+
origin: ['https://app.example.com', 'https://admin.example.com'], // Explicit origins
|
|
337
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE'],
|
|
338
|
+
allowedHeaders: ['Content-Type', 'Authorization', 'X-CSRF-Token'],
|
|
339
|
+
credentials: true, // Allow cookies
|
|
340
|
+
maxAge: 86400, // Cache preflight for 24h
|
|
341
|
+
}));
|
|
342
|
+
|
|
343
|
+
// NEVER do this in production:
|
|
344
|
+
// origin: '*' — allows any origin
|
|
345
|
+
// origin: true — reflects any origin (equivalent to *)
|
|
346
|
+
// credentials: true with origin: '*' — browsers block this, but misconfig is dangerous
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
---
|
|
350
|
+
|
|
351
|
+
## Rate Limiting
|
|
352
|
+
|
|
353
|
+
### Sliding Window Implementation
|
|
354
|
+
```javascript
|
|
355
|
+
const rateLimit = require('express-rate-limit');
|
|
356
|
+
const RedisStore = require('rate-limit-redis').default;
|
|
357
|
+
const Redis = require('ioredis');
|
|
358
|
+
|
|
359
|
+
const redisClient = new Redis(process.env.REDIS_URL);
|
|
360
|
+
|
|
361
|
+
// Tiered rate limiting
|
|
362
|
+
const apiLimiter = rateLimit({
|
|
363
|
+
store: new RedisStore({ sendCommand: (...args) => redisClient.call(...args) }),
|
|
364
|
+
windowMs: 60 * 1000, // 1 minute
|
|
365
|
+
max: 100, // 100 requests per minute
|
|
366
|
+
standardHeaders: true,
|
|
367
|
+
legacyHeaders: false,
|
|
368
|
+
keyGenerator: (req) => req.user?.id || req.ip,
|
|
369
|
+
skip: (req) => req.user?.role === 'service-account',
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
// Stricter limits for sensitive endpoints
|
|
373
|
+
const authLimiter = rateLimit({
|
|
374
|
+
store: new RedisStore({ sendCommand: (...args) => redisClient.call(...args) }),
|
|
375
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
376
|
+
max: 5,
|
|
377
|
+
keyGenerator: (req) => req.body.email || req.ip,
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
app.use('/api/', apiLimiter);
|
|
381
|
+
app.post('/api/auth/login', authLimiter);
|
|
382
|
+
app.post('/api/auth/reset-password', authLimiter);
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
---
|
|
386
|
+
|
|
387
|
+
## JWT Security
|
|
388
|
+
|
|
389
|
+
### Algorithm Validation
|
|
390
|
+
```javascript
|
|
391
|
+
const jwt = require('jsonwebtoken');
|
|
392
|
+
|
|
393
|
+
// ALWAYS specify algorithm explicitly to prevent algorithm confusion attacks
|
|
394
|
+
const JWT_SECRET = process.env.JWT_SECRET;
|
|
395
|
+
const JWT_OPTIONS = {
|
|
396
|
+
algorithms: ['HS256'], // Allowlist — prevents "none" and RS256 confusion
|
|
397
|
+
issuer: 'app.example.com',
|
|
398
|
+
audience: 'app.example.com',
|
|
399
|
+
};
|
|
400
|
+
|
|
401
|
+
function verifyToken(token) {
|
|
402
|
+
return jwt.verify(token, JWT_SECRET, JWT_OPTIONS);
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
// Short-lived access tokens + refresh token rotation
|
|
406
|
+
function generateTokenPair(userId) {
|
|
407
|
+
const accessToken = jwt.sign({ sub: userId, type: 'access' }, JWT_SECRET, {
|
|
408
|
+
expiresIn: '15m',
|
|
409
|
+
issuer: 'app.example.com',
|
|
410
|
+
});
|
|
411
|
+
const refreshToken = jwt.sign({ sub: userId, type: 'refresh' }, JWT_SECRET, {
|
|
412
|
+
expiresIn: '7d',
|
|
413
|
+
issuer: 'app.example.com',
|
|
414
|
+
jwtid: crypto.randomUUID(), // Unique ID for revocation tracking
|
|
415
|
+
});
|
|
416
|
+
return { accessToken, refreshToken };
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
### Token Revocation
|
|
421
|
+
```javascript
|
|
422
|
+
// Maintain a denylist of revoked token IDs (jti) in Redis
|
|
423
|
+
async function revokeToken(jti, expiresAt) {
|
|
424
|
+
const ttl = Math.ceil((expiresAt * 1000 - Date.now()) / 1000);
|
|
425
|
+
if (ttl > 0) {
|
|
426
|
+
await redisClient.setex(`revoked:${jti}`, ttl, '1');
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
async function isTokenRevoked(jti) {
|
|
431
|
+
return await redisClient.exists(`revoked:${jti}`);
|
|
432
|
+
}
|
|
433
|
+
```
|