@stackmemoryai/stackmemory 1.6.0 → 1.6.2

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.
Files changed (34) hide show
  1. package/dist/src/cli/commands/orchestrate.js +334 -4
  2. package/dist/src/cli/commands/orchestrator.js +283 -24
  3. package/package.json +1 -2
  4. package/scripts/gepa/.before-optimize.md +0 -112
  5. package/scripts/gepa/README.md +0 -275
  6. package/scripts/gepa/config.json +0 -59
  7. package/scripts/gepa/evals/coding-tasks.jsonl +0 -5
  8. package/scripts/gepa/evals/fixtures/api-endpoint.ts +0 -31
  9. package/scripts/gepa/evals/fixtures/brittle-integration.ts +0 -38
  10. package/scripts/gepa/evals/fixtures/buggy-loop.js +0 -18
  11. package/scripts/gepa/evals/fixtures/callback-hell.js +0 -53
  12. package/scripts/gepa/evals/fixtures/fts5-triggers.sql +0 -23
  13. package/scripts/gepa/evals/fixtures/leaky-service.ts +0 -70
  14. package/scripts/gepa/evals/fixtures/mcp-dispatch-stub.ts +0 -39
  15. package/scripts/gepa/evals/fixtures/pr-diff.txt +0 -24
  16. package/scripts/gepa/evals/fixtures/unsafe-webhook.ts +0 -34
  17. package/scripts/gepa/evals/fixtures/unwrapped-db-op.ts +0 -42
  18. package/scripts/gepa/evals/stackmemory-tasks.jsonl +0 -8
  19. package/scripts/gepa/generations/gen-000/baseline.md +0 -112
  20. package/scripts/gepa/generations/gen-001/baseline.md +0 -112
  21. package/scripts/gepa/generations/gen-001/variant-a.md +0 -107
  22. package/scripts/gepa/generations/gen-001/variant-b.md +0 -216
  23. package/scripts/gepa/generations/gen-001/variant-c.md +0 -83
  24. package/scripts/gepa/generations/gen-001/variant-d.md +0 -90
  25. package/scripts/gepa/hooks/auto-optimize.js +0 -494
  26. package/scripts/gepa/hooks/eval-tracker.js +0 -203
  27. package/scripts/gepa/hooks/reflect.js +0 -350
  28. package/scripts/gepa/optimize.js +0 -853
  29. package/scripts/gepa/results/eval-1-baseline.json +0 -218
  30. package/scripts/gepa/results/eval-1-variant-a.json +0 -218
  31. package/scripts/gepa/results/eval-1-variant-b.json +0 -218
  32. package/scripts/gepa/results/eval-1-variant-c.json +0 -218
  33. package/scripts/gepa/results/eval-1-variant-d.json +0 -218
  34. package/scripts/gepa/state.json +0 -49
@@ -1,275 +0,0 @@
1
- # Prompt Forge (GEPA)
2
-
3
- **Genetic Eval-driven Prompt Algorithm**
4
-
5
- Automatically evolve and optimize your `CLAUDE.md` system prompts using AI-powered evolutionary algorithms.
6
-
7
- ## Auto-Optimize on Save
8
-
9
- ```bash
10
- # Start watcher - auto-optimizes when CLAUDE.md changes
11
- node scripts/gepa/hooks/auto-optimize.js watch ./CLAUDE.md
12
- ```
13
-
14
- Output shows before/after comparison:
15
- ```
16
- ╔════════════════════════════════════════════════════════════╗
17
- ║ BEFORE / AFTER COMPARISON ║
18
- ╠════════════════════════════════════════════════════════════╣
19
- ║ Metric Before After Change ║
20
- ╠════════════════════════════════════════════════════════════╣
21
- ║ Lines 125 142 +17 (+14%) ║
22
- ║ Est. Tokens 873 920 +47 (+5%) ║
23
- ║ MUST rules 1 3 +2 (+200%) ║
24
- ║ NEVER rules 3 5 +2 (+67%) ║
25
- ╚════════════════════════════════════════════════════════════╝
26
-
27
- Section Changes:
28
- Added:
29
- + Error Handling
30
- + Performance Guidelines
31
-
32
- Summary:
33
- Token budget: +47 tokens
34
- Rule density: +4 explicit rules
35
- ```
36
-
37
- ## How It Works
38
-
39
- ```
40
- ┌─────────────────────────────────────────────────────────────────┐
41
- │ GEPA Loop │
42
- │ │
43
- │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
44
- │ │ Seed │───►│ Mutate │───►│ Eval │───►│ Select │ │
45
- │ │ Prompt │ │ (AI gen) │ │ (Claude) │ │ (best) │ │
46
- │ └──────────┘ └──────────┘ └──────────┘ └────┬─────┘ │
47
- │ ▲ │ │
48
- │ │ ┌──────────┐ │ │
49
- │ └──────────────┤ Reflect │◄────────────────────┘ │
50
- │ │(insights)│ │
51
- │ └──────────┘ │
52
- └─────────────────────────────────────────────────────────────────┘
53
- ```
54
-
55
- ## Quick Start
56
-
57
- ```bash
58
- # 1. Initialize with your current CLAUDE.md
59
- node scripts/gepa/optimize.js init ./CLAUDE.md
60
-
61
- # 2. Run full optimization (10 generations)
62
- node scripts/gepa/optimize.js run
63
-
64
- # 3. Apply the best result
65
- cp scripts/gepa/generations/current ./CLAUDE.md
66
- ```
67
-
68
- ## Commands
69
-
70
- | Command | Description |
71
- |---------|-------------|
72
- | `init [path]` | Initialize with a CLAUDE.md file |
73
- | `mutate` | Generate new prompt variants |
74
- | `eval [variant]` | Run evals on a specific variant |
75
- | `score` | Score all variants and select best |
76
- | `run [N]` | Full optimization loop for N generations |
77
- | `status` | Show current optimization status |
78
- | `diff [a] [b]` | Compare two variants |
79
-
80
- ## Mutation Strategies
81
-
82
- GEPA uses 6 mutation strategies, cycling through them:
83
-
84
- 1. **rephrase** - Reword for clarity without changing meaning
85
- 2. **add_examples** - Add concrete examples where abstract
86
- 3. **remove_redundancy** - DRY up repetitive instructions
87
- 4. **restructure** - Reorganize for better flow
88
- 5. **add_constraints** - Add guardrails for failure modes
89
- 6. **simplify** - Break down complex rules
90
-
91
- ## Reflection (Key Innovation)
92
-
93
- Unlike random mutations, GEPA analyzes **why** prompts fail:
94
-
95
- ```bash
96
- # Analyze session patterns
97
- node scripts/gepa/hooks/reflect.js analyze
98
-
99
- # Generate targeted improvements
100
- node scripts/gepa/hooks/reflect.js reflect
101
- ```
102
-
103
- The reflection engine examines:
104
- - Common error patterns
105
- - Tool call success rates
106
- - User feedback (thumbs up/down)
107
- - Performance by variant
108
-
109
- Then generates **targeted** mutation suggestions.
110
-
111
- ## Hook Integration
112
-
113
- To track real usage for evals, add to `~/.claude/settings.json`:
114
-
115
- ```json
116
- {
117
- "hooks": {
118
- "postToolCall": [
119
- {
120
- "command": "node ~/.claude/gepa/hooks/eval-tracker.js track-tool"
121
- }
122
- ],
123
- "postSession": [
124
- {
125
- "command": "node ~/.claude/gepa/hooks/eval-tracker.js save"
126
- }
127
- ]
128
- }
129
- }
130
- ```
131
-
132
- Or use the StackMemory daemon integration:
133
-
134
- ```bash
135
- # Add to your .env
136
- GEPA_ENABLED=true
137
- GEPA_DIR=~/.claude/gepa
138
- ```
139
-
140
- ## Configuration
141
-
142
- Edit `config.json`:
143
-
144
- ```json
145
- {
146
- "evolution": {
147
- "populationSize": 4, // Variants per generation
148
- "generations": 10, // Max generations
149
- "selectionRate": 0.5 // Top 50% survive
150
- },
151
- "evals": {
152
- "minSamplesPerVariant": 5, // Evals per variant
153
- "timeout": 120000 // 2 min per eval
154
- },
155
- "scoring": {
156
- "threshold": 0.8 // Stop when 80% success
157
- }
158
- }
159
- ```
160
-
161
- ## Writing Good Evals
162
-
163
- Evals live in `evals/*.jsonl`:
164
-
165
- ```json
166
- {
167
- "id": "eval-001",
168
- "name": "simple_function",
169
- "prompt": "Write a function that checks if a string is a palindrome",
170
- "expected": {
171
- "has_function": true,
172
- "handles_edge_cases": true
173
- },
174
- "weight": 1.0
175
- }
176
- ```
177
-
178
- ### Expected Checks
179
-
180
- | Check | What It Looks For |
181
- |-------|-------------------|
182
- | `has_function` | Function definition in output |
183
- | `handles_edge_cases` | Null/empty/edge case handling |
184
- | `uses_async` | async/await usage |
185
- | `bug_fixed` | Fix-related language |
186
- | `explains_fix` | Explanation of changes |
187
- | Custom key | Looks for key as substring |
188
-
189
- ## Directory Structure
190
-
191
- ```
192
- scripts/gepa/
193
- ├── config.json # Settings
194
- ├── state.json # Current state
195
- ├── optimize.js # Main optimizer
196
- ├── hooks/
197
- │ ├── eval-tracker.js # Session tracking hook
198
- │ └── reflect.js # Reflection engine
199
- ├── evals/
200
- │ └── coding-tasks.jsonl
201
- ├── generations/
202
- │ ├── gen-000/
203
- │ │ └── baseline.md
204
- │ ├── gen-001/
205
- │ │ ├── variant-a.md
206
- │ │ ├── variant-b.md
207
- │ │ └── baseline.md
208
- │ └── current -> gen-001/variant-a.md
209
- └── results/
210
- ├── scores.jsonl
211
- └── sessions/
212
- ```
213
-
214
- ## Best Practices
215
-
216
- 1. **Start with good evals** - Garbage in, garbage out
217
- 2. **Run multiple generations** - Improvements compound
218
- 3. **Review diffs** - Understand what changed
219
- 4. **Keep baseline** - Always compare against original
220
- 5. **Monitor for drift** - Watch for unintended changes
221
-
222
- ## Example Output
223
-
224
- ```
225
- $ node optimize.js run 3
226
-
227
- ============================================================
228
- GENERATION 1/3
229
- ============================================================
230
-
231
- Generating 4 variants for generation 1...
232
- Creating variant-a using strategy: rephrase
233
- Creating variant-b using strategy: add_examples
234
- Creating variant-c using strategy: remove_redundancy
235
- Creating variant-d using strategy: restructure
236
-
237
- Scoring 5 variants in generation 1...
238
- Running evals on baseline... Score: 65.0%
239
- Running evals on variant-a... Score: 72.0%
240
- Running evals on variant-b... Score: 78.0%
241
- Running evals on variant-c... Score: 70.0%
242
- Running evals on variant-d... Score: 68.0%
243
-
244
- Results:
245
- 1. variant-b: 78.0% <-- BEST
246
- 2. variant-a: 72.0%
247
- 3. variant-c: 70.0%
248
- 4. variant-d: 68.0%
249
- 5. baseline: 65.0%
250
-
251
- New best: variant-b (78.0%)
252
-
253
- ============================================================
254
- OPTIMIZATION COMPLETE
255
- ============================================================
256
- Best variant: variant-b
257
- Best score: 85.2%
258
- Generations: 3
259
-
260
- To apply: cp generations/current /path/to/your/CLAUDE.md
261
- ```
262
-
263
- ## Troubleshooting
264
-
265
- **"claude CLI not found"**
266
- Set `ANTHROPIC_API_KEY` for API fallback.
267
-
268
- **Slow evals**
269
- Reduce `minSamplesPerVariant` in config.
270
-
271
- **Poor results**
272
- Add more diverse evals covering failure modes.
273
-
274
- **Drift from original intent**
275
- Add evals that test for desired behaviors explicitly.
@@ -1,59 +0,0 @@
1
- {
2
- "name": "GEPA - Genetic Eval-driven Prompt Algorithm",
3
- "version": "1.0.0",
4
-
5
- "target": {
6
- "file": "CLAUDE.md",
7
- "scope": "project",
8
- "backup": true
9
- },
10
-
11
- "evolution": {
12
- "populationSize": 4,
13
- "generations": 10,
14
- "selectionRate": 0.5,
15
- "mutationStrategies": [
16
- "rephrase",
17
- "add_examples",
18
- "remove_redundancy",
19
- "restructure",
20
- "add_constraints",
21
- "simplify"
22
- ]
23
- },
24
-
25
- "evals": {
26
- "directory": "./evals",
27
- "minSamplesPerVariant": 8,
28
- "timeout": 120000,
29
- "metrics": [
30
- "task_completion",
31
- "code_quality",
32
- "instruction_following",
33
- "efficiency"
34
- ]
35
- },
36
-
37
- "judge": {
38
- "model": "claude-haiku-4-5-20251001",
39
- "maxOutputTokens": 2000,
40
- "timeoutMs": 30000
41
- },
42
-
43
- "scoring": {
44
- "weights": {
45
- "task_completion": 0.4,
46
- "code_quality": 0.25,
47
- "instruction_following": 0.25,
48
- "efficiency": 0.1
49
- },
50
- "threshold": 0.7
51
- },
52
-
53
- "hooks": {
54
- "trackToolCalls": true,
55
- "trackTokenUsage": true,
56
- "trackErrors": true,
57
- "trackUserFeedback": true
58
- }
59
- }
@@ -1,5 +0,0 @@
1
- {"id": "eval-001", "name": "simple_function", "prompt": "Write a function that checks if a string is a palindrome", "expected": {"has_function": true, "handles_edge_cases": true, "is_tested": false}, "weight": 1.0}
2
- {"id": "eval-002", "name": "refactor_code", "prompt": "Refactor this code to use async/await instead of callbacks", "input_file": "fixtures/callback-hell.js", "expected": {"uses_async": true, "preserves_behavior": true, "no_nested_callbacks": true}, "weight": 1.5}
3
- {"id": "eval-003", "name": "fix_bug", "prompt": "Fix the off-by-one error in this loop", "input_file": "fixtures/buggy-loop.js", "expected": {"bug_fixed": true, "no_new_bugs": true, "explains_fix": true}, "weight": 1.2}
4
- {"id": "eval-004", "name": "add_feature", "prompt": "Add pagination to this API endpoint", "input_file": "fixtures/api-endpoint.ts", "expected": {"has_pagination": true, "has_limit": true, "has_offset": true, "backwards_compatible": true}, "weight": 1.5}
5
- {"id": "eval-005", "name": "code_review", "prompt": "Review this PR and identify issues", "input_file": "fixtures/pr-diff.txt", "expected": {"identifies_security_issue": true, "identifies_performance_issue": true, "actionable_feedback": true}, "weight": 1.3}
@@ -1,31 +0,0 @@
1
- // Simple API endpoint that needs pagination added
2
- import express from 'express';
3
-
4
- const router = express.Router();
5
-
6
- interface User {
7
- id: number;
8
- name: string;
9
- email: string;
10
- }
11
-
12
- // In-memory store
13
- const users: User[] = Array.from({ length: 100 }, (_, i) => ({
14
- id: i + 1,
15
- name: `User ${i + 1}`,
16
- email: `user${i + 1}@example.com`,
17
- }));
18
-
19
- // GET /users - returns ALL users (no pagination)
20
- router.get('/users', (req, res) => {
21
- res.json(users);
22
- });
23
-
24
- // GET /users/:id
25
- router.get('/users/:id', (req, res) => {
26
- const user = users.find((u) => u.id === parseInt(req.params.id));
27
- if (!user) return res.status(404).json({ error: 'Not found' });
28
- res.json(user);
29
- });
30
-
31
- export default router;
@@ -1,38 +0,0 @@
1
- // Integration handler that crashes on external API failure
2
- // Needs graceful degradation following the DiffMem/Greptile pattern
3
-
4
- interface MCPResponse {
5
- content: Array<{ type: string; text: string }>;
6
- metadata?: Record<string, unknown>;
7
- }
8
-
9
- // This handler crashes the entire MCP server when the API is unreachable.
10
- // Refactor to degrade gracefully.
11
- async function handleExternalLookup(args: {
12
- query: string;
13
- limit?: number;
14
- }): Promise<MCPResponse> {
15
- const response = await fetch('https://api.external-service.com/search', {
16
- method: 'POST',
17
- headers: { 'Content-Type': 'application/json' },
18
- body: JSON.stringify({ query: args.query, limit: args.limit || 10 }),
19
- });
20
-
21
- // BUG: No error handling — fetch failures (ECONNREFUSED, DNS, timeout)
22
- // will throw and crash the MCP server process.
23
- // BUG: 4xx errors (bad request, auth failed) should not be retried.
24
- // BUG: Logs at error level, flooding logs when service is down.
25
-
26
- if (!response.ok) {
27
- throw new Error(`API error: ${response.status}`);
28
- }
29
-
30
- const data = await response.json();
31
-
32
- return {
33
- content: [{ type: 'text', text: JSON.stringify(data, null, 2) }],
34
- metadata: { tool: 'external_lookup', resultCount: data.results?.length },
35
- };
36
- }
37
-
38
- export { handleExternalLookup };
@@ -1,18 +0,0 @@
1
- // Bug: Off-by-one error - processes one too many items
2
- function processItems(items) {
3
- const results = [];
4
-
5
- // BUG: should be i < items.length, not i <= items.length
6
- for (let i = 0; i <= items.length; i++) {
7
- const item = items[i];
8
- results.push(item.toUpperCase()); // Will crash on undefined
9
- }
10
-
11
- return results;
12
- }
13
-
14
- // Test
15
- const items = ['apple', 'banana', 'cherry'];
16
- console.log(processItems(items)); // Crashes!
17
-
18
- module.exports = { processItems };
@@ -1,53 +0,0 @@
1
- // Callback hell example - needs refactoring to async/await
2
- function fetchUserData(userId, callback) {
3
- getUser(userId, function (err, user) {
4
- if (err) {
5
- callback(err);
6
- return;
7
- }
8
-
9
- getPosts(user.id, function (err, posts) {
10
- if (err) {
11
- callback(err);
12
- return;
13
- }
14
-
15
- getComments(posts[0].id, function (err, comments) {
16
- if (err) {
17
- callback(err);
18
- return;
19
- }
20
-
21
- getLikes(comments[0].id, function (err, likes) {
22
- if (err) {
23
- callback(err);
24
- return;
25
- }
26
-
27
- callback(null, {
28
- user: user,
29
- posts: posts,
30
- comments: comments,
31
- likes: likes,
32
- });
33
- });
34
- });
35
- });
36
- });
37
- }
38
-
39
- // Mock functions
40
- function getUser(id, cb) {
41
- cb(null, { id, name: 'Test' });
42
- }
43
- function getPosts(userId, cb) {
44
- cb(null, [{ id: 1, title: 'Post' }]);
45
- }
46
- function getComments(postId, cb) {
47
- cb(null, [{ id: 1, text: 'Comment' }]);
48
- }
49
- function getLikes(commentId, cb) {
50
- cb(null, [{ id: 1 }]);
51
- }
52
-
53
- module.exports = { fetchUserData };
@@ -1,23 +0,0 @@
1
- -- FTS5 external content table for full-text search on frames
2
- CREATE VIRTUAL TABLE IF NOT EXISTS frames_fts USING fts5(
3
- name, digest_text, inputs, outputs,
4
- content='frames', content_rowid='rowid'
5
- );
6
-
7
- -- Sync trigger: INSERT
8
- CREATE TRIGGER IF NOT EXISTS frames_ai AFTER INSERT ON frames BEGIN
9
- INSERT INTO frames_fts(rowid, name, digest_text, inputs, outputs)
10
- VALUES (new.rowid, new.name, new.digest_text, new.inputs, new.outputs);
11
- END;
12
-
13
- -- Sync trigger: UPDATE
14
- CREATE TRIGGER IF NOT EXISTS frames_au AFTER UPDATE ON frames BEGIN
15
- INSERT INTO frames_fts(frames_fts, rowid, name, digest_text, inputs, outputs)
16
- VALUES ('delete', old.rowid, old.name, old.digest_text, old.inputs, old.outputs);
17
- INSERT INTO frames_fts(rowid, name, digest_text, inputs, outputs)
18
- VALUES (new.rowid, new.name, new.digest_text, new.inputs, new.outputs);
19
- END;
20
-
21
- -- BUG: Missing DELETE trigger!
22
- -- When a frame is deleted, the FTS index still contains stale data.
23
- -- Add the missing AFTER DELETE trigger below.
@@ -1,70 +0,0 @@
1
- // Daemon service with timer leak bug
2
- interface ServiceConfig {
3
- enabled: boolean;
4
- interval: number; // minutes
5
- }
6
-
7
- interface ServiceState {
8
- isRunning: boolean;
9
- intervalMs: number;
10
- lastRunTime: number;
11
- errorCount: number;
12
- }
13
-
14
- class MonitorService {
15
- private config: ServiceConfig;
16
- private intervalId?: NodeJS.Timeout;
17
- private isRunning = false;
18
- private lastRunTime = 0;
19
- private errorCount = 0;
20
-
21
- constructor(config: ServiceConfig) {
22
- this.config = config;
23
- }
24
-
25
- // BUG: No guard against double-start — calling start() twice
26
- // creates two intervals but only stores the second one.
27
- // The first interval leaks and keeps running forever.
28
- start(): void {
29
- this.isRunning = true;
30
- const intervalMs = this.config.interval * 60 * 1000;
31
-
32
- this.doWork(); // initial run
33
-
34
- this.intervalId = setInterval(() => {
35
- this.doWork();
36
- }, intervalMs);
37
- }
38
-
39
- stop(): void {
40
- if (this.intervalId) {
41
- clearInterval(this.intervalId);
42
- this.intervalId = undefined;
43
- }
44
- this.isRunning = false;
45
- }
46
-
47
- // BUG: Calls stop() then start() but start() doesn't check
48
- // if already running, so if stop() fails to clear the interval
49
- // (e.g., intervalId is undefined), we leak.
50
- updateConfig(config: Partial<ServiceConfig>): void {
51
- const wasRunning = this.isRunning;
52
- if (wasRunning) this.stop();
53
- this.config = { ...this.config, ...config };
54
- if (wasRunning && this.config.enabled) this.start();
55
- }
56
-
57
- // TODO: Add getState() method returning ServiceState
58
-
59
- private doWork(): void {
60
- try {
61
- // Simulate work
62
- console.log('Running monitor check...');
63
- this.lastRunTime = Date.now();
64
- } catch (err) {
65
- this.errorCount++;
66
- }
67
- }
68
- }
69
-
70
- export { MonitorService, ServiceConfig, ServiceState };
@@ -1,39 +0,0 @@
1
- // Existing MCP server dispatch pattern — add get_frame_summary handler
2
- import { z } from 'zod';
3
-
4
- interface Frame {
5
- id: string;
6
- name: string;
7
- status: 'open' | 'closed';
8
- events: Array<{ id: string; type: string }>;
9
- }
10
-
11
- // Simulated frame store
12
- const frames = new Map<string, Frame>();
13
-
14
- // Existing tool dispatch (add your handler here)
15
- async function handleToolCall(name: string, args: unknown) {
16
- switch (name) {
17
- case 'start_frame': {
18
- const input = z.object({ name: z.string().min(1) }).parse(args);
19
- const id = `frame-${Date.now()}`;
20
- frames.set(id, { id, name: input.name, status: 'open', events: [] });
21
- return { frameId: id, status: 'opened' };
22
- }
23
-
24
- case 'close_frame': {
25
- const input = z.object({ frameId: z.string() }).parse(args);
26
- const frame = frames.get(input.frameId);
27
- if (!frame) throw new Error(`Frame not found: ${input.frameId}`);
28
- frame.status = 'closed';
29
- return { frameId: frame.id, status: 'closed' };
30
- }
31
-
32
- // TODO: Add get_frame_summary handler here
33
-
34
- default:
35
- throw new Error(`Unknown tool: ${name}`);
36
- }
37
- }
38
-
39
- export { handleToolCall };
@@ -1,24 +0,0 @@
1
- diff --git a/src/auth/login.ts b/src/auth/login.ts
2
- index 1a2b3c4..5d6e7f8 100644
3
- --- a/src/auth/login.ts
4
- +++ b/src/auth/login.ts
5
- @@ -12,8 +12,15 @@ export async function handleLogin(req: Request, res: Response) {
6
- const { email, password } = req.body;
7
-
8
- - const user = await db.query('SELECT * FROM users WHERE email = $1', [email]);
9
- + // Quick fix: direct string interpolation for faster queries
10
- + const user = await db.query(`SELECT * FROM users WHERE email = '${email}'`);
11
- if (!user) return res.status(401).json({ error: 'Invalid credentials' });
12
-
13
- - const valid = await bcrypt.compare(password, user.passwordHash);
14
- + const valid = password === user.passwordHash;
15
- if (!valid) return res.status(401).json({ error: 'Invalid credentials' });
16
-
17
- + // Store session
18
- + const token = email + ':' + Date.now();
19
- + res.cookie('session', token);
20
- +
21
- + // Log for debugging
22
- + console.log(`Login: ${email} / ${password}`);
23
- +
24
- return res.json({ success: true, user: { id: user.id, email: user.email } });
@@ -1,34 +0,0 @@
1
- // Webhook handler with validation vulnerabilities
2
- interface WebhookPayload {
3
- action: string;
4
- type: string;
5
- data: {
6
- id: string;
7
- title?: string;
8
- description?: string;
9
- [key: string]: unknown;
10
- };
11
- }
12
-
13
- // VULNERABLE: No prototype pollution protection, no length limits,
14
- // no action validation. Fix this function.
15
- function validateWebhookPayload(payload: unknown): WebhookPayload | null {
16
- if (!payload || typeof payload !== 'object') return null;
17
-
18
- const p = payload as Record<string, unknown>;
19
-
20
- if (!p.action || typeof p.action !== 'string') return null;
21
- if (!p.type || typeof p.type !== 'string') return null;
22
- if (!p.data || typeof p.data !== 'object') return null;
23
-
24
- const data = p.data as Record<string, unknown>;
25
- if (!data.id || typeof data.id !== 'string') return null;
26
-
27
- // No sanitization — title and description can be any length
28
- // No action validation — accepts any string
29
- // No prototype pollution check — __proto__ passes through
30
-
31
- return p as unknown as WebhookPayload;
32
- }
33
-
34
- export { validateWebhookPayload, WebhookPayload };