project-shield 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +440 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +151 -0
- package/dist/index.js.map +1 -0
- package/dist/integrity/failsafe.d.ts +17 -0
- package/dist/integrity/failsafe.d.ts.map +1 -0
- package/dist/integrity/failsafe.js +45 -0
- package/dist/integrity/failsafe.js.map +1 -0
- package/dist/integrity/ruleset.d.ts +12 -0
- package/dist/integrity/ruleset.d.ts.map +1 -0
- package/dist/integrity/ruleset.js +77 -0
- package/dist/integrity/ruleset.js.map +1 -0
- package/dist/integrity/seal.d.ts +12 -0
- package/dist/integrity/seal.d.ts.map +1 -0
- package/dist/integrity/seal.js +77 -0
- package/dist/integrity/seal.js.map +1 -0
- package/dist/output/badge.d.ts +16 -0
- package/dist/output/badge.d.ts.map +1 -0
- package/dist/output/badge.js +112 -0
- package/dist/output/badge.js.map +1 -0
- package/dist/output/evidence.d.ts +18 -0
- package/dist/output/evidence.d.ts.map +1 -0
- package/dist/output/evidence.js +205 -0
- package/dist/output/evidence.js.map +1 -0
- package/dist/output/fixit.d.ts +32 -0
- package/dist/output/fixit.d.ts.map +1 -0
- package/dist/output/fixit.js +387 -0
- package/dist/output/fixit.js.map +1 -0
- package/dist/output/terminal.d.ts +10 -0
- package/dist/output/terminal.d.ts.map +1 -0
- package/dist/output/terminal.js +190 -0
- package/dist/output/terminal.js.map +1 -0
- package/dist/scanner/engine.d.ts +6 -0
- package/dist/scanner/engine.d.ts.map +1 -0
- package/dist/scanner/engine.js +155 -0
- package/dist/scanner/engine.js.map +1 -0
- package/dist/scanner/ignore.d.ts +20 -0
- package/dist/scanner/ignore.d.ts.map +1 -0
- package/dist/scanner/ignore.js +125 -0
- package/dist/scanner/ignore.js.map +1 -0
- package/dist/scanner/injection.d.ts +15 -0
- package/dist/scanner/injection.d.ts.map +1 -0
- package/dist/scanner/injection.js +234 -0
- package/dist/scanner/injection.js.map +1 -0
- package/dist/scanner/mcp.d.ts +6 -0
- package/dist/scanner/mcp.d.ts.map +1 -0
- package/dist/scanner/mcp.js +322 -0
- package/dist/scanner/mcp.js.map +1 -0
- package/dist/scanner/pii.d.ts +21 -0
- package/dist/scanner/pii.d.ts.map +1 -0
- package/dist/scanner/pii.js +161 -0
- package/dist/scanner/pii.js.map +1 -0
- package/dist/scanner/secrets.d.ts +10 -0
- package/dist/scanner/secrets.d.ts.map +1 -0
- package/dist/scanner/secrets.js +224 -0
- package/dist/scanner/secrets.js.map +1 -0
- package/dist/scoring/lock.d.ts +12 -0
- package/dist/scoring/lock.d.ts.map +1 -0
- package/dist/scoring/lock.js +58 -0
- package/dist/scoring/lock.js.map +1 -0
- package/dist/scoring/score.d.ts +14 -0
- package/dist/scoring/score.d.ts.map +1 -0
- package/dist/scoring/score.js +74 -0
- package/dist/scoring/score.js.map +1 -0
- package/dist/types/index.d.ts +205 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +3 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +52 -0
- package/rules/v1.0.0.json +248 -0
package/README.md
ADDED
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
# Project Shield
|
|
2
|
+
|
|
3
|
+
**Security scanner for AI coders and MCP users.**
|
|
4
|
+
|
|
5
|
+
Detects leaked API keys, PII, insecure MCP configs, and prompt injection — in one command.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npx project-shield scan ./my-project
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
Project Shield v1.0.0
|
|
13
|
+
Ruleset: v1.0.0 (SHA-256: f408a4fd...)
|
|
14
|
+
Scanning: 47 files (3 excluded)
|
|
15
|
+
|
|
16
|
+
━━━ Secrets ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
17
|
+
CRITICAL src/config.ts:12
|
|
18
|
+
AWS Access Key detected (AKIA****)
|
|
19
|
+
Layers: regex ✓ entropy ✓ context ✓
|
|
20
|
+
|
|
21
|
+
━━━ PII ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
22
|
+
CONFIRMED data/users.csv:5
|
|
23
|
+
Korean Resident Registration Number detected (900*****)
|
|
24
|
+
Layers: regex ✓ checksum ✓
|
|
25
|
+
|
|
26
|
+
━━━ MCP Config ━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
27
|
+
CRITICAL mcp.json (3/5 failed)
|
|
28
|
+
✗ Auth: No authentication configured
|
|
29
|
+
✗ Secrets: Hardcoded API key found
|
|
30
|
+
✓ Tool Meta: OK
|
|
31
|
+
✗ Permissions: Root filesystem access
|
|
32
|
+
✓ Logging: OK
|
|
33
|
+
|
|
34
|
+
━━━ Injection ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
35
|
+
CRITICAL prompts/system.txt:3
|
|
36
|
+
Hidden injection in comment (Direct instruction override attempt) [structural]
|
|
37
|
+
Pattern: inj_ignore_prev | Layers: keyword ✓ structure ✓
|
|
38
|
+
|
|
39
|
+
━━━ Score ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
40
|
+
Grade: F (0/100) — Locked
|
|
41
|
+
Badge generation LOCKED — Fix all critical findings first.
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Why Project Shield?
|
|
47
|
+
|
|
48
|
+
| Problem | Project Shield |
|
|
49
|
+
|---------|---------------|
|
|
50
|
+
| API key leaked in commit | 3-layer detection (regex + entropy + context) |
|
|
51
|
+
| PII in test data | Korean RRN checksum validation, credit card Luhn check |
|
|
52
|
+
| MCP config wide open | 5-point check (auth, secrets, tools, permissions, logging) |
|
|
53
|
+
| Prompt injection in tool descriptions | 2-layer detection (keyword + structural analysis) |
|
|
54
|
+
| "What do I fix first?" | Fix-it guides with code examples |
|
|
55
|
+
| Compliance evidence needed | PDF + JSON evidence pack |
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Install
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
# Global
|
|
63
|
+
npm install -g project-shield
|
|
64
|
+
|
|
65
|
+
# npx (no install)
|
|
66
|
+
npx project-shield scan ./my-project
|
|
67
|
+
|
|
68
|
+
# Dev dependency
|
|
69
|
+
npm install -D project-shield
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
---
|
|
73
|
+
|
|
74
|
+
## Usage
|
|
75
|
+
|
|
76
|
+
### Basic scan
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
project-shield scan ./my-project
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### JSON output
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
project-shield scan ./my-project --format json
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Fix-it guides
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
# Free: top 3 guides (summary only)
|
|
92
|
+
project-shield scan ./my-project --fix
|
|
93
|
+
|
|
94
|
+
# Pro: all guides with code examples + references
|
|
95
|
+
project-shield scan ./my-project --fix --pro
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Evidence pack (JSON + PDF)
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
project-shield scan ./my-project --evidence ./report
|
|
102
|
+
# Creates: report.json + report.pdf
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Badge generation
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
# Free badge (with watermark)
|
|
109
|
+
project-shield scan ./my-project --badge shield-badge.svg
|
|
110
|
+
|
|
111
|
+
# Pro badge (no watermark)
|
|
112
|
+
project-shield scan ./my-project --badge shield-badge.svg --pro
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
### Custom ignore
|
|
116
|
+
|
|
117
|
+
```bash
|
|
118
|
+
project-shield scan ./my-project --ignore .shieldignore
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### All options
|
|
122
|
+
|
|
123
|
+
```
|
|
124
|
+
project-shield scan <path>
|
|
125
|
+
-f, --format <format> Output format: terminal | json (default: terminal)
|
|
126
|
+
-i, --ignore <path> Path to .shieldignore file
|
|
127
|
+
-r, --ruleset <path> Path to custom ruleset JSON
|
|
128
|
+
-b, --badge <path> Output path for SVG badge
|
|
129
|
+
--pro Pro mode (no watermark, full fix-it guides)
|
|
130
|
+
--fix Show fix-it remediation guides
|
|
131
|
+
--evidence <path> Output path for evidence pack (JSON + PDF)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## What It Detects
|
|
137
|
+
|
|
138
|
+
### F001: Secrets (3-layer detection)
|
|
139
|
+
|
|
140
|
+
| Layer | Method | Example |
|
|
141
|
+
|-------|--------|---------|
|
|
142
|
+
| Regex | Pattern matching | `AKIA...`, `sk_live_...`, `ghp_...` |
|
|
143
|
+
| Entropy | Shannon entropy > 4.5 | High-randomness strings in secret context |
|
|
144
|
+
| Context | Variable name analysis | `API_KEY=`, `secret:`, `.env` files |
|
|
145
|
+
|
|
146
|
+
**10 built-in patterns**: AWS, Stripe, OpenAI, GitHub PAT/OAuth, Slack, Google, Private Keys + high-entropy catch-all.
|
|
147
|
+
|
|
148
|
+
**Severity rules**:
|
|
149
|
+
- 2+ layers hit = **critical**
|
|
150
|
+
- 1 layer hit = **warning**
|
|
151
|
+
- Pattern override (e.g., `sk_live_` always critical)
|
|
152
|
+
|
|
153
|
+
### F002: MCP Config (5-point check)
|
|
154
|
+
|
|
155
|
+
| Check | What | Critical When |
|
|
156
|
+
|-------|------|---------------|
|
|
157
|
+
| Auth | Authentication configured? | No auth field found |
|
|
158
|
+
| Secrets | Hardcoded keys in config? | Literal API key (not `${ENV_VAR}`) |
|
|
159
|
+
| Tool Meta | Dangerous tool keywords? | `exec`, `eval`, `shell`, `rm` in tools |
|
|
160
|
+
| Permissions | Overly broad access? | Root `/`, wildcard `*`, `--privileged` |
|
|
161
|
+
| Logging | Audit logging enabled? | No logging/audit field |
|
|
162
|
+
|
|
163
|
+
### F003: Prompt Injection (2-layer detection)
|
|
164
|
+
|
|
165
|
+
| Layer | Method | Example |
|
|
166
|
+
|-------|--------|---------|
|
|
167
|
+
| Keyword | 11 patterns (6 direct + 5 indirect) | "ignore previous instructions", "secretly" |
|
|
168
|
+
| Structure | Comment hiding, zero-width chars, tool length | `<!-- inject -->`, `\u200B` |
|
|
169
|
+
|
|
170
|
+
**Cross-judgment**:
|
|
171
|
+
- Keyword + Structure = **critical**
|
|
172
|
+
- Keyword only = **warning**
|
|
173
|
+
- Structure only (with hidden keyword) = **critical**
|
|
174
|
+
- Encoded bypass (Base64/URL) = **automatic critical**
|
|
175
|
+
|
|
176
|
+
### F004: PII (2-layer detection)
|
|
177
|
+
|
|
178
|
+
| Pattern | Locale | Checksum |
|
|
179
|
+
|---------|--------|----------|
|
|
180
|
+
| Korean RRN (주민등록번호) | KR | Yes |
|
|
181
|
+
| Korean Phone | KR | No |
|
|
182
|
+
| Korean Business Number | KR | Yes |
|
|
183
|
+
| Korean Passport | KR | No |
|
|
184
|
+
| Email | Global | No |
|
|
185
|
+
| US SSN | Global | No |
|
|
186
|
+
| Credit Card | Global | Luhn |
|
|
187
|
+
|
|
188
|
+
### F005: Scoring (0-100)
|
|
189
|
+
|
|
190
|
+
| Grade | Score | Status |
|
|
191
|
+
|-------|-------|--------|
|
|
192
|
+
| A | 90-100 | Excellent |
|
|
193
|
+
| B | 75-89 | Good |
|
|
194
|
+
| C | 60-74 | Pass |
|
|
195
|
+
| D | 40-59 | Warning |
|
|
196
|
+
| E | 20-39 | Warning |
|
|
197
|
+
| F | 0-19 | Locked |
|
|
198
|
+
|
|
199
|
+
**Any critical finding = automatic F + badge locked.**
|
|
200
|
+
|
|
201
|
+
Deductions: critical -25, warning -10, possible -5, info -2.
|
|
202
|
+
|
|
203
|
+
---
|
|
204
|
+
|
|
205
|
+
## Inline Suppression
|
|
206
|
+
|
|
207
|
+
Add `shield-ignore` to suppress a specific line:
|
|
208
|
+
|
|
209
|
+
```javascript
|
|
210
|
+
const API_KEY = "sk_test_abc123"; // shield-ignore
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
```python
|
|
214
|
+
API_KEY = "sk_test_abc123" # shield-ignore
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Or use `.shieldignore` (gitignore syntax):
|
|
218
|
+
|
|
219
|
+
```
|
|
220
|
+
# .shieldignore
|
|
221
|
+
tests/fixtures/
|
|
222
|
+
*.test.ts
|
|
223
|
+
docs/examples/
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Evidence Pack
|
|
229
|
+
|
|
230
|
+
The `--evidence` flag generates a compliance-ready report:
|
|
231
|
+
|
|
232
|
+
**JSON** (`report.json`):
|
|
233
|
+
- Full findings (secrets, PII, MCP, injection)
|
|
234
|
+
- Score + lock status
|
|
235
|
+
- Fix-it summary
|
|
236
|
+
- Integrity hashes (result SHA-256 + ruleset SHA-256)
|
|
237
|
+
- Disclaimer
|
|
238
|
+
|
|
239
|
+
**PDF** (`report.pdf`):
|
|
240
|
+
- Cover page with grade
|
|
241
|
+
- Executive summary
|
|
242
|
+
- Findings detail by category
|
|
243
|
+
- Integrity verification
|
|
244
|
+
- Disclaimer
|
|
245
|
+
|
|
246
|
+
---
|
|
247
|
+
|
|
248
|
+
## CI Integration
|
|
249
|
+
|
|
250
|
+
```yaml
|
|
251
|
+
# GitHub Actions
|
|
252
|
+
- name: Security Scan
|
|
253
|
+
run: npx project-shield scan . --format json
|
|
254
|
+
# Exit code 1 if any critical finding
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
```yaml
|
|
258
|
+
# With badge
|
|
259
|
+
- name: Security Scan + Badge
|
|
260
|
+
run: |
|
|
261
|
+
npx project-shield scan . --badge shield-badge.svg
|
|
262
|
+
# Commit badge to repo
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
---
|
|
266
|
+
|
|
267
|
+
## Architecture
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
src/
|
|
271
|
+
index.ts CLI entry (commander)
|
|
272
|
+
types/index.ts All TypeScript interfaces
|
|
273
|
+
scanner/
|
|
274
|
+
engine.ts Scan orchestrator (glob, binary skip, ignore)
|
|
275
|
+
secrets.ts 3-layer secret detection
|
|
276
|
+
pii.ts 2-layer PII detection
|
|
277
|
+
mcp.ts 5-point MCP config check
|
|
278
|
+
injection.ts 2-layer injection detection
|
|
279
|
+
ignore.ts shield-ignore + .shieldignore
|
|
280
|
+
scoring/
|
|
281
|
+
score.ts 0-100 scoring + A-F grading
|
|
282
|
+
lock.ts Badge lock logic
|
|
283
|
+
integrity/
|
|
284
|
+
ruleset.ts Ruleset loader + SHA-256 verification
|
|
285
|
+
failsafe.ts Crash = fail, no ruleset = reject
|
|
286
|
+
seal.ts Result hash sealing (SHA-256 + UUID)
|
|
287
|
+
output/
|
|
288
|
+
terminal.ts Terminal + JSON formatter
|
|
289
|
+
badge.ts SVG badge generator (shields.io style)
|
|
290
|
+
fixit.ts 10-type fix-it guide system
|
|
291
|
+
evidence.ts Evidence pack (JSON + PDF)
|
|
292
|
+
rules/
|
|
293
|
+
v1.0.0.json Ruleset (secrets + PII + MCP + injection patterns)
|
|
294
|
+
```
|
|
295
|
+
|
|
296
|
+
---
|
|
297
|
+
|
|
298
|
+
## Integrity
|
|
299
|
+
|
|
300
|
+
- Ruleset is SHA-256 verified on every load. Tampered ruleset = scan rejected.
|
|
301
|
+
- Scan results are hash-sealed. Same input = same hash.
|
|
302
|
+
- Fail-safe: crash during scan = `SCAN_FAILED` (never false PASS).
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## License
|
|
307
|
+
|
|
308
|
+
MIT
|
|
309
|
+
|
|
310
|
+
---
|
|
311
|
+
|
|
312
|
+
<p align="center">
|
|
313
|
+
<sub>Built for developers who ship AI-powered apps and want to sleep at night.</sub>
|
|
314
|
+
</p>
|
|
315
|
+
|
|
316
|
+
---
|
|
317
|
+
|
|
318
|
+
# Project Shield (한국어)
|
|
319
|
+
|
|
320
|
+
**AI 코더/MCP 사용자를 위한 보안 스캐너 CLI.**
|
|
321
|
+
|
|
322
|
+
API 키 유출, 개인정보, MCP 설정 보안 취약점, 프롬프트 인젝션을 한 번에 탐지합니다.
|
|
323
|
+
|
|
324
|
+
```bash
|
|
325
|
+
npx project-shield scan ./my-project
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
---
|
|
329
|
+
|
|
330
|
+
## 왜 Project Shield?
|
|
331
|
+
|
|
332
|
+
| 문제 | Project Shield |
|
|
333
|
+
|------|---------------|
|
|
334
|
+
| 커밋에서 API 키 유출 | 3중 탐지 (정규식 + 엔트로피 + 컨텍스트) |
|
|
335
|
+
| 테스트 데이터에서 개인정보 노출 | 주민등록번호 체크섬 검증, 신용카드 Luhn 검증 |
|
|
336
|
+
| MCP 설정 보안 허점 | 5항목 점검 (인증, 시크릿, 툴, 권한, 로깅) |
|
|
337
|
+
| 툴 설명에 숨겨진 프롬프트 인젝션 | 2중 탐지 (키워드 + 구조 분석) |
|
|
338
|
+
| "뭐부터 고쳐야 하지?" | Fix-it 가이드 (코드 예제 포함) |
|
|
339
|
+
| 컴플라이언스 증빙 필요 | PDF + JSON Evidence Pack |
|
|
340
|
+
|
|
341
|
+
---
|
|
342
|
+
|
|
343
|
+
## 설치
|
|
344
|
+
|
|
345
|
+
```bash
|
|
346
|
+
# 글로벌
|
|
347
|
+
npm install -g project-shield
|
|
348
|
+
|
|
349
|
+
# npx (설치 없이)
|
|
350
|
+
npx project-shield scan ./my-project
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
## 사용법
|
|
356
|
+
|
|
357
|
+
### 기본 스캔
|
|
358
|
+
|
|
359
|
+
```bash
|
|
360
|
+
project-shield scan ./my-project
|
|
361
|
+
```
|
|
362
|
+
|
|
363
|
+
### Fix-it 가이드 포함
|
|
364
|
+
|
|
365
|
+
```bash
|
|
366
|
+
# Free: 상위 3개 (요약만)
|
|
367
|
+
project-shield scan ./my-project --fix
|
|
368
|
+
|
|
369
|
+
# Pro: 전체 가이드 + 코드 예제 + 참조
|
|
370
|
+
project-shield scan ./my-project --fix --pro
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
### Evidence Pack (JSON + PDF)
|
|
374
|
+
|
|
375
|
+
```bash
|
|
376
|
+
project-shield scan ./my-project --evidence ./report
|
|
377
|
+
# 생성: report.json + report.pdf
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
### 뱃지 생성
|
|
381
|
+
|
|
382
|
+
```bash
|
|
383
|
+
project-shield scan ./my-project --badge shield-badge.svg
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
---
|
|
387
|
+
|
|
388
|
+
## 탐지 항목
|
|
389
|
+
|
|
390
|
+
### F001: 시크릿(Secret) 탐지
|
|
391
|
+
|
|
392
|
+
3중 탐지: 정규식 매칭 + 섀넌 엔트로피 + 컨텍스트 분석 (변수명, `.env` 파일 등)
|
|
393
|
+
|
|
394
|
+
10종 패턴: AWS, Stripe, OpenAI, GitHub PAT/OAuth, Slack, Google, Private Key
|
|
395
|
+
|
|
396
|
+
### F002: MCP 설정 점검
|
|
397
|
+
|
|
398
|
+
5항목 점검: 인증(auth), 시크릿(secrets), 툴(tools), 권한(permissions), 로깅(logging)
|
|
399
|
+
|
|
400
|
+
### F003: 프롬프트 인젝션 탐지
|
|
401
|
+
|
|
402
|
+
2중 탐지: 키워드 매칭 (11종 패턴) + 구조 분석 (HTML 주석, zero-width 유니코드, 툴 설명 길이)
|
|
403
|
+
|
|
404
|
+
Base64/URL 인코딩된 우회 시도 감지 시 **자동 critical**
|
|
405
|
+
|
|
406
|
+
### F004: PII 탐지
|
|
407
|
+
|
|
408
|
+
주민등록번호 (체크섬 검증), 전화번호, 사업자등록번호, 여권번호, 이메일, US SSN, 신용카드 (Luhn 검증)
|
|
409
|
+
|
|
410
|
+
### F005: 스코어
|
|
411
|
+
|
|
412
|
+
0-100점 방식, A-F 등급. **Critical 발견이 1개라도 있으면 자동 F + 뱃지 잠금.**
|
|
413
|
+
|
|
414
|
+
### F006: Fix-it 가이드
|
|
415
|
+
|
|
416
|
+
10종 맞춤형 가이드: AWS 키 로테이션, Stripe 키 로테이션, Private Key 제거, RRN 마스킹, 신용카드 제거, MCP 설정 수정 3종, 프롬프트 인젝션 제거
|
|
417
|
+
|
|
418
|
+
### F007: Evidence Pack
|
|
419
|
+
|
|
420
|
+
JSON + PDF. 점수, 등급, 발견, 수정 가이드, 무결성 해시, 면책조항 포함.
|
|
421
|
+
|
|
422
|
+
---
|
|
423
|
+
|
|
424
|
+
## 라인 무시 (shield-ignore)
|
|
425
|
+
|
|
426
|
+
```javascript
|
|
427
|
+
const key = "sk_test_abc"; // shield-ignore
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
`.shieldignore` 파일:
|
|
431
|
+
```
|
|
432
|
+
tests/fixtures/
|
|
433
|
+
*.test.ts
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
---
|
|
437
|
+
|
|
438
|
+
## 라이선스
|
|
439
|
+
|
|
440
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
+
if (k2 === undefined) k2 = k;
|
|
5
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
+
}
|
|
9
|
+
Object.defineProperty(o, k2, desc);
|
|
10
|
+
}) : (function(o, m, k, k2) {
|
|
11
|
+
if (k2 === undefined) k2 = k;
|
|
12
|
+
o[k2] = m[k];
|
|
13
|
+
}));
|
|
14
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
+
}) : function(o, v) {
|
|
17
|
+
o["default"] = v;
|
|
18
|
+
});
|
|
19
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
+
var ownKeys = function(o) {
|
|
21
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
+
var ar = [];
|
|
23
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
+
return ar;
|
|
25
|
+
};
|
|
26
|
+
return ownKeys(o);
|
|
27
|
+
};
|
|
28
|
+
return function (mod) {
|
|
29
|
+
if (mod && mod.__esModule) return mod;
|
|
30
|
+
var result = {};
|
|
31
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
+
__setModuleDefault(result, mod);
|
|
33
|
+
return result;
|
|
34
|
+
};
|
|
35
|
+
})();
|
|
36
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
37
|
+
const commander_1 = require("commander");
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const engine_js_1 = require("./scanner/engine.js");
|
|
40
|
+
const ruleset_js_1 = require("./integrity/ruleset.js");
|
|
41
|
+
const score_js_1 = require("./scoring/score.js");
|
|
42
|
+
const lock_js_1 = require("./scoring/lock.js");
|
|
43
|
+
const seal_js_1 = require("./integrity/seal.js");
|
|
44
|
+
const badge_js_1 = require("./output/badge.js");
|
|
45
|
+
const terminal_js_1 = require("./output/terminal.js");
|
|
46
|
+
const fixit_js_1 = require("./output/fixit.js");
|
|
47
|
+
const evidence_js_1 = require("./output/evidence.js");
|
|
48
|
+
const failsafe_js_1 = require("./integrity/failsafe.js");
|
|
49
|
+
const program = new commander_1.Command();
|
|
50
|
+
program
|
|
51
|
+
.name('project-shield')
|
|
52
|
+
.description('Security scanner for AI coders and MCP users')
|
|
53
|
+
.version('1.0.0');
|
|
54
|
+
program
|
|
55
|
+
.command('scan')
|
|
56
|
+
.description('Scan a directory for secrets, PII, and MCP config issues')
|
|
57
|
+
.argument('<path>', 'Path to scan')
|
|
58
|
+
.option('-f, --format <format>', 'Output format (terminal or json)', 'terminal')
|
|
59
|
+
.option('-i, --ignore <path>', 'Path to .shieldignore file')
|
|
60
|
+
.option('-r, --ruleset <path>', 'Path to ruleset JSON file')
|
|
61
|
+
.option('-b, --badge <path>', 'Output path for SVG badge', '')
|
|
62
|
+
.option('--pro', 'Generate Pro badge (no watermark)', false)
|
|
63
|
+
.option('--fix', 'Show fix-it remediation guides', false)
|
|
64
|
+
.option('--evidence <path>', 'Output path for evidence pack (JSON + PDF)')
|
|
65
|
+
.action(async (targetPath, options) => {
|
|
66
|
+
try {
|
|
67
|
+
const resolvedPath = path.resolve(targetPath);
|
|
68
|
+
const format = options.format;
|
|
69
|
+
// Load ruleset info for display
|
|
70
|
+
const ruleset = (0, ruleset_js_1.loadRuleset)(options.ruleset);
|
|
71
|
+
const result = await (0, engine_js_1.scan)({
|
|
72
|
+
targetPath: resolvedPath,
|
|
73
|
+
format,
|
|
74
|
+
ignorePath: options.ignore,
|
|
75
|
+
rulesetPath: options.ruleset,
|
|
76
|
+
});
|
|
77
|
+
// Calculate score and lock status
|
|
78
|
+
const score = (0, score_js_1.calculateScore)(result);
|
|
79
|
+
const lockStatus = (0, lock_js_1.getLockStatus)(score);
|
|
80
|
+
const seal = (0, seal_js_1.sealResult)(result, score, ruleset.version, { isPro: options.pro });
|
|
81
|
+
if (format === 'json') {
|
|
82
|
+
const jsonObj = JSON.parse((0, terminal_js_1.formatJsonOutput)(result, score, seal));
|
|
83
|
+
if (options.fix) {
|
|
84
|
+
const guides = (0, fixit_js_1.getFixitGuides)(result, { isPro: options.pro });
|
|
85
|
+
jsonObj.fixit = (0, fixit_js_1.formatFixitJson)(guides, options.pro);
|
|
86
|
+
}
|
|
87
|
+
console.log(JSON.stringify(jsonObj, null, 2));
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
90
|
+
console.log((0, terminal_js_1.formatTerminalOutput)(result, ruleset.version, ruleset.sha256, score, lockStatus));
|
|
91
|
+
if (options.fix) {
|
|
92
|
+
const guides = (0, fixit_js_1.getFixitGuides)(result, { isPro: options.pro });
|
|
93
|
+
console.log((0, fixit_js_1.formatFixitTerminal)(guides, options.pro));
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
// Evidence Pack generation
|
|
97
|
+
if (options.evidence) {
|
|
98
|
+
const evidencePath = path.resolve(options.evidence);
|
|
99
|
+
const pack = (0, evidence_js_1.generateEvidenceJSON)(result, score, lockStatus, seal, {
|
|
100
|
+
scanTarget: resolvedPath,
|
|
101
|
+
rulesetHash: ruleset.sha256,
|
|
102
|
+
rulesetVersion: ruleset.version,
|
|
103
|
+
});
|
|
104
|
+
(0, evidence_js_1.saveEvidenceJSON)(pack, `${evidencePath}.json`);
|
|
105
|
+
console.log(`Evidence JSON saved to: ${evidencePath}.json`);
|
|
106
|
+
await (0, evidence_js_1.generateEvidencePDF)(pack, `${evidencePath}.pdf`);
|
|
107
|
+
console.log(`Evidence PDF saved to: ${evidencePath}.pdf`);
|
|
108
|
+
}
|
|
109
|
+
// Badge generation
|
|
110
|
+
if (options.badge) {
|
|
111
|
+
try {
|
|
112
|
+
const svg = (0, badge_js_1.generateBadge)(score, lockStatus, { isPro: options.pro });
|
|
113
|
+
const badgePath = path.resolve(options.badge);
|
|
114
|
+
(0, badge_js_1.saveBadge)(svg, badgePath);
|
|
115
|
+
console.log(`Badge saved to: ${badgePath}`);
|
|
116
|
+
}
|
|
117
|
+
catch (error) {
|
|
118
|
+
if (error instanceof badge_js_1.BadgeLockedError) {
|
|
119
|
+
console.error(`Error: ${error.message}`);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
// Exit with code 1 if critical findings
|
|
127
|
+
const hasCritical = result.summary.critical > 0 ||
|
|
128
|
+
result.summary.confirmedPii > 0 ||
|
|
129
|
+
result.summary.mcpCritical > 0 ||
|
|
130
|
+
result.summary.injectionCritical > 0;
|
|
131
|
+
process.exit(hasCritical ? 1 : 0);
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
if (error instanceof failsafe_js_1.RulesetNotFoundError) {
|
|
135
|
+
console.error(`Error: ${error.message}`);
|
|
136
|
+
process.exit(1);
|
|
137
|
+
}
|
|
138
|
+
if (error instanceof failsafe_js_1.RulesetIntegrityError) {
|
|
139
|
+
console.error(`Error: ${error.message}`);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
if (error instanceof failsafe_js_1.ScanFailedError) {
|
|
143
|
+
console.error(`Error: ${error.message}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
console.error('SCAN_FAILED:', error instanceof Error ? error.message : String(error));
|
|
147
|
+
process.exit(1);
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
program.parse();
|
|
151
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import type { ScanResult } from '../types/index.js';
|
|
2
|
+
export declare class ScanFailedError extends Error {
|
|
3
|
+
constructor(message: string);
|
|
4
|
+
}
|
|
5
|
+
export declare class RulesetNotFoundError extends Error {
|
|
6
|
+
constructor(path: string);
|
|
7
|
+
}
|
|
8
|
+
export declare class RulesetIntegrityError extends Error {
|
|
9
|
+
constructor();
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Wraps a scan operation with fail-safe logic.
|
|
13
|
+
* - On crash → ScanFailedError (never returns PASS)
|
|
14
|
+
* - Empty result → never claims "safe"
|
|
15
|
+
*/
|
|
16
|
+
export declare function withFailsafe(scanFn: () => Promise<ScanResult>): Promise<ScanResult>;
|
|
17
|
+
//# sourceMappingURL=failsafe.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failsafe.d.ts","sourceRoot":"","sources":["../../src/integrity/failsafe.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,mBAAmB,CAAC;AAEpD,qBAAa,eAAgB,SAAQ,KAAK;gBAC5B,OAAO,EAAE,MAAM;CAI5B;AAED,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,IAAI,EAAE,MAAM;CAIzB;AAED,qBAAa,qBAAsB,SAAQ,KAAK;;CAK/C;AAED;;;;GAIG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,GAChC,OAAO,CAAC,UAAU,CAAC,CAcrB"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.RulesetIntegrityError = exports.RulesetNotFoundError = exports.ScanFailedError = void 0;
|
|
4
|
+
exports.withFailsafe = withFailsafe;
|
|
5
|
+
class ScanFailedError extends Error {
|
|
6
|
+
constructor(message) {
|
|
7
|
+
super(message);
|
|
8
|
+
this.name = 'ScanFailedError';
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
exports.ScanFailedError = ScanFailedError;
|
|
12
|
+
class RulesetNotFoundError extends Error {
|
|
13
|
+
constructor(path) {
|
|
14
|
+
super(`Ruleset not found: ${path}`);
|
|
15
|
+
this.name = 'RulesetNotFoundError';
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
exports.RulesetNotFoundError = RulesetNotFoundError;
|
|
19
|
+
class RulesetIntegrityError extends Error {
|
|
20
|
+
constructor() {
|
|
21
|
+
super('Ruleset integrity check failed');
|
|
22
|
+
this.name = 'RulesetIntegrityError';
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
exports.RulesetIntegrityError = RulesetIntegrityError;
|
|
26
|
+
/**
|
|
27
|
+
* Wraps a scan operation with fail-safe logic.
|
|
28
|
+
* - On crash → ScanFailedError (never returns PASS)
|
|
29
|
+
* - Empty result → never claims "safe"
|
|
30
|
+
*/
|
|
31
|
+
async function withFailsafe(scanFn) {
|
|
32
|
+
try {
|
|
33
|
+
const result = await scanFn();
|
|
34
|
+
return result;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
if (error instanceof RulesetNotFoundError ||
|
|
38
|
+
error instanceof RulesetIntegrityError) {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
41
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
42
|
+
throw new ScanFailedError(`SCAN_FAILED: ${message}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
//# sourceMappingURL=failsafe.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"failsafe.js","sourceRoot":"","sources":["../../src/integrity/failsafe.ts"],"names":[],"mappings":";;;AA4BA,oCAgBC;AA1CD,MAAa,eAAgB,SAAQ,KAAK;IACxC,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;QACf,IAAI,CAAC,IAAI,GAAG,iBAAiB,CAAC;IAChC,CAAC;CACF;AALD,0CAKC;AAED,MAAa,oBAAqB,SAAQ,KAAK;IAC7C,YAAY,IAAY;QACtB,KAAK,CAAC,sBAAsB,IAAI,EAAE,CAAC,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,sBAAsB,CAAC;IACrC,CAAC;CACF;AALD,oDAKC;AAED,MAAa,qBAAsB,SAAQ,KAAK;IAC9C;QACE,KAAK,CAAC,gCAAgC,CAAC,CAAC;QACxC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;IACtC,CAAC;CACF;AALD,sDAKC;AAED;;;;GAIG;AACI,KAAK,UAAU,YAAY,CAChC,MAAiC;IAEjC,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,MAAM,EAAE,CAAC;QAC9B,OAAO,MAAM,CAAC;IAChB,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IACE,KAAK,YAAY,oBAAoB;YACrC,KAAK,YAAY,qBAAqB,EACtC,CAAC;YACD,MAAM,KAAK,CAAC;QACd,CAAC;QACD,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACvE,MAAM,IAAI,eAAe,CAAC,gBAAgB,OAAO,EAAE,CAAC,CAAC;IACvD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { Ruleset } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Compute SHA-256 hash of the ruleset content (excluding the sha256 field itself).
|
|
4
|
+
*/
|
|
5
|
+
export declare function computeRulesetHash(content: string): string;
|
|
6
|
+
/**
|
|
7
|
+
* Load and verify a ruleset file.
|
|
8
|
+
* Throws RulesetNotFoundError if file doesn't exist.
|
|
9
|
+
* Throws RulesetIntegrityError if hash doesn't match.
|
|
10
|
+
*/
|
|
11
|
+
export declare function loadRuleset(rulesetPath?: string): Ruleset;
|
|
12
|
+
//# sourceMappingURL=ruleset.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ruleset.d.ts","sourceRoot":"","sources":["../../src/integrity/ruleset.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAGjD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAM1D;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,WAAW,CAAC,EAAE,MAAM,GAAG,OAAO,CAwBzD"}
|