forgehive 0.6.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 +631 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +6265 -0
- package/dist/confirm.d.ts +1 -0
- package/dist/confirm.js +15 -0
- package/dist/fulfillment-catalog.d.ts +10 -0
- package/dist/fulfillment-catalog.js +119 -0
- package/dist/lookup-table.d.ts +16 -0
- package/dist/lookup-table.js +128 -0
- package/dist/rollback.d.ts +1 -0
- package/dist/rollback.js +9 -0
- package/dist/scanner.d.ts +2 -0
- package/dist/scanner.js +87 -0
- package/dist/types.d.ts +13 -0
- package/dist/types.js +1 -0
- package/dist/update.d.ts +7 -0
- package/dist/update.js +42 -0
- package/dist/writer.d.ts +3 -0
- package/dist/writer.js +63 -0
- package/forgehive/agents/eli.yaml +8 -0
- package/forgehive/agents/kai.yaml +8 -0
- package/forgehive/agents/nora.yaml +8 -0
- package/forgehive/agents/remy.yaml +8 -0
- package/forgehive/agents/sam.yaml +8 -0
- package/forgehive/agents/suki.yaml +8 -0
- package/forgehive/agents/vera.yaml +24 -0
- package/forgehive/agents/viktor.yaml +8 -0
- package/forgehive/commands/fh-hotfix.md +16 -0
- package/forgehive/commands/fh-review.md +16 -0
- package/forgehive/commands/fh-ship.md +18 -0
- package/forgehive/commands/fh-start-task.md +13 -0
- package/forgehive/hooks/guardrails.sh +17 -0
- package/forgehive/party/defaults.yaml +21 -0
- package/forgehive/skills/INDEX.yaml +64 -0
- package/forgehive/skills/expert/.gitkeep +0 -0
- package/forgehive/skills/expert/api-design.md +77 -0
- package/forgehive/skills/expert/auth-security.md +80 -0
- package/forgehive/skills/expert/clean-architecture.md +83 -0
- package/forgehive/skills/expert/code-review.md +81 -0
- package/forgehive/skills/expert/database-patterns.md +81 -0
- package/forgehive/skills/expert/error-handling.md +90 -0
- package/forgehive/skills/expert/gdpr-compliance.md +64 -0
- package/forgehive/skills/expert/git-conventions.md +84 -0
- package/forgehive/skills/expert/monorepo-patterns.md +91 -0
- package/forgehive/skills/expert/observability.md +98 -0
- package/forgehive/skills/expert/owasp-top10.md +153 -0
- package/forgehive/skills/expert/performance-patterns.md +79 -0
- package/forgehive/skills/expert/security-checklist.md +70 -0
- package/forgehive/skills/expert/testing-strategies.md +74 -0
- package/forgehive/skills/expert/typescript-patterns.md +62 -0
- package/forgehive/skills/templates/.gitkeep +0 -0
- package/forgehive/templates/claude-md.block.md +62 -0
- package/package.json +30 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# GDPR-Compliant Coding Patterns
|
|
2
|
+
|
|
3
|
+
## Core Principles (Art. 5 GDPR)
|
|
4
|
+
|
|
5
|
+
| Principle | Implementation |
|
|
6
|
+
|-----------|---------------|
|
|
7
|
+
| Lawfulness, fairness, transparency | Consent records, privacy notices |
|
|
8
|
+
| Purpose limitation | Separate schemas per data use |
|
|
9
|
+
| Data minimisation | Collect only what's needed |
|
|
10
|
+
| Accuracy | Update mechanisms, validation |
|
|
11
|
+
| Storage limitation | Retention policies, automatic deletion |
|
|
12
|
+
| Integrity & confidentiality | Encryption, access control |
|
|
13
|
+
| Accountability | Audit logs |
|
|
14
|
+
|
|
15
|
+
---
|
|
16
|
+
|
|
17
|
+
## PII Data Classification
|
|
18
|
+
|
|
19
|
+
```typescript
|
|
20
|
+
type PiiCategory =
|
|
21
|
+
| "DIRECT_IDENTIFIER" // name, email, NIN, passport
|
|
22
|
+
| "QUASI_IDENTIFIER" // DOB, postcode, gender
|
|
23
|
+
| "SENSITIVE_SPECIAL" // health, biometric, race, religion (Art. 9)
|
|
24
|
+
| "FINANCIAL" // IBAN, card numbers
|
|
25
|
+
| "BEHAVIORAL" // browsing history, location traces
|
|
26
|
+
| "NONE";
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
---
|
|
30
|
+
|
|
31
|
+
## Right to Erasure (Art. 17)
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
export async function eraseUserData(db: Database, userId: string): Promise<void> {
|
|
35
|
+
// 1. Anonymize user record (don't delete if referenced by orders etc.)
|
|
36
|
+
await db.users.update({
|
|
37
|
+
where: { id: userId },
|
|
38
|
+
data: {
|
|
39
|
+
email: `deleted-${userId}@erasure.invalid`,
|
|
40
|
+
name: "Deleted User",
|
|
41
|
+
deletedAt: new Date(),
|
|
42
|
+
},
|
|
43
|
+
});
|
|
44
|
+
// 2. Delete personally identifiable child records
|
|
45
|
+
await db.addresses.deleteMany({ where: { userId } });
|
|
46
|
+
await db.sessions.deleteMany({ where: { userId } });
|
|
47
|
+
// 3. Log the erasure (keep audit trail even without PII)
|
|
48
|
+
await db.erasureLog.create({ data: { userId, erasedAt: new Date() } });
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## GDPR Checklist for New Features
|
|
55
|
+
|
|
56
|
+
- [ ] Lawful basis for processing defined
|
|
57
|
+
- [ ] Data minimised — only collect what's needed
|
|
58
|
+
- [ ] Retention period defined and enforced
|
|
59
|
+
- [ ] Data encrypted at rest (Art. 9 special categories)
|
|
60
|
+
- [ ] Access controls in place
|
|
61
|
+
- [ ] Processing documented in data register
|
|
62
|
+
- [ ] Erasure endpoint handles this data
|
|
63
|
+
- [ ] Privacy notice updated
|
|
64
|
+
- [ ] DPIA completed if high risk (Art. 35)
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Git Conventions
|
|
2
|
+
|
|
3
|
+
## Commit Messages (Conventional Commits)
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
<type>(<scope>): <short description>
|
|
7
|
+
|
|
8
|
+
[optional body — wrap at 72 chars]
|
|
9
|
+
|
|
10
|
+
[optional footer: BREAKING CHANGE, Closes #123]
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Types:**
|
|
14
|
+
|
|
15
|
+
| Type | When |
|
|
16
|
+
|---|---|
|
|
17
|
+
| `feat` | New feature |
|
|
18
|
+
| `fix` | Bug fix |
|
|
19
|
+
| `chore` | Build, deps, config (no code change) |
|
|
20
|
+
| `refactor` | Code change without feature/fix |
|
|
21
|
+
| `test` | Adding/fixing tests |
|
|
22
|
+
| `docs` | Documentation only |
|
|
23
|
+
| `perf` | Performance improvement |
|
|
24
|
+
| `ci` | CI/CD changes |
|
|
25
|
+
|
|
26
|
+
**Rules:**
|
|
27
|
+
- Imperative mood: "add" not "added", "fix" not "fixes"
|
|
28
|
+
- No period at end of subject line
|
|
29
|
+
- Subject ≤ 72 chars
|
|
30
|
+
- Body explains WHY, not what (the diff shows what)
|
|
31
|
+
|
|
32
|
+
## Branch Naming
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
feat/user-authentication
|
|
36
|
+
fix/login-redirect-loop
|
|
37
|
+
chore/upgrade-dependencies
|
|
38
|
+
refactor/extract-auth-middleware
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Workflow
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
main (protected)
|
|
45
|
+
└── feat/my-feature
|
|
46
|
+
├── commit: feat: add login form
|
|
47
|
+
├── commit: test: add login form tests
|
|
48
|
+
└── PR → squash merge to main
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
- Work on feature branches — never directly on `main`
|
|
52
|
+
- Keep branches short-lived (days, not weeks)
|
|
53
|
+
- Squash merge for features, merge commit for releases
|
|
54
|
+
|
|
55
|
+
## Good Commit Habits
|
|
56
|
+
|
|
57
|
+
- One logical change per commit (atomic)
|
|
58
|
+
- Green tests before committing
|
|
59
|
+
- `git add -p` for partial staging when you changed multiple things
|
|
60
|
+
- `git commit --fixup` for small corrections before push
|
|
61
|
+
|
|
62
|
+
## PR Description Template
|
|
63
|
+
|
|
64
|
+
```markdown
|
|
65
|
+
## What
|
|
66
|
+
[1-2 sentences: what changed]
|
|
67
|
+
|
|
68
|
+
## Why
|
|
69
|
+
[motivation, ticket link]
|
|
70
|
+
|
|
71
|
+
## Test Plan
|
|
72
|
+
- [ ] Unit tests added/updated
|
|
73
|
+
- [ ] Smoke tested locally
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
## Anti-Patterns
|
|
77
|
+
|
|
78
|
+
| Avoid | Why |
|
|
79
|
+
|---|---|
|
|
80
|
+
| "WIP", "fix", "asdf" commits | Meaningless history |
|
|
81
|
+
| 1000-line commits | Hard to review, hard to bisect |
|
|
82
|
+
| `git push --force` on shared branches | Rewrites shared history |
|
|
83
|
+
| Committing generated files | Noise, conflicts |
|
|
84
|
+
| Committing secrets | Permanent in history |
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# Monorepo Patterns
|
|
2
|
+
|
|
3
|
+
## When to Use a Monorepo
|
|
4
|
+
|
|
5
|
+
Use when packages share code, have coordinated releases, or need atomic cross-package changes. Don't use just because you have multiple services — separate repos are fine for fully independent teams.
|
|
6
|
+
|
|
7
|
+
## Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
packages/
|
|
11
|
+
core/ ← shared domain types, utilities
|
|
12
|
+
api/ ← backend service
|
|
13
|
+
web/ ← frontend app
|
|
14
|
+
cli/ ← command-line tool
|
|
15
|
+
apps/ ← deployable applications (thin, import from packages)
|
|
16
|
+
tools/ ← internal scripts, generators
|
|
17
|
+
package.json ← workspaces config
|
|
18
|
+
turbo.json ← build orchestration (if using Turborepo)
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Workspace Setup (npm/pnpm)
|
|
22
|
+
|
|
23
|
+
```json
|
|
24
|
+
// root package.json
|
|
25
|
+
{
|
|
26
|
+
"workspaces": ["packages/*", "apps/*"],
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "turbo build",
|
|
29
|
+
"test": "turbo test",
|
|
30
|
+
"lint": "turbo lint"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Package Boundaries
|
|
36
|
+
|
|
37
|
+
```typescript
|
|
38
|
+
// packages/core/index.ts — explicit public API
|
|
39
|
+
export type { User, Order, Money };
|
|
40
|
+
export { parseConfig } from "./config.ts";
|
|
41
|
+
// Don't export internal helpers
|
|
42
|
+
|
|
43
|
+
// packages/api/src/service.ts
|
|
44
|
+
import { User } from "@company/core"; // ✅ use workspace package
|
|
45
|
+
import { helper } from "../../../core/src/internal"; // ❌ bypass boundary
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Dependency Management
|
|
49
|
+
|
|
50
|
+
- Hoist shared dev dependencies to root
|
|
51
|
+
- Keep runtime deps in each package (visibility)
|
|
52
|
+
- Use `workspace:*` protocol for internal deps
|
|
53
|
+
|
|
54
|
+
```json
|
|
55
|
+
// packages/api/package.json
|
|
56
|
+
{
|
|
57
|
+
"dependencies": {
|
|
58
|
+
"@company/core": "workspace:*",
|
|
59
|
+
"express": "^4.18"
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Build Order
|
|
65
|
+
|
|
66
|
+
Turborepo / Nx handle this with task graphs:
|
|
67
|
+
```json
|
|
68
|
+
// turbo.json
|
|
69
|
+
{
|
|
70
|
+
"pipeline": {
|
|
71
|
+
"build": { "dependsOn": ["^build"], "outputs": ["dist/**"] },
|
|
72
|
+
"test": { "dependsOn": ["build"] }
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## CI Strategy
|
|
78
|
+
|
|
79
|
+
- Cache build artifacts by content hash
|
|
80
|
+
- Run only affected packages on PR: `turbo run test --filter=[HEAD^1]`
|
|
81
|
+
- Full build on merge to main
|
|
82
|
+
|
|
83
|
+
## Anti-Patterns
|
|
84
|
+
|
|
85
|
+
| Avoid | Why |
|
|
86
|
+
|---|---|
|
|
87
|
+
| Circular package dependencies | Build order impossible |
|
|
88
|
+
| Importing internal paths across packages | Bypasses public API |
|
|
89
|
+
| One giant `packages/shared` | Becomes a dumping ground |
|
|
90
|
+
| Versioning each package independently | Defeats coordination benefits |
|
|
91
|
+
| No build cache | CI takes 20min for every change |
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# Observability
|
|
2
|
+
|
|
3
|
+
## Three Pillars
|
|
4
|
+
|
|
5
|
+
| Pillar | What | Tool examples |
|
|
6
|
+
|---|---|---|
|
|
7
|
+
| Logs | What happened | Winston, Pino, Datadog |
|
|
8
|
+
| Metrics | How much / how fast | Prometheus, Datadog |
|
|
9
|
+
| Traces | Where time was spent | OpenTelemetry, Jaeger |
|
|
10
|
+
|
|
11
|
+
## Structured Logging
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
// ✅ Structured — queryable, filterable
|
|
15
|
+
log.info("user.login", { userId, ip, duration_ms: 45, success: true });
|
|
16
|
+
|
|
17
|
+
// ❌ String interpolation — unsearchable
|
|
18
|
+
console.log(`User ${userId} logged in from ${ip} in 45ms`);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
**Log Levels:**
|
|
22
|
+
|
|
23
|
+
| Level | When |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `error` | Something broke — requires action |
|
|
26
|
+
| `warn` | Unexpected but handled |
|
|
27
|
+
| `info` | Key business events (login, purchase, deploy) |
|
|
28
|
+
| `debug` | Detailed flow for troubleshooting (not in prod) |
|
|
29
|
+
|
|
30
|
+
**Never log:** passwords, tokens, PII, full request bodies with secrets.
|
|
31
|
+
|
|
32
|
+
## Metrics to Track
|
|
33
|
+
|
|
34
|
+
**RED Method** (for services):
|
|
35
|
+
- **R**ate — requests per second
|
|
36
|
+
- **E**rrors — error rate (%)
|
|
37
|
+
- **D**uration — latency (p50, p95, p99)
|
|
38
|
+
|
|
39
|
+
**USE Method** (for infrastructure):
|
|
40
|
+
- **U**tilization — CPU/memory/disk %
|
|
41
|
+
- **S**aturation — queue depth, wait time
|
|
42
|
+
- **E**rrors — hardware/kernel errors
|
|
43
|
+
|
|
44
|
+
## OpenTelemetry (Node.js)
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
import { trace } from "@opentelemetry/api";
|
|
48
|
+
|
|
49
|
+
const tracer = trace.getTracer("my-service");
|
|
50
|
+
|
|
51
|
+
async function processOrder(orderId: string) {
|
|
52
|
+
const span = tracer.startSpan("processOrder");
|
|
53
|
+
span.setAttribute("order.id", orderId);
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
await doWork(orderId);
|
|
57
|
+
span.setStatus({ code: SpanStatusCode.OK });
|
|
58
|
+
} catch (err) {
|
|
59
|
+
span.recordException(err as Error);
|
|
60
|
+
span.setStatus({ code: SpanStatusCode.ERROR });
|
|
61
|
+
throw err;
|
|
62
|
+
} finally {
|
|
63
|
+
span.end();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Alerting
|
|
69
|
+
|
|
70
|
+
- Alert on symptoms (high error rate, slow responses), not causes
|
|
71
|
+
- Every alert must have a runbook
|
|
72
|
+
- PagerDuty for P1/P2 (revenue impact, data loss risk)
|
|
73
|
+
- Slack for P3/P4 (degraded, not down)
|
|
74
|
+
- Alert fatigue = ignored alerts = missed incidents
|
|
75
|
+
|
|
76
|
+
## Health Endpoints
|
|
77
|
+
|
|
78
|
+
```typescript
|
|
79
|
+
// Liveness: is the process alive?
|
|
80
|
+
app.get("/health/live", (req, res) => res.json({ status: "ok" }));
|
|
81
|
+
|
|
82
|
+
// Readiness: can it serve traffic?
|
|
83
|
+
app.get("/health/ready", async (req, res) => {
|
|
84
|
+
const dbOk = await db.ping().then(() => true).catch(() => false);
|
|
85
|
+
if (!dbOk) return res.status(503).json({ status: "not ready", db: false });
|
|
86
|
+
res.json({ status: "ok", db: true });
|
|
87
|
+
});
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
## Anti-Patterns
|
|
91
|
+
|
|
92
|
+
| Avoid | Why |
|
|
93
|
+
|---|---|
|
|
94
|
+
| `console.log` in production | No structure, no log level, no context |
|
|
95
|
+
| Logging every function entry/exit | Noise drowns signal |
|
|
96
|
+
| Metrics without dashboards | Data no one looks at |
|
|
97
|
+
| Alerting on causes (`cpu > 80%`) | Noisy, doesn't map to user impact |
|
|
98
|
+
| No correlation IDs | Can't trace a request across services |
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# OWASP Top 10 — Security Patterns for Node.js/TypeScript
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
This skill covers the OWASP Top 10 (2021) vulnerability categories with detection
|
|
6
|
+
patterns and remediation examples in TypeScript/Node.js.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## A01: Broken Access Control
|
|
11
|
+
|
|
12
|
+
**Risk:** Users can act outside their intended permissions.
|
|
13
|
+
|
|
14
|
+
**Detection patterns:**
|
|
15
|
+
- Missing authorization checks before database queries
|
|
16
|
+
- Direct object references using user-supplied IDs without ownership verification
|
|
17
|
+
- CORS misconfiguration allowing all origins
|
|
18
|
+
|
|
19
|
+
**Vulnerable:**
|
|
20
|
+
```typescript
|
|
21
|
+
// ❌ No ownership check
|
|
22
|
+
app.get("/documents/:id", authenticate, async (req, res) => {
|
|
23
|
+
const doc = await db.documents.findById(req.params.id);
|
|
24
|
+
res.json(doc);
|
|
25
|
+
});
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Secure:**
|
|
29
|
+
```typescript
|
|
30
|
+
// ✅ Verify ownership
|
|
31
|
+
app.get("/documents/:id", authenticate, async (req, res) => {
|
|
32
|
+
const doc = await db.documents.findOne({
|
|
33
|
+
_id: req.params.id,
|
|
34
|
+
ownerId: req.user.id,
|
|
35
|
+
});
|
|
36
|
+
if (!doc) return res.status(404).json({ error: "Not found" });
|
|
37
|
+
res.json(doc);
|
|
38
|
+
});
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## A02: Cryptographic Failures
|
|
44
|
+
|
|
45
|
+
**Risk:** Sensitive data exposed due to weak or missing encryption.
|
|
46
|
+
|
|
47
|
+
**Detection patterns:**
|
|
48
|
+
- `MD5` or `SHA1` for password hashing
|
|
49
|
+
- Sensitive data in plaintext
|
|
50
|
+
- Weak JWT secrets
|
|
51
|
+
|
|
52
|
+
**Vulnerable:**
|
|
53
|
+
```typescript
|
|
54
|
+
// ❌ MD5 not suitable for passwords
|
|
55
|
+
const hash = crypto.createHash("md5").update(password).digest("hex");
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Secure:**
|
|
59
|
+
```typescript
|
|
60
|
+
// ✅ Use bcrypt for passwords
|
|
61
|
+
import bcrypt from "bcrypt";
|
|
62
|
+
const hash = await bcrypt.hash(password, 12);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## A03: Injection
|
|
68
|
+
|
|
69
|
+
**Risk:** Untrusted data sent to an interpreter as part of a command or query.
|
|
70
|
+
|
|
71
|
+
**Vulnerable:**
|
|
72
|
+
```typescript
|
|
73
|
+
// ❌ SQL injection
|
|
74
|
+
const rows = await db.query(`SELECT * FROM users WHERE email = '${req.body.email}'`);
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
**Secure:**
|
|
78
|
+
```typescript
|
|
79
|
+
// ✅ Parameterized queries
|
|
80
|
+
const rows = await db.query("SELECT * FROM users WHERE email = $1", [req.body.email]);
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
---
|
|
84
|
+
|
|
85
|
+
## A04: Insecure Design
|
|
86
|
+
|
|
87
|
+
**Checklist:**
|
|
88
|
+
- [ ] Threat model for sensitive flows
|
|
89
|
+
- [ ] Rate limiting on public endpoints
|
|
90
|
+
- [ ] Business logic validated server-side
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## A05: Security Misconfiguration
|
|
95
|
+
|
|
96
|
+
**Secure Express setup:**
|
|
97
|
+
```typescript
|
|
98
|
+
import helmet from "helmet";
|
|
99
|
+
app.use(helmet());
|
|
100
|
+
app.use(cors({ origin: process.env.ALLOWED_ORIGINS?.split(",") ?? [] }));
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
---
|
|
104
|
+
|
|
105
|
+
## A06: Vulnerable and Outdated Components
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
npm audit
|
|
109
|
+
fh security deps
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## A07: Identification and Authentication Failures
|
|
115
|
+
|
|
116
|
+
**Secure JWT:**
|
|
117
|
+
```typescript
|
|
118
|
+
const token = jwt.sign({ userId }, JWT_SECRET, { expiresIn: "15m" });
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## A08: Software and Data Integrity Failures
|
|
124
|
+
|
|
125
|
+
- Verify npm package integrity with `npm audit`
|
|
126
|
+
- Use lockfiles (`package-lock.json`)
|
|
127
|
+
- Pin dependencies in production
|
|
128
|
+
|
|
129
|
+
---
|
|
130
|
+
|
|
131
|
+
## A09: Security Logging and Monitoring Failures
|
|
132
|
+
|
|
133
|
+
```typescript
|
|
134
|
+
// Log security events
|
|
135
|
+
fh security audit
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## A10: Server-Side Request Forgery (SSRF)
|
|
141
|
+
|
|
142
|
+
**Vulnerable:**
|
|
143
|
+
```typescript
|
|
144
|
+
// ❌ Fetch user-controlled URL
|
|
145
|
+
const data = await fetch(req.body.url);
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Secure:**
|
|
149
|
+
```typescript
|
|
150
|
+
// ✅ Validate URL against allowlist
|
|
151
|
+
const allowed = new URL(req.body.url);
|
|
152
|
+
if (!ALLOWED_HOSTS.includes(allowed.hostname)) throw new Error("Blocked");
|
|
153
|
+
```
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Performance Patterns
|
|
2
|
+
|
|
3
|
+
## Measure First
|
|
4
|
+
|
|
5
|
+
Never optimize without data. Profile before you guess.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Node.js profiling
|
|
9
|
+
node --prof app.js
|
|
10
|
+
node --prof-process isolate-*.log
|
|
11
|
+
|
|
12
|
+
# Simple timing
|
|
13
|
+
console.time("operation");
|
|
14
|
+
await heavyOperation();
|
|
15
|
+
console.timeEnd("operation");
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Database
|
|
19
|
+
|
|
20
|
+
**The N+1 Problem:**
|
|
21
|
+
```typescript
|
|
22
|
+
// ❌ N+1: 1 query for posts + N queries for authors
|
|
23
|
+
const posts = await db.posts.findMany();
|
|
24
|
+
for (const post of posts) {
|
|
25
|
+
post.author = await db.users.findById(post.authorId); // N queries
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// ✅ 1 query with JOIN or 2 queries with batch load
|
|
29
|
+
const posts = await db.posts.findMany({ include: { author: true } });
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
**Indexing:**
|
|
33
|
+
- Index foreign keys and columns used in WHERE, ORDER BY
|
|
34
|
+
- Composite indexes: column order matters (most selective first)
|
|
35
|
+
- Check `EXPLAIN ANALYZE` for slow queries
|
|
36
|
+
|
|
37
|
+
**Pagination:** Always paginate large result sets — never `SELECT *` without `LIMIT`.
|
|
38
|
+
|
|
39
|
+
## Caching
|
|
40
|
+
|
|
41
|
+
```typescript
|
|
42
|
+
// Cache expensive computations
|
|
43
|
+
const cache = new Map<string, { data: T; expires: number }>();
|
|
44
|
+
|
|
45
|
+
function cached<T>(key: string, ttlMs: number, fn: () => Promise<T>): Promise<T> {
|
|
46
|
+
const hit = cache.get(key);
|
|
47
|
+
if (hit && Date.now() < hit.expires) return Promise.resolve(hit.data);
|
|
48
|
+
return fn().then(data => { cache.set(key, { data, expires: Date.now() + ttlMs }); return data; });
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Cache at the right layer: HTTP (CDN/ETag), application (Redis), DB (query cache).
|
|
53
|
+
|
|
54
|
+
## Async / Concurrency
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
// ❌ Sequential when parallel is possible
|
|
58
|
+
const user = await getUser(id);
|
|
59
|
+
const prefs = await getPrefs(id);
|
|
60
|
+
|
|
61
|
+
// ✅ Parallel
|
|
62
|
+
const [user, prefs] = await Promise.all([getUser(id), getPrefs(id)]);
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Bundle Size (Frontend)
|
|
66
|
+
|
|
67
|
+
- Code splitting: lazy-load routes and heavy components
|
|
68
|
+
- Tree shaking: named imports only (`import { fn } from "lib"`)
|
|
69
|
+
- Analyze: `npx source-map-explorer dist/*.js`
|
|
70
|
+
|
|
71
|
+
## Anti-Patterns
|
|
72
|
+
|
|
73
|
+
| Avoid | Why |
|
|
74
|
+
|---|---|
|
|
75
|
+
| Premature optimization | Adds complexity for imaginary gains |
|
|
76
|
+
| Synchronous I/O in async context | Blocks event loop |
|
|
77
|
+
| Loading all records into memory | OOM on large datasets |
|
|
78
|
+
| Polling every 100ms | Use webhooks or SSE instead |
|
|
79
|
+
| Re-fetching uncached data on every render | Stale-while-revalidate pattern |
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# Security Checklist
|
|
2
|
+
|
|
3
|
+
## Input Validation (OWASP Top 10 — A03)
|
|
4
|
+
|
|
5
|
+
- [ ] Validate all input at the boundary (type, length, format, range)
|
|
6
|
+
- [ ] Use allowlist validation, not denylist
|
|
7
|
+
- [ ] Sanitize before storing, escape before rendering
|
|
8
|
+
- [ ] Never trust user-controlled data in SQL, shell commands, file paths
|
|
9
|
+
|
|
10
|
+
```typescript
|
|
11
|
+
// Parameterized queries — never string interpolation
|
|
12
|
+
const user = await db.query("SELECT * FROM users WHERE id = $1", [userId]);
|
|
13
|
+
|
|
14
|
+
// Path traversal prevention
|
|
15
|
+
const safe = path.resolve(baseDir, userInput);
|
|
16
|
+
if (!safe.startsWith(baseDir)) throw new ForbiddenError();
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Authentication & Authorization
|
|
20
|
+
|
|
21
|
+
- [ ] Passwords: bcrypt/argon2 (never MD5/SHA1), min 12 chars
|
|
22
|
+
- [ ] Sessions: HttpOnly, Secure, SameSite=Strict cookies
|
|
23
|
+
- [ ] JWT: verify signature + expiry, short-lived access tokens
|
|
24
|
+
- [ ] Check authorization on every request — don't rely on UI hiding
|
|
25
|
+
- [ ] Rate limit auth endpoints (login, password reset, OTP)
|
|
26
|
+
|
|
27
|
+
## Secrets Management
|
|
28
|
+
|
|
29
|
+
- [ ] Never hardcode secrets in source code
|
|
30
|
+
- [ ] Use env vars for config — never `.env` files in production
|
|
31
|
+
- [ ] Rotate secrets regularly; revoke on any exposure
|
|
32
|
+
- [ ] Secrets in logs? Redact them.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// ✅ Safe logging
|
|
36
|
+
log.info("request", { userId, endpoint }); // no token
|
|
37
|
+
|
|
38
|
+
// ❌ Never
|
|
39
|
+
log.info("auth", { token: req.headers.authorization });
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## Dependencies
|
|
43
|
+
|
|
44
|
+
- [ ] `npm audit` before every release
|
|
45
|
+
- [ ] Pin major versions in production
|
|
46
|
+
- [ ] Review changelogs for security advisories when upgrading
|
|
47
|
+
- [ ] Remove unused dependencies
|
|
48
|
+
|
|
49
|
+
## HTTP Security Headers
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
// Minimum set for web APIs
|
|
53
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
54
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
55
|
+
res.setHeader("Strict-Transport-Security", "max-age=31536000; includeSubDomains");
|
|
56
|
+
res.setHeader("Content-Security-Policy", "default-src 'none'");
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Error Responses
|
|
60
|
+
|
|
61
|
+
- [ ] Never expose stack traces to clients
|
|
62
|
+
- [ ] Generic error messages in production (log details server-side)
|
|
63
|
+
- [ ] Consistent error format (don't leak internal structure)
|
|
64
|
+
|
|
65
|
+
## Data
|
|
66
|
+
|
|
67
|
+
- [ ] Encrypt sensitive data at rest (PII, payment info)
|
|
68
|
+
- [ ] TLS everywhere in transit
|
|
69
|
+
- [ ] Minimal data collection — don't store what you don't need
|
|
70
|
+
- [ ] GDPR: deletion capability, data export capability
|