@barissozen/csns 0.7.5 → 0.7.6
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 +215 -225
- package/dist/agent/index.d.ts +1 -0
- package/dist/agent/index.d.ts.map +1 -1
- package/dist/agent/index.js +1 -0
- package/dist/agent/index.js.map +1 -1
- package/dist/agent/tracer/index.d.ts +2 -0
- package/dist/agent/tracer/index.d.ts.map +1 -1
- package/dist/agent/tracer/index.js +1 -0
- package/dist/agent/tracer/index.js.map +1 -1
- package/dist/agent/tracer/security-scanner.d.ts +54 -0
- package/dist/agent/tracer/security-scanner.d.ts.map +1 -0
- package/dist/agent/tracer/security-scanner.js +397 -0
- package/dist/agent/tracer/security-scanner.js.map +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,64 +1,93 @@
|
|
|
1
|
-
#
|
|
1
|
+
# CSNS
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> **C**ode-aware **S**elf-correcting **N**ever-forgetting **S**ystem
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Just say what you want. It handles the rest.
|
|
6
|
+
Or point it at existing code — it reverse-engineers, audits, and scores your architecture.
|
|
6
7
|
|
|
7
|
-
**Multi-model** · **Multi-language** · **Memory-first** · **Self-correcting**
|
|
8
|
+
**Multi-model** · **Multi-language** · **Memory-first** · **Self-correcting** · **Codebase auditor**
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npm install -g @barissozen/csns
|
|
12
|
+
csns
|
|
13
|
+
```
|
|
8
14
|
|
|
9
15
|
---
|
|
10
16
|
|
|
11
|
-
##
|
|
17
|
+
## What It Does
|
|
12
18
|
|
|
13
19
|
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
>
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
20
|
+
csns> /new "URL shortener with auth" → builds it from scratch
|
|
21
|
+
csns> /audit → reverse-engineers & scores existing code
|
|
22
|
+
csns> /trace → 4-layer deep analysis
|
|
23
|
+
csns> /health → checks LLM + agent + memory status
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
**Two modes:**
|
|
27
|
+
1. **Build** — describe what you want, CSNS makes technical decisions, writes code, tests it, audits the result
|
|
28
|
+
2. **Audit** — point it at any TypeScript project, it classifies layers, traces data flows, checks architectural decisions, scores health 0–100
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## Demo: Build
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
$ csns
|
|
36
|
+
|
|
37
|
+
╔══════════════════════════════════════════════╗
|
|
38
|
+
║ CSNS v0.7.5 ║
|
|
39
|
+
║ Code-aware Self-correcting ║
|
|
40
|
+
║ Never-forgetting System ║
|
|
41
|
+
╚══════════════════════════════════════════════╝
|
|
42
|
+
|
|
43
|
+
Type /help for commands, /quit to exit.
|
|
44
|
+
|
|
45
|
+
csns> /new
|
|
46
|
+
📋 What do you want to build?
|
|
47
|
+
> URL shortener with user registration
|
|
48
|
+
|
|
49
|
+
🔍 Analyzing...
|
|
50
|
+
✅ JWT Auth · ✅ SQLite · ✅ REST API · ✅ TypeScript
|
|
51
|
+
|
|
52
|
+
🚀 Starting orchestration...
|
|
53
|
+
📦 M01: Foundation → ✅ (tsc ✅, test ✅)
|
|
54
|
+
📦 M02: Auth → ✅ (tsc ✅, test ✅, endpoint ✅)
|
|
55
|
+
📦 M03: Shortener → ✅ (tsc ✅, test ✅, endpoint ✅)
|
|
56
|
+
|
|
57
|
+
🔍 Post-build audit...
|
|
58
|
+
💯 Health: 94/100 — 2 minor violations, auto-fixing...
|
|
59
|
+
✅ Re-audit: 100/100
|
|
60
|
+
|
|
61
|
+
csns> /quit
|
|
62
|
+
👋 Bye.
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
## Demo: Audit
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
csns> /audit
|
|
69
|
+
🔍 Running reverse engineering audit...
|
|
70
|
+
|
|
71
|
+
═══════════════════════════════════════════
|
|
72
|
+
📋 AUDIT REPORT
|
|
73
|
+
═══════════════════════════════════════════
|
|
74
|
+
|
|
75
|
+
🏗️ Layer Distribution:
|
|
76
|
+
controller 42 service 37 middleware 17
|
|
77
|
+
repository 239 config 16 schema 10
|
|
78
|
+
|
|
79
|
+
🔀 Data Flows: 5/68 complete
|
|
80
|
+
|
|
81
|
+
⚠️ Violations: 43 (43 acknowledged, 0 real)
|
|
82
|
+
✅ 19x GraphQL resolver-first pattern
|
|
83
|
+
✅ 30x Inline resolver logic — accepted convention
|
|
84
|
+
|
|
85
|
+
🧩 Patterns:
|
|
86
|
+
GraphQL Federation · GraphQL Resolvers · Event-Driven
|
|
87
|
+
Circuit Breaker · Service Layer · Middleware Chain
|
|
88
|
+
|
|
89
|
+
💯 Health Score: 96/100
|
|
90
|
+
═══════════════════════════════════════════
|
|
62
91
|
```
|
|
63
92
|
|
|
64
93
|
---
|
|
@@ -66,11 +95,8 @@ $ pc run
|
|
|
66
95
|
## Install
|
|
67
96
|
|
|
68
97
|
```bash
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
npm install -g project-consciousness
|
|
72
|
-
pc init
|
|
73
|
-
pc run
|
|
98
|
+
npm install -g @barissozen/csns
|
|
99
|
+
csns
|
|
74
100
|
```
|
|
75
101
|
|
|
76
102
|
**Requirements:** Node.js 20+, at least one LLM provider
|
|
@@ -86,8 +112,6 @@ export OLLAMA_HOST=http://localhost:11434 # Local (Llama, Mistral, etc.)
|
|
|
86
112
|
|
|
87
113
|
## Multi-Model Support
|
|
88
114
|
|
|
89
|
-
Use any LLM provider — switch with a single env variable:
|
|
90
|
-
|
|
91
115
|
| Provider | Env Variable | Models |
|
|
92
116
|
|----------|-------------|--------|
|
|
93
117
|
| **Anthropic** (default) | `ANTHROPIC_API_KEY` | Claude Sonnet, Opus, Haiku |
|
|
@@ -96,177 +120,151 @@ Use any LLM provider — switch with a single env variable:
|
|
|
96
120
|
| **OpenAI-compatible** | `OPENAI_API_KEY` + `LLM_BASE_URL` | Groq, Together, Azure, etc. |
|
|
97
121
|
|
|
98
122
|
```bash
|
|
99
|
-
# Auto-detect
|
|
100
|
-
|
|
101
|
-
pc run "Build a todo API"
|
|
123
|
+
# Auto-detect from env
|
|
124
|
+
csns
|
|
102
125
|
|
|
103
126
|
# Explicit override
|
|
104
|
-
LLM_PROVIDER=openai OPENAI_API_KEY=sk-...
|
|
127
|
+
LLM_PROVIDER=openai OPENAI_API_KEY=sk-... csns
|
|
105
128
|
|
|
106
|
-
# Local
|
|
107
|
-
LLM_PROVIDER=ollama
|
|
129
|
+
# Local — no API key needed
|
|
130
|
+
LLM_PROVIDER=ollama csns
|
|
108
131
|
```
|
|
109
132
|
|
|
110
|
-
|
|
133
|
+
---
|
|
111
134
|
|
|
112
|
-
|
|
113
|
-
import { createProvider } from 'project-consciousness/llm';
|
|
135
|
+
## CLI Commands
|
|
114
136
|
|
|
115
|
-
|
|
116
|
-
const provider = createProvider();
|
|
137
|
+
### Interactive (REPL)
|
|
117
138
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
139
|
+
```
|
|
140
|
+
csns → starts interactive prompt
|
|
141
|
+
csns> /new [brief] → build a new project
|
|
142
|
+
csns> /audit → reverse-engineer & audit codebase
|
|
143
|
+
csns> /trace → full 4-layer analysis
|
|
144
|
+
csns> /status → show STATE.md
|
|
145
|
+
csns> /log → show DECISIONS.md
|
|
146
|
+
csns> /health → check LLM + agent CLI + memory files
|
|
147
|
+
csns> /help → list commands
|
|
148
|
+
csns> /quit → exit
|
|
122
149
|
```
|
|
123
150
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
## Multi-Language (i18n)
|
|
127
|
-
|
|
128
|
-
All user-facing messages, prompts, and agent personas support multiple languages:
|
|
151
|
+
### Non-interactive (CI / scripts)
|
|
129
152
|
|
|
130
153
|
```bash
|
|
131
|
-
|
|
132
|
-
|
|
154
|
+
csns new "Build a todo API with auth"
|
|
155
|
+
csns audit
|
|
156
|
+
csns trace
|
|
157
|
+
csns status
|
|
158
|
+
csns health
|
|
133
159
|
```
|
|
134
160
|
|
|
135
|
-
Currently supported: **English** (`en`), **Turkish** (`tr`). Adding a new locale is one file — see `src/i18n/`.
|
|
136
|
-
|
|
137
161
|
---
|
|
138
162
|
|
|
139
|
-
##
|
|
140
|
-
|
|
141
|
-
The orchestrator spawns coding agents via CLI. Default is Claude Code, but any compatible CLI works:
|
|
163
|
+
## Audit Engine
|
|
142
164
|
|
|
143
|
-
|
|
144
|
-
AGENT_BINARY=claude pc run "..." # Claude Code (default)
|
|
145
|
-
AGENT_BINARY=codex pc run "..." # OpenAI Codex CLI
|
|
146
|
-
AGENT_BINARY=aider pc run "..." # Aider
|
|
147
|
-
```
|
|
165
|
+
The `/audit` command reverse-engineers any TypeScript project:
|
|
148
166
|
|
|
149
|
-
|
|
167
|
+
### 4 Layers
|
|
150
168
|
|
|
151
|
-
|
|
169
|
+
| Layer | What | How |
|
|
170
|
+
|-------|------|-----|
|
|
171
|
+
| **Static** | Import/export graph, dead exports, circular deps, phantom deps | Regex scan of all `.ts` files |
|
|
172
|
+
| **Semantic** | "This service should be injected but isn't" | LLM reasoning over graph + file summaries |
|
|
173
|
+
| **Runtime** | Server started, HTTP probed, handler chain traced | Express middleware + HTTP probing |
|
|
174
|
+
| **Audit** | Architecture recovery, decision archaeology, pattern detection | Layer classification + cross-reference |
|
|
152
175
|
|
|
153
|
-
|
|
176
|
+
### What It Finds
|
|
154
177
|
|
|
155
|
-
|
|
178
|
+
| Finding | Example |
|
|
179
|
+
|---------|---------|
|
|
180
|
+
| **Layer skip** | Controller → Repository (skipping service layer) |
|
|
181
|
+
| **Wrong direction** | Service imports controller (upward dependency) |
|
|
182
|
+
| **Dead export** | `unusedHelper()` exported but never imported |
|
|
183
|
+
| **Circular dep** | `logger → config → logger` |
|
|
184
|
+
| **Phantom dep** | `winston` imported but not in `package.json` |
|
|
185
|
+
| **Decision contradiction** | ARCHITECTURE says JWT but code uses session |
|
|
186
|
+
| **Pattern inconsistency** | 7 routes use service layer, 52 don't |
|
|
156
187
|
|
|
157
|
-
|
|
188
|
+
### Smart Acknowledgement
|
|
158
189
|
|
|
159
|
-
|
|
160
|
-
|------|-----------------|----------|
|
|
161
|
-
| `MISSION.md` | What to build, what NOT to build, success criteria | You (immutable) |
|
|
162
|
-
| `ARCHITECTURE.md` | Technical decisions — auth, DB, API style | System (auto) |
|
|
163
|
-
| `DECISIONS.md` | Every decision, why it was made, when | Log (append-only) |
|
|
164
|
-
| `STATE.md` | Current phase, what's done, what's left | Live status |
|
|
165
|
-
|
|
166
|
-
### Architecture
|
|
190
|
+
CSNS distinguishes **design decisions** from **real bugs**:
|
|
167
191
|
|
|
168
192
|
```
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
│
|
|
175
|
-
|
|
176
|
-
│ → MISSION.md + ARCHITECTURE.md │
|
|
177
|
-
└──────────┬──────────────────────┘
|
|
178
|
-
▼
|
|
179
|
-
┌─────────────────────────────────┐
|
|
180
|
-
│ Orchestrator │
|
|
181
|
-
│ Plan → Milestone → Agent → Test │
|
|
182
|
-
│ Failed? → 3x retry → ask you │
|
|
183
|
-
└──────────┬──────────────────────┘
|
|
184
|
-
▼
|
|
185
|
-
┌─────────────────────────────────┐
|
|
186
|
-
│ Memory Layer │
|
|
187
|
-
│ Every decision → DECISIONS.md │
|
|
188
|
-
│ Every step → STATE.md │
|
|
189
|
-
│ Nothing is lost │
|
|
190
|
-
└─────────────────────────────────┘
|
|
193
|
+
Violation found
|
|
194
|
+
│
|
|
195
|
+
├── GraphQL resolver → DB direct? → ✅ Acknowledged (resolver-first pattern)
|
|
196
|
+
├── Written in ARCHITECTURE.md? → ✅ Acknowledged (explicit decision)
|
|
197
|
+
├── >80% routes follow same way? → ✅ Acknowledged (project convention)
|
|
198
|
+
│
|
|
199
|
+
└── None of the above? → ⚠️ Real issue — counts against health
|
|
191
200
|
```
|
|
192
201
|
|
|
193
|
-
###
|
|
194
|
-
|
|
195
|
-
| Asks you ✅ | Decides itself ❌ |
|
|
196
|
-
|---|---|
|
|
197
|
-
| "Are links public or private?" | JWT or session? |
|
|
198
|
-
| "Can users see each other's data?" | Which database? |
|
|
199
|
-
| "Will there be payments?" | REST or GraphQL? |
|
|
200
|
-
| "Do links expire?" | File structure? |
|
|
201
|
-
|
|
202
|
-
Technical decisions are inferred from your brief. Only **product decisions** — things you need to know — are asked.
|
|
203
|
-
|
|
204
|
-
### Quality Control Pipeline
|
|
205
|
-
|
|
206
|
-
After code is written, it's verified:
|
|
207
|
-
|
|
208
|
-
1. **Type checking** — `tsc --noEmit`
|
|
209
|
-
2. **Test execution** — `vitest run` / `pytest` / `go test`
|
|
210
|
-
3. **HTTP endpoint testing** — server started, real HTTP requests sent
|
|
211
|
-
4. **Anti-scope enforcement** — protected files touched? forbidden deps added?
|
|
202
|
+
### Tested On Real Projects
|
|
212
203
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
204
|
+
```
|
|
205
|
+
Project Files Violations Health
|
|
206
|
+
──────────────────────────────────────────────────────────
|
|
207
|
+
Cayman Data 37 0 (0 ack, 0 real) 100.0 💯
|
|
208
|
+
Yieldex 84 1 (0 ack, 1 real) 98.3
|
|
209
|
+
Wallet SDK 26 1 (0 ack, 1 real) 97.8
|
|
210
|
+
Cayman-Hashlock 530 43 (43 ack, 0 real) 96.0
|
|
211
|
+
CSNS (self) 72 6 (2 ack, 4 real) 93.0
|
|
212
|
+
Cayman Mobile 32 5 (0 ack, 5 real) 89.4
|
|
213
|
+
```
|
|
216
214
|
|
|
217
|
-
|
|
215
|
+
### Programmatic
|
|
218
216
|
|
|
219
217
|
```typescript
|
|
220
|
-
import {
|
|
218
|
+
import { ReverseEngineer } from '@barissozen/csns/agent';
|
|
221
219
|
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
llmProvider: provider, // any LLMProvider
|
|
225
|
-
port: 3000,
|
|
226
|
-
});
|
|
220
|
+
const auditor = new ReverseEngineer('/path/to/project');
|
|
221
|
+
const report = await auditor.audit();
|
|
227
222
|
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
//
|
|
223
|
+
report.classifications // file → layer mapping
|
|
224
|
+
report.dataFlows // end-to-end request chains
|
|
225
|
+
report.violations // architectural issues (acknowledged vs real)
|
|
226
|
+
report.decisionAudit // DECISIONS.md cross-check
|
|
227
|
+
report.patterns // detected design patterns
|
|
228
|
+
report.summary.healthScore // 0–100
|
|
232
229
|
```
|
|
233
230
|
|
|
234
|
-
**3-layer analysis:**
|
|
235
|
-
|
|
236
|
-
| Layer | What | How |
|
|
237
|
-
|-------|------|-----|
|
|
238
|
-
| **Static** | Import/export graph, dead code, circular deps, phantom deps | Regex scan of all `.ts` files |
|
|
239
|
-
| **Semantic** | "This service should be injected but isn't" | LLM reasoning over graph + file summaries |
|
|
240
|
-
| **Runtime** | Server started, HTTP probed, handler chain traced | Express middleware injection + HTTP probing |
|
|
241
|
-
|
|
242
231
|
---
|
|
243
232
|
|
|
244
|
-
##
|
|
245
|
-
|
|
246
|
-
| Command | What It Does |
|
|
247
|
-
|---------|-------------|
|
|
248
|
-
| `pc init` | Collect brief → create 4 memory files |
|
|
249
|
-
| `pc run` | Start the orchestrator |
|
|
250
|
-
| `pc run "Build a todo API"` | Start with inline brief |
|
|
251
|
-
| `pc status` | Show STATE.md |
|
|
252
|
-
| `pc log` | Show DECISIONS.md |
|
|
253
|
-
| `pc help` | Help |
|
|
233
|
+
## Build Engine
|
|
254
234
|
|
|
255
|
-
|
|
235
|
+
### The 4-File Memory System
|
|
256
236
|
|
|
257
|
-
|
|
237
|
+
| File | What | Who |
|
|
238
|
+
|------|------|-----|
|
|
239
|
+
| `MISSION.md` | What to build, what NOT to build, success criteria | You (immutable) |
|
|
240
|
+
| `ARCHITECTURE.md` | Technical decisions — auth, DB, API style | System (auto) |
|
|
241
|
+
| `DECISIONS.md` | Every decision + rationale (append-only) | Log |
|
|
242
|
+
| `STATE.md` | Current phase, what's done, what's left | Live |
|
|
258
243
|
|
|
259
|
-
|
|
244
|
+
### Build → Audit → Fix Loop
|
|
260
245
|
|
|
261
|
-
```
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
246
|
+
```
|
|
247
|
+
/new "Build a todo API"
|
|
248
|
+
│
|
|
249
|
+
▼
|
|
250
|
+
SmartBrief → Scaffold → Orchestrator → Agents write code
|
|
251
|
+
│ │
|
|
252
|
+
│ tsc ✅ test ✅
|
|
253
|
+
│ │
|
|
254
|
+
▼ ▼
|
|
255
|
+
MISSION.md Audit Gate (automatic)
|
|
256
|
+
ARCHITECTURE.md Health < 70? → Fix tasks
|
|
257
|
+
DECISIONS.md Re-audit → ✅ Done
|
|
258
|
+
STATE.md
|
|
267
259
|
```
|
|
268
260
|
|
|
269
|
-
|
|
261
|
+
### What It Asks vs. What It Decides
|
|
262
|
+
|
|
263
|
+
| Asks you ✅ | Decides itself ❌ |
|
|
264
|
+
|---|---|
|
|
265
|
+
| "Are links public or private?" | JWT or session? |
|
|
266
|
+
| "Can users see each other's data?" | Which database? |
|
|
267
|
+
| "Will there be payments?" | REST or GraphQL? |
|
|
270
268
|
|
|
271
269
|
---
|
|
272
270
|
|
|
@@ -279,84 +277,76 @@ Everything is logged, nothing is deleted:
|
|
|
279
277
|
| `ANTHROPIC_API_KEY` | Anthropic API key | — |
|
|
280
278
|
| `OPENAI_API_KEY` | OpenAI API key | — |
|
|
281
279
|
| `OLLAMA_HOST` | Ollama server URL | `http://localhost:11434` |
|
|
282
|
-
| `LLM_PROVIDER` | Force
|
|
280
|
+
| `LLM_PROVIDER` | Force: `anthropic` / `openai` / `ollama` | auto-detect |
|
|
283
281
|
| `LLM_MODEL` | Model name | provider default |
|
|
284
|
-
| `LLM_BASE_URL` | Custom API base URL
|
|
282
|
+
| `LLM_BASE_URL` | Custom API base URL | — |
|
|
285
283
|
| `AGENT_BINARY` | Coding agent CLI binary | `claude` |
|
|
286
|
-
| `
|
|
284
|
+
| `CSNS_LOCALE` | Language: `en` / `tr` | `en` |
|
|
287
285
|
|
|
288
286
|
### Programmatic
|
|
289
287
|
|
|
290
288
|
```typescript
|
|
291
|
-
import { Orchestrator } from '
|
|
289
|
+
import { Orchestrator } from '@barissozen/csns/orchestrator';
|
|
290
|
+
import { createProvider } from '@barissozen/csns/llm';
|
|
291
|
+
import { TracerAgent } from '@barissozen/csns/agent';
|
|
292
292
|
|
|
293
|
+
// Build
|
|
293
294
|
const orchestrator = new Orchestrator({
|
|
294
295
|
projectRoot: process.cwd(),
|
|
295
296
|
llmProvider: 'openai',
|
|
296
297
|
llmApiKey: 'sk-...',
|
|
297
|
-
llmModel: 'gpt-4o',
|
|
298
|
-
agentBinary: 'claude',
|
|
299
298
|
locale: 'en',
|
|
300
299
|
maxRetries: 3,
|
|
301
|
-
escalationThreshold: 0.4,
|
|
302
|
-
maxParallelAgents: 3,
|
|
303
300
|
verbose: true,
|
|
304
301
|
});
|
|
302
|
+
await orchestrator.run('Build a REST API for todos');
|
|
305
303
|
|
|
306
|
-
|
|
304
|
+
// Audit
|
|
305
|
+
const tracer = new TracerAgent({
|
|
306
|
+
projectRoot: '/path/to/project',
|
|
307
|
+
llmProvider: createProvider(),
|
|
308
|
+
});
|
|
309
|
+
const report = await tracer.run();
|
|
307
310
|
```
|
|
308
311
|
|
|
309
312
|
---
|
|
310
313
|
|
|
311
|
-
##
|
|
312
|
-
|
|
313
|
-
### Project Structure
|
|
314
|
+
## Project Structure
|
|
314
315
|
|
|
315
316
|
```
|
|
316
317
|
src/
|
|
318
|
+
├── bin/ CLI — csns (interactive REPL + non-interactive)
|
|
317
319
|
├── brief/ SmartBrief + BriefCollector
|
|
318
320
|
├── agent/ Agent Runner, Context Builder, Codebase Reader
|
|
319
|
-
│ └── tracer/
|
|
320
|
-
|
|
321
|
+
│ └── tracer/ Static Analyzer, Semantic Analyzer, Runtime Tracer,
|
|
322
|
+
│ Reverse Engineer (audit engine)
|
|
323
|
+
├── orchestrator/ Planner, Evaluator, Escalator, Scaffold, Audit Gate
|
|
321
324
|
├── memory/ 4-file read/write layer
|
|
322
|
-
├── llm/
|
|
325
|
+
├── llm/ Provider abstraction (Anthropic, OpenAI, Ollama)
|
|
323
326
|
├── i18n/ Internationalization (en, tr)
|
|
324
|
-
|
|
325
|
-
└── bin/ CLI (pc init/run/status/log)
|
|
327
|
+
└── types/ TypeScript interfaces
|
|
326
328
|
```
|
|
327
329
|
|
|
328
|
-
|
|
330
|
+
## Tests
|
|
329
331
|
|
|
330
332
|
```bash
|
|
331
|
-
npm test
|
|
332
|
-
|
|
333
|
-
SKIP_E2E=1 npm test # skip real CLI tests
|
|
333
|
+
npm test # 229 tests, 19 suites
|
|
334
|
+
SKIP_E2E=1 npm test # skip real CLI tests
|
|
334
335
|
```
|
|
335
336
|
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
### Stack
|
|
339
|
-
|
|
340
|
-
- **TypeScript + Node.js** — strict mode, ESM
|
|
341
|
-
- **LLM Providers** — Anthropic SDK, OpenAI API, Ollama REST (pluggable)
|
|
342
|
-
- **Agent Execution** — Claude Code CLI (configurable)
|
|
343
|
-
- **Testing** — Vitest (229 tests)
|
|
344
|
-
- **Storage** — File system (markdown, no DB)
|
|
345
|
-
|
|
346
|
-
### Design Principles
|
|
337
|
+
## Design Principles
|
|
347
338
|
|
|
348
339
|
1. **Memory-First** — Every decision leaves a trace in files
|
|
349
340
|
2. **Fail-Safe** — When in doubt, ask the human
|
|
350
341
|
3. **Append-Only** — DECISIONS.md is never edited
|
|
351
|
-
4. **
|
|
352
|
-
5. **
|
|
353
|
-
6. **Provider-Agnostic** — Works with any LLM, any agent CLI
|
|
342
|
+
4. **Provider-Agnostic** — Any LLM, any agent CLI
|
|
343
|
+
5. **Acknowledged vs Real** — Design decisions ≠ bugs
|
|
354
344
|
|
|
355
|
-
|
|
345
|
+
## Contributing
|
|
356
346
|
|
|
357
347
|
1. Fork → branch → test → PR
|
|
358
348
|
2. `npm test` must pass
|
|
359
|
-
3. `npx tsc --noEmit`
|
|
349
|
+
3. `npx tsc --noEmit` — 0 errors
|
|
360
350
|
4. Conventional commits (`feat:`, `fix:`, `docs:`)
|
|
361
351
|
|
|
362
352
|
## License
|
package/dist/agent/index.d.ts
CHANGED
|
@@ -11,4 +11,5 @@ export { StaticAnalyzer } from './tracer/index.js';
|
|
|
11
11
|
export { SemanticAnalyzer } from './tracer/index.js';
|
|
12
12
|
export { RuntimeTracer } from './tracer/index.js';
|
|
13
13
|
export { ReverseEngineer } from './tracer/index.js';
|
|
14
|
+
export { SecurityScanner } from './tracer/index.js';
|
|
14
15
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAC;AAC3D,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/agent/index.js
CHANGED
|
@@ -9,4 +9,5 @@ export { StaticAnalyzer } from './tracer/index.js';
|
|
|
9
9
|
export { SemanticAnalyzer } from './tracer/index.js';
|
|
10
10
|
export { RuntimeTracer } from './tracer/index.js';
|
|
11
11
|
export { ReverseEngineer } from './tracer/index.js';
|
|
12
|
+
export { SecurityScanner } from './tracer/index.js';
|
|
12
13
|
//# sourceMappingURL=index.js.map
|
package/dist/agent/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/agent/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AAClD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC"}
|
|
@@ -5,4 +5,6 @@ export { SemanticAnalyzer } from './semantic-analyzer.js';
|
|
|
5
5
|
export { RuntimeTracer } from './runtime-tracer.js';
|
|
6
6
|
export { ReverseEngineer } from './reverse-engineer.js';
|
|
7
7
|
export type { AuditReport, FileClassification, DataFlowChain, ArchitectureViolation, DecisionAuditResult, ArchLayer, } from './reverse-engineer.js';
|
|
8
|
+
export { SecurityScanner } from './security-scanner.js';
|
|
9
|
+
export type { SecurityReport, SecurityFinding } from './security-scanner.js';
|
|
8
10
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/tracer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,GACV,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/agent/tracer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAChD,YAAY,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACtD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EACV,WAAW,EACX,kBAAkB,EAClB,aAAa,EACb,qBAAqB,EACrB,mBAAmB,EACnB,SAAS,GACV,MAAM,uBAAuB,CAAC;AAC/B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -3,4 +3,5 @@ export { StaticAnalyzer } from './static-analyzer.js';
|
|
|
3
3
|
export { SemanticAnalyzer } from './semantic-analyzer.js';
|
|
4
4
|
export { RuntimeTracer } from './runtime-tracer.js';
|
|
5
5
|
export { ReverseEngineer } from './reverse-engineer.js';
|
|
6
|
+
export { SecurityScanner } from './security-scanner.js';
|
|
6
7
|
//# sourceMappingURL=index.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/tracer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/agent/tracer/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAEhD,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAC;AAC1D,OAAO,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AASxD,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — Automated Security Vulnerability Detection
|
|
3
|
+
*
|
|
4
|
+
* Finds the vulnerabilities that structural audit can't:
|
|
5
|
+
* - Non-null env assertions without validation (crash risk)
|
|
6
|
+
* - JSON.parse without try/catch (crash risk)
|
|
7
|
+
* - Hardcoded secrets
|
|
8
|
+
* - SQL string concatenation (injection risk)
|
|
9
|
+
* - Weak CSP directives
|
|
10
|
+
* - Missing CORS origin
|
|
11
|
+
* - Exposed stack traces
|
|
12
|
+
* - Cookie/JWT TTL mismatches (cross-file value checker)
|
|
13
|
+
* - Dead imports
|
|
14
|
+
*
|
|
15
|
+
* Tested against real findings from manual Cayman-Hashlock audit.
|
|
16
|
+
*/
|
|
17
|
+
export type SecuritySeverity = 'critical' | 'high' | 'medium' | 'low' | 'info';
|
|
18
|
+
export interface SecurityFinding {
|
|
19
|
+
id: string;
|
|
20
|
+
rule: string;
|
|
21
|
+
severity: SecuritySeverity;
|
|
22
|
+
file: string;
|
|
23
|
+
line: number;
|
|
24
|
+
code: string;
|
|
25
|
+
description: string;
|
|
26
|
+
fix: string;
|
|
27
|
+
}
|
|
28
|
+
export interface SecurityReport {
|
|
29
|
+
findings: SecurityFinding[];
|
|
30
|
+
summary: {
|
|
31
|
+
total: number;
|
|
32
|
+
critical: number;
|
|
33
|
+
high: number;
|
|
34
|
+
medium: number;
|
|
35
|
+
low: number;
|
|
36
|
+
info: number;
|
|
37
|
+
securityScore: number;
|
|
38
|
+
};
|
|
39
|
+
scannedFiles: number;
|
|
40
|
+
duration: number;
|
|
41
|
+
}
|
|
42
|
+
export declare class SecurityScanner {
|
|
43
|
+
private projectRoot;
|
|
44
|
+
constructor(projectRoot: string);
|
|
45
|
+
scan(): Promise<SecurityReport>;
|
|
46
|
+
private detectValueMismatches;
|
|
47
|
+
private computeScore;
|
|
48
|
+
private deduplicate;
|
|
49
|
+
private getLineNumber;
|
|
50
|
+
private isTestFile;
|
|
51
|
+
private collectFiles;
|
|
52
|
+
private walk;
|
|
53
|
+
}
|
|
54
|
+
//# sourceMappingURL=security-scanner.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-scanner.d.ts","sourceRoot":"","sources":["../../../src/agent/tracer/security-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AASH,MAAM,MAAM,gBAAgB,GAAG,UAAU,GAAG,MAAM,GAAG,QAAQ,GAAG,KAAK,GAAG,MAAM,CAAC;AAE/E,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,eAAe,EAAE,CAAC;IAC5B,OAAO,EAAE;QACP,KAAK,EAAE,MAAM,CAAC;QACd,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,MAAM,EAAE,MAAM,CAAC;QACf,GAAG,EAAE,MAAM,CAAC;QACZ,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;KACvB,CAAC;IACF,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;CAClB;AA8ND,qBAAa,eAAe;IAC1B,OAAO,CAAC,WAAW,CAAS;gBAEhB,WAAW,EAAE,MAAM;IAIzB,IAAI,IAAI,OAAO,CAAC,cAAc,CAAC;IA2HrC,OAAO,CAAC,qBAAqB;IAgC7B,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,WAAW;IAUnB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,UAAU;YAKJ,YAAY;YAMZ,IAAI;CAYnB"}
|
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Scanner — Automated Security Vulnerability Detection
|
|
3
|
+
*
|
|
4
|
+
* Finds the vulnerabilities that structural audit can't:
|
|
5
|
+
* - Non-null env assertions without validation (crash risk)
|
|
6
|
+
* - JSON.parse without try/catch (crash risk)
|
|
7
|
+
* - Hardcoded secrets
|
|
8
|
+
* - SQL string concatenation (injection risk)
|
|
9
|
+
* - Weak CSP directives
|
|
10
|
+
* - Missing CORS origin
|
|
11
|
+
* - Exposed stack traces
|
|
12
|
+
* - Cookie/JWT TTL mismatches (cross-file value checker)
|
|
13
|
+
* - Dead imports
|
|
14
|
+
*
|
|
15
|
+
* Tested against real findings from manual Cayman-Hashlock audit.
|
|
16
|
+
*/
|
|
17
|
+
import { readFile, readdir } from 'node:fs/promises';
|
|
18
|
+
import { join, relative, extname } from 'node:path';
|
|
19
|
+
const RULES = [
|
|
20
|
+
// ── Crash Risks ──────────────────────────────────────
|
|
21
|
+
{
|
|
22
|
+
id: 'SEC-01',
|
|
23
|
+
name: 'non-null-env-assertion',
|
|
24
|
+
severity: 'medium',
|
|
25
|
+
pattern: /process\.env\[?['"]?\w+['"]?\]?!/g,
|
|
26
|
+
antiPattern: /if\s*\(!?\s*process\.env|const\s+\w+\s*=\s*process\.env[^!]|process\.env\.\w+\s*\?\?|process\.env\.\w+\s*\|\|/,
|
|
27
|
+
description: 'Non-null assertion on process.env without prior validation — crashes with cryptic error if env var is missing',
|
|
28
|
+
fix: 'Add startup validation: const X = process.env.X; if (!X) throw new Error("X is required");',
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
id: 'SEC-02',
|
|
32
|
+
name: 'json-parse-no-try-catch',
|
|
33
|
+
severity: 'medium',
|
|
34
|
+
pattern: /JSON\.parse\s*\(\s*(?:process\.env|req\.|request\.|body|params|query|headers)/g,
|
|
35
|
+
antiPattern: /try\s*{[^}]*JSON\.parse/,
|
|
36
|
+
description: 'JSON.parse on untrusted input without try/catch — malformed input crashes the process',
|
|
37
|
+
fix: 'Wrap in try/catch or use a safe parse utility (e.g., zod .safeParse())',
|
|
38
|
+
},
|
|
39
|
+
// ── Secrets ──────────────────────────────────────────
|
|
40
|
+
{
|
|
41
|
+
id: 'SEC-03',
|
|
42
|
+
name: 'hardcoded-secret',
|
|
43
|
+
severity: 'critical',
|
|
44
|
+
pattern: /(?:password|secret|apikey|api_key|token|private_key)\s*[:=]\s*['"][^'"]{8,}['"]/gi,
|
|
45
|
+
antiPattern: /process\.env|example|placeholder|TODO|CHANGE|test|mock|fake|dummy|sample/i,
|
|
46
|
+
description: 'Possible hardcoded secret in source code',
|
|
47
|
+
fix: 'Move to environment variable or secrets manager',
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
id: 'SEC-04',
|
|
51
|
+
name: 'hardcoded-jwt-secret',
|
|
52
|
+
severity: 'critical',
|
|
53
|
+
pattern: /jwt\.sign\s*\([^)]*['"][a-zA-Z0-9+/=]{16,}['"]/g,
|
|
54
|
+
description: 'Hardcoded JWT signing secret in jwt.sign() call',
|
|
55
|
+
fix: 'Use process.env.JWT_SECRET with startup validation',
|
|
56
|
+
},
|
|
57
|
+
// ── SQL Injection ────────────────────────────────────
|
|
58
|
+
{
|
|
59
|
+
id: 'SEC-05',
|
|
60
|
+
name: 'sql-string-concat',
|
|
61
|
+
severity: 'critical',
|
|
62
|
+
pattern: /(?:query|execute|raw)\s*\(\s*`[^`]*\$\{(?!.*params|.*where|.*conditions|.*clause)/g,
|
|
63
|
+
antiPattern: /\$\d+|_patches|\.test\.|\.spec\./,
|
|
64
|
+
description: 'SQL query with template literal interpolation — potential SQL injection',
|
|
65
|
+
fix: 'Use parameterized queries: query("SELECT * FROM x WHERE id = $1", [id])',
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
id: 'SEC-06',
|
|
69
|
+
name: 'sql-string-concat-plus',
|
|
70
|
+
severity: 'critical',
|
|
71
|
+
pattern: /(?:query|execute|raw)\s*\(\s*['"][^'"]*['"]\s*\+\s*(?:req\.|args\.|params\.|body\.|input)/g,
|
|
72
|
+
description: 'SQL query built with string concatenation from user input',
|
|
73
|
+
fix: 'Use parameterized queries instead of string concatenation',
|
|
74
|
+
},
|
|
75
|
+
// ── CSP / Headers ────────────────────────────────────
|
|
76
|
+
{
|
|
77
|
+
id: 'SEC-07',
|
|
78
|
+
name: 'csp-unsafe-inline',
|
|
79
|
+
severity: 'low',
|
|
80
|
+
pattern: /(?:script-src|style-src)[^;]*'unsafe-inline'/g,
|
|
81
|
+
description: 'CSP allows unsafe-inline — enables XSS via inline scripts/styles',
|
|
82
|
+
fix: 'Use nonce-based or hash-based CSP instead of unsafe-inline',
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
id: 'SEC-08',
|
|
86
|
+
name: 'csp-unsafe-eval',
|
|
87
|
+
severity: 'medium',
|
|
88
|
+
pattern: /(?:script-src)[^;]*'unsafe-eval'/g,
|
|
89
|
+
description: 'CSP allows unsafe-eval — enables code injection via eval()',
|
|
90
|
+
fix: 'Remove unsafe-eval from CSP; refactor code to avoid eval()',
|
|
91
|
+
},
|
|
92
|
+
// ── CORS ─────────────────────────────────────────────
|
|
93
|
+
{
|
|
94
|
+
id: 'SEC-09',
|
|
95
|
+
name: 'cors-wildcard',
|
|
96
|
+
severity: 'medium',
|
|
97
|
+
pattern: /cors\s*\(\s*\)|origin\s*:\s*['"]?\*['"]?|Access-Control-Allow-Origin.*\*/g,
|
|
98
|
+
antiPattern: /NODE_ENV.*(?:dev|test)|development/,
|
|
99
|
+
description: 'CORS allows all origins — any website can make authenticated requests',
|
|
100
|
+
fix: 'Restrict origin to specific allowed domains',
|
|
101
|
+
},
|
|
102
|
+
// ── Error Exposure ───────────────────────────────────
|
|
103
|
+
{
|
|
104
|
+
id: 'SEC-10',
|
|
105
|
+
name: 'exposed-stack-trace',
|
|
106
|
+
severity: 'low',
|
|
107
|
+
pattern: /res\.(?:send|json)\s*\(\s*(?:err|error)(?:\.stack|\.message|\))/g,
|
|
108
|
+
antiPattern: /NODE_ENV.*prod|production/,
|
|
109
|
+
description: 'Raw error/stack trace sent in HTTP response — leaks internal details',
|
|
110
|
+
fix: 'Return generic error message in production; log full error server-side',
|
|
111
|
+
},
|
|
112
|
+
// ── Auth ─────────────────────────────────────────────
|
|
113
|
+
{
|
|
114
|
+
id: 'SEC-11',
|
|
115
|
+
name: 'jwt-alg-none',
|
|
116
|
+
severity: 'critical',
|
|
117
|
+
pattern: /algorithms?\s*:\s*\[?\s*['"]none['"]/gi,
|
|
118
|
+
description: 'JWT configured to accept "none" algorithm — allows unsigned tokens',
|
|
119
|
+
fix: 'Never allow alg: none; always specify: algorithms: ["HS256"]',
|
|
120
|
+
},
|
|
121
|
+
{
|
|
122
|
+
id: 'SEC-12',
|
|
123
|
+
name: 'eval-usage',
|
|
124
|
+
severity: 'high',
|
|
125
|
+
pattern: /\beval\s*\(/g,
|
|
126
|
+
antiPattern: /\/\/.*eval|eslint-disable|\.test\.|\.spec\./,
|
|
127
|
+
description: 'eval() usage — allows arbitrary code execution',
|
|
128
|
+
fix: 'Replace eval with JSON.parse, Function constructor, or domain-specific parser',
|
|
129
|
+
},
|
|
130
|
+
{
|
|
131
|
+
id: 'SEC-13',
|
|
132
|
+
name: 'innerhtml-usage',
|
|
133
|
+
severity: 'high',
|
|
134
|
+
pattern: /\.innerHTML\s*=|dangerouslySetInnerHTML/g,
|
|
135
|
+
antiPattern: /sanitize|DOMPurify|xss/i,
|
|
136
|
+
description: 'innerHTML/dangerouslySetInnerHTML usage — XSS risk if input not sanitized',
|
|
137
|
+
fix: 'Use textContent, or sanitize with DOMPurify before setting innerHTML',
|
|
138
|
+
},
|
|
139
|
+
// ── Dead Import ──────────────────────────────────────
|
|
140
|
+
{
|
|
141
|
+
id: 'SEC-14',
|
|
142
|
+
name: 'unused-import-hint',
|
|
143
|
+
severity: 'info',
|
|
144
|
+
pattern: /import\s+{?\s*(\w+).*}\s+from/g,
|
|
145
|
+
// This is a hint — real detection needs usage check (below)
|
|
146
|
+
description: 'Potential unused import',
|
|
147
|
+
fix: 'Remove if unused — reduces attack surface and bundle size',
|
|
148
|
+
},
|
|
149
|
+
];
|
|
150
|
+
/** Patterns to extract related values across files */
|
|
151
|
+
const VALUE_PATTERNS = [
|
|
152
|
+
{
|
|
153
|
+
key: 'access-token-ttl',
|
|
154
|
+
pattern: /(?:ACCESS_TOKEN_TTL|access.*ttl|token.*expir(?:es|y|ation)|maxAge|max_age)\s*[:=]\s*['"]?(\d+[smhd]?|\d+\s*\*\s*\d+)['"]?/gi,
|
|
155
|
+
extractValue: (m) => {
|
|
156
|
+
const raw = m[1];
|
|
157
|
+
if (!raw)
|
|
158
|
+
return null;
|
|
159
|
+
return { value: raw, unit: 'time' };
|
|
160
|
+
},
|
|
161
|
+
},
|
|
162
|
+
{
|
|
163
|
+
key: 'refresh-token-ttl',
|
|
164
|
+
pattern: /(?:REFRESH_TOKEN_TTL|refresh.*ttl|refresh.*expir)\s*[:=]\s*['"]?(\d+[smhd]?|\d+\s*\*\s*\d+)['"]?/gi,
|
|
165
|
+
extractValue: (m) => {
|
|
166
|
+
const raw = m[1];
|
|
167
|
+
if (!raw)
|
|
168
|
+
return null;
|
|
169
|
+
return { value: raw, unit: 'time' };
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
key: 'cookie-max-age',
|
|
174
|
+
pattern: /maxAge\s*:\s*(\d+(?:\s*\*\s*\d+)*)/g,
|
|
175
|
+
extractValue: (m) => {
|
|
176
|
+
const raw = m[1];
|
|
177
|
+
if (!raw)
|
|
178
|
+
return null;
|
|
179
|
+
try {
|
|
180
|
+
// eslint-disable-next-line no-eval
|
|
181
|
+
const val = Function(`"use strict"; return (${raw})`)();
|
|
182
|
+
return { value: val, unit: 'seconds' };
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return { value: raw, unit: 'expression' };
|
|
186
|
+
}
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
// ═══════════════════════════════════════════════════════════
|
|
191
|
+
// Scanner
|
|
192
|
+
// ═══════════════════════════════════════════════════════════
|
|
193
|
+
const SOURCE_EXTS = new Set(['.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs']);
|
|
194
|
+
const SKIP_DIRS = new Set(['node_modules', '.git', 'dist', 'build', 'coverage', '.next', '.turbo']);
|
|
195
|
+
export class SecurityScanner {
|
|
196
|
+
projectRoot;
|
|
197
|
+
constructor(projectRoot) {
|
|
198
|
+
this.projectRoot = projectRoot;
|
|
199
|
+
}
|
|
200
|
+
async scan() {
|
|
201
|
+
const start = Date.now();
|
|
202
|
+
const files = await this.collectFiles();
|
|
203
|
+
const findings = [];
|
|
204
|
+
const valueExtractions = [];
|
|
205
|
+
let findingCounter = 0;
|
|
206
|
+
for (const file of files) {
|
|
207
|
+
let content;
|
|
208
|
+
try {
|
|
209
|
+
content = await readFile(join(this.projectRoot, file), 'utf-8');
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
continue;
|
|
213
|
+
}
|
|
214
|
+
const lines = content.split('\n');
|
|
215
|
+
// Run security rules
|
|
216
|
+
for (const rule of RULES) {
|
|
217
|
+
// Skip unused-import-hint (handled separately below)
|
|
218
|
+
if (rule.id === 'SEC-14')
|
|
219
|
+
continue;
|
|
220
|
+
// Check anti-pattern on whole file first
|
|
221
|
+
if (rule.antiPattern) {
|
|
222
|
+
// For some rules, anti-pattern means "this file has proper handling"
|
|
223
|
+
// We check per-match below for more precision
|
|
224
|
+
}
|
|
225
|
+
const regex = new RegExp(rule.pattern.source, rule.pattern.flags);
|
|
226
|
+
let match;
|
|
227
|
+
while ((match = regex.exec(content)) !== null) {
|
|
228
|
+
const lineNum = this.getLineNumber(content, match.index);
|
|
229
|
+
const codeLine = lines[lineNum - 1]?.trim() ?? '';
|
|
230
|
+
// Per-line anti-pattern check
|
|
231
|
+
if (rule.antiPattern) {
|
|
232
|
+
// Check surrounding context (5 lines before)
|
|
233
|
+
const contextStart = Math.max(0, lineNum - 6);
|
|
234
|
+
const context = lines.slice(contextStart, lineNum).join('\n');
|
|
235
|
+
if (rule.antiPattern.test(context) || rule.antiPattern.test(codeLine)) {
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// Skip test files for most rules
|
|
240
|
+
if (this.isTestFile(file) && rule.severity !== 'critical')
|
|
241
|
+
continue;
|
|
242
|
+
// Skip test files for hardcoded secret rules — test fixtures are expected
|
|
243
|
+
if (this.isTestFile(file) && (rule.id === 'SEC-03' || rule.id === 'SEC-04'))
|
|
244
|
+
continue;
|
|
245
|
+
findings.push({
|
|
246
|
+
id: `F${++findingCounter}`,
|
|
247
|
+
rule: rule.id,
|
|
248
|
+
severity: rule.severity,
|
|
249
|
+
file,
|
|
250
|
+
line: lineNum,
|
|
251
|
+
code: codeLine.slice(0, 150),
|
|
252
|
+
description: rule.description,
|
|
253
|
+
fix: rule.fix,
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
// Extract values for cross-file mismatch detection
|
|
258
|
+
for (const vp of VALUE_PATTERNS) {
|
|
259
|
+
const regex = new RegExp(vp.pattern.source, vp.pattern.flags);
|
|
260
|
+
let match;
|
|
261
|
+
while ((match = regex.exec(content)) !== null) {
|
|
262
|
+
const extracted = vp.extractValue(match);
|
|
263
|
+
if (extracted) {
|
|
264
|
+
valueExtractions.push({
|
|
265
|
+
file,
|
|
266
|
+
line: this.getLineNumber(content, match.index),
|
|
267
|
+
key: vp.key,
|
|
268
|
+
...extracted,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
// Cross-file value mismatch detection
|
|
275
|
+
const mismatches = this.detectValueMismatches(valueExtractions);
|
|
276
|
+
for (const mismatch of mismatches) {
|
|
277
|
+
findings.push({
|
|
278
|
+
id: `F${++findingCounter}`,
|
|
279
|
+
rule: 'SEC-XVAL',
|
|
280
|
+
severity: 'low',
|
|
281
|
+
file: mismatch.values.map(v => v.file).join(' ↔ '),
|
|
282
|
+
line: mismatch.values[0]?.line ?? 0,
|
|
283
|
+
code: mismatch.values.map(v => `${v.file}:${v.line} → ${v.value}`).join(' | '),
|
|
284
|
+
description: mismatch.description,
|
|
285
|
+
fix: 'Ensure related values are consistent; extract to shared constant',
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
// Deduplicate (same rule + same file + same line)
|
|
289
|
+
const deduped = this.deduplicate(findings);
|
|
290
|
+
// Sort by severity
|
|
291
|
+
const severityOrder = { critical: 0, high: 1, medium: 2, low: 3, info: 4 };
|
|
292
|
+
deduped.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity]);
|
|
293
|
+
const summary = {
|
|
294
|
+
total: deduped.length,
|
|
295
|
+
critical: deduped.filter(f => f.severity === 'critical').length,
|
|
296
|
+
high: deduped.filter(f => f.severity === 'high').length,
|
|
297
|
+
medium: deduped.filter(f => f.severity === 'medium').length,
|
|
298
|
+
low: deduped.filter(f => f.severity === 'low').length,
|
|
299
|
+
info: deduped.filter(f => f.severity === 'info').length,
|
|
300
|
+
securityScore: this.computeScore(deduped),
|
|
301
|
+
};
|
|
302
|
+
return {
|
|
303
|
+
findings: deduped,
|
|
304
|
+
summary,
|
|
305
|
+
scannedFiles: files.length,
|
|
306
|
+
duration: Date.now() - start,
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
// ═══════════════════════════════════════════════════════════
|
|
310
|
+
// Helpers
|
|
311
|
+
// ═══════════════════════════════════════════════════════════
|
|
312
|
+
detectValueMismatches(extractions) {
|
|
313
|
+
const mismatches = [];
|
|
314
|
+
// Group by key
|
|
315
|
+
const byKey = new Map();
|
|
316
|
+
for (const e of extractions) {
|
|
317
|
+
if (!byKey.has(e.key))
|
|
318
|
+
byKey.set(e.key, []);
|
|
319
|
+
byKey.get(e.key).push(e);
|
|
320
|
+
}
|
|
321
|
+
// Check access-token-ttl vs cookie-max-age
|
|
322
|
+
const tokenTTLs = byKey.get('access-token-ttl') ?? [];
|
|
323
|
+
const cookieMaxAges = byKey.get('cookie-max-age') ?? [];
|
|
324
|
+
if (tokenTTLs.length > 0 && cookieMaxAges.length > 0) {
|
|
325
|
+
// Simple heuristic: if values look different and are from different files
|
|
326
|
+
const tokenFiles = new Set(tokenTTLs.map(t => t.file));
|
|
327
|
+
const cookieFiles = new Set(cookieMaxAges.map(c => c.file));
|
|
328
|
+
const crossFile = [...cookieFiles].some(f => !tokenFiles.has(f));
|
|
329
|
+
if (crossFile) {
|
|
330
|
+
mismatches.push({
|
|
331
|
+
key: 'token-ttl-vs-cookie-maxage',
|
|
332
|
+
values: [...tokenTTLs, ...cookieMaxAges],
|
|
333
|
+
description: `Token TTL and cookie maxAge defined in different files — values may be mismatched. Token: ${tokenTTLs.map(t => `${t.value} (${t.file}:${t.line})`).join(', ')}. Cookie: ${cookieMaxAges.map(c => `${c.value} (${c.file}:${c.line})`).join(', ')}`,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
return mismatches;
|
|
338
|
+
}
|
|
339
|
+
computeScore(findings) {
|
|
340
|
+
let score = 100;
|
|
341
|
+
score -= findings.filter(f => f.severity === 'critical').length * 20;
|
|
342
|
+
score -= findings.filter(f => f.severity === 'high').length * 10;
|
|
343
|
+
score -= findings.filter(f => f.severity === 'medium').length * 5;
|
|
344
|
+
score -= findings.filter(f => f.severity === 'low').length * 2;
|
|
345
|
+
score -= findings.filter(f => f.severity === 'info').length * 0.5;
|
|
346
|
+
return Math.max(0, Math.min(100, score));
|
|
347
|
+
}
|
|
348
|
+
deduplicate(findings) {
|
|
349
|
+
const seen = new Set();
|
|
350
|
+
return findings.filter(f => {
|
|
351
|
+
const key = `${f.rule}:${f.file}:${f.line}`;
|
|
352
|
+
if (seen.has(key))
|
|
353
|
+
return false;
|
|
354
|
+
seen.add(key);
|
|
355
|
+
return true;
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
getLineNumber(content, charIndex) {
|
|
359
|
+
let count = 0;
|
|
360
|
+
const lines = content.split('\n');
|
|
361
|
+
for (let i = 0; i < lines.length; i++) {
|
|
362
|
+
count += lines[i].length + 1;
|
|
363
|
+
if (count > charIndex)
|
|
364
|
+
return i + 1;
|
|
365
|
+
}
|
|
366
|
+
return lines.length;
|
|
367
|
+
}
|
|
368
|
+
isTestFile(file) {
|
|
369
|
+
return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(file) ||
|
|
370
|
+
/__tests__\//.test(file) || /\btests?\//.test(file);
|
|
371
|
+
}
|
|
372
|
+
async collectFiles() {
|
|
373
|
+
const files = [];
|
|
374
|
+
await this.walk(this.projectRoot, files);
|
|
375
|
+
return files;
|
|
376
|
+
}
|
|
377
|
+
async walk(dir, files) {
|
|
378
|
+
let entries;
|
|
379
|
+
try {
|
|
380
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
381
|
+
}
|
|
382
|
+
catch {
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
for (const entry of entries) {
|
|
386
|
+
if (entry.isDirectory()) {
|
|
387
|
+
if (SKIP_DIRS.has(entry.name) || entry.name.startsWith('.'))
|
|
388
|
+
continue;
|
|
389
|
+
await this.walk(join(dir, entry.name), files);
|
|
390
|
+
}
|
|
391
|
+
else if (entry.isFile() && SOURCE_EXTS.has(extname(entry.name).toLowerCase())) {
|
|
392
|
+
files.push(relative(this.projectRoot, join(dir, entry.name)).replace(/\\/g, '/'));
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
//# sourceMappingURL=security-scanner.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"security-scanner.js","sourceRoot":"","sources":["../../../src/agent/tracer/security-scanner.ts"],"names":[],"mappings":"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"}
|