@cencori/scan 0.3.3 → 0.3.5
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 +225 -43
- package/dist/cli.js +55 -1
- package/dist/cli.js.map +1 -1
- package/dist/cli.mjs +55 -1
- package/dist/cli.mjs.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,85 +1,171 @@
|
|
|
1
1
|
# @cencori/scan
|
|
2
2
|
|
|
3
|
-
Security scanner for AI apps
|
|
3
|
+
**Security scanner for AI apps.** Detect hardcoded secrets, PII leaks, exposed routes, and security vulnerabilities — with AI-powered auto-fix.
|
|
4
|
+
|
|
5
|
+
[](https://www.npmjs.com/package/@cencori/scan)
|
|
6
|
+
[](https://opensource.org/licenses/MIT)
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
npx @cencori/scan
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
That's it. Run it in any project directory to instantly scan for security issues.
|
|
15
|
+
|
|
16
|
+
## Features
|
|
17
|
+
|
|
18
|
+
- 🔍 **Pattern-based scanning** - Detects 50+ types of secrets, PII, and vulnerabilities
|
|
19
|
+
- 🤖 **AI-powered auto-fix** - Automatically fixes issues with one command
|
|
20
|
+
- ⚡ **Fast** - Scans thousands of files in seconds
|
|
21
|
+
- 🎯 **Zero config** - Works out of the box
|
|
22
|
+
- 📊 **Security scoring** - A through F tier grading
|
|
4
23
|
|
|
5
24
|
## Installation
|
|
6
25
|
|
|
7
26
|
```bash
|
|
8
|
-
# Run directly
|
|
27
|
+
# Run directly (recommended)
|
|
9
28
|
npx @cencori/scan
|
|
10
29
|
|
|
11
30
|
# Or install globally
|
|
12
31
|
npm install -g @cencori/scan
|
|
32
|
+
|
|
33
|
+
# Or as a dev dependency
|
|
34
|
+
npm install -D @cencori/scan
|
|
13
35
|
```
|
|
14
36
|
|
|
15
37
|
## Usage
|
|
16
38
|
|
|
39
|
+
### Basic Scan
|
|
40
|
+
|
|
17
41
|
```bash
|
|
18
42
|
# Scan current directory
|
|
19
|
-
cencori
|
|
43
|
+
npx @cencori/scan
|
|
20
44
|
|
|
21
45
|
# Scan specific path
|
|
22
|
-
cencori
|
|
46
|
+
npx @cencori/scan ./my-project
|
|
23
47
|
|
|
24
|
-
# Output JSON
|
|
25
|
-
cencori
|
|
48
|
+
# Output JSON (for CI/CD)
|
|
49
|
+
npx @cencori/scan --json
|
|
26
50
|
|
|
27
51
|
# Quiet mode (score only)
|
|
28
|
-
cencori
|
|
52
|
+
npx @cencori/scan --quiet
|
|
53
|
+
|
|
54
|
+
# Skip interactive prompts
|
|
55
|
+
npx @cencori/scan --no-prompt
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### AI Auto-Fix (Pro)
|
|
59
|
+
|
|
60
|
+
After scanning, you'll be prompted:
|
|
61
|
+
|
|
62
|
+
```
|
|
63
|
+
? Would you like Cencori to auto-fix these issues? (y/n)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
Enter `y` and you'll be asked for your API key (if not already saved):
|
|
67
|
+
|
|
68
|
+
```
|
|
69
|
+
? Enter your Cencori API key: ************************
|
|
29
70
|
```
|
|
30
71
|
|
|
72
|
+
The AI will:
|
|
73
|
+
1. Analyze each issue for false positives
|
|
74
|
+
2. Generate secure code fixes
|
|
75
|
+
3. Apply fixes automatically
|
|
76
|
+
|
|
77
|
+
Your API key is saved to `~/.cencorirc` for future scans.
|
|
78
|
+
|
|
79
|
+
**Get your free API key at [cencori.com/dashboard](https://cencori.com/dashboard)**
|
|
80
|
+
|
|
31
81
|
## What It Detects
|
|
32
82
|
|
|
33
|
-
### API Keys & Secrets
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
83
|
+
### 🔐 API Keys & Secrets
|
|
84
|
+
|
|
85
|
+
| Provider | Pattern |
|
|
86
|
+
|----------|---------|
|
|
87
|
+
| OpenAI | `sk-...`, `sk-proj-...` |
|
|
88
|
+
| Anthropic | `sk-ant-...` |
|
|
89
|
+
| Google AI | `AIza...` |
|
|
90
|
+
| Supabase | `eyJh...` (service role) |
|
|
91
|
+
| Stripe | `sk_live_...`, `sk_test_...` |
|
|
92
|
+
| AWS | `AKIA...` |
|
|
93
|
+
| GitHub | `ghp_...`, `gho_...` |
|
|
94
|
+
| Firebase | `firebase-adminsdk-...` |
|
|
95
|
+
| And 20+ more... | |
|
|
38
96
|
|
|
39
|
-
### PII (Personal Identifiable Information)
|
|
40
|
-
|
|
97
|
+
### 👤 PII (Personal Identifiable Information)
|
|
98
|
+
|
|
99
|
+
- Email addresses in code
|
|
41
100
|
- Phone numbers
|
|
42
101
|
- Social Security Numbers
|
|
43
102
|
- Credit card numbers
|
|
44
103
|
|
|
45
|
-
### Exposed Routes
|
|
46
|
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
104
|
+
### 🛣️ Exposed Routes
|
|
105
|
+
|
|
106
|
+
- Next.js API routes without authentication
|
|
107
|
+
- Express routes without auth middleware
|
|
108
|
+
- Sensitive files in `/public` folders
|
|
109
|
+
- Dashboard/admin routes without protection
|
|
110
|
+
|
|
111
|
+
### ⚠️ Security Vulnerabilities
|
|
112
|
+
|
|
113
|
+
- SQL injection patterns
|
|
114
|
+
- XSS vulnerabilities (innerHTML, dangerouslySetInnerHTML)
|
|
115
|
+
- Insecure CORS configuration (`Access-Control-Allow-Origin: *`)
|
|
116
|
+
- Hardcoded passwords
|
|
117
|
+
- Debug modes in production
|
|
49
118
|
|
|
50
119
|
## Security Score
|
|
51
120
|
|
|
52
|
-
| Score | Meaning |
|
|
53
|
-
|
|
54
|
-
| A-Tier | Excellent
|
|
55
|
-
| B-Tier | Good
|
|
56
|
-
| C-Tier | Fair
|
|
57
|
-
| D-Tier | Poor
|
|
58
|
-
| F-Tier | Critical
|
|
121
|
+
| Score | Meaning | Action Required |
|
|
122
|
+
|-------|---------|-----------------|
|
|
123
|
+
| **A-Tier** | Excellent | No security issues detected |
|
|
124
|
+
| **B-Tier** | Good | Minor improvements recommended |
|
|
125
|
+
| **C-Tier** | Fair | Some concerns need attention |
|
|
126
|
+
| **D-Tier** | Poor | Significant issues found |
|
|
127
|
+
| **F-Tier** | Critical | Secrets or major vulnerabilities exposed |
|
|
59
128
|
|
|
60
129
|
## Example Output
|
|
61
130
|
|
|
62
131
|
```
|
|
63
|
-
Cencori Scan
|
|
64
|
-
v0.
|
|
132
|
+
Cencori Scan
|
|
133
|
+
v0.3.4
|
|
134
|
+
|
|
135
|
+
✔ Scanned 142 files
|
|
136
|
+
|
|
137
|
+
┌─────────────────────────────────────────────┐
|
|
138
|
+
│ Security Score: D-Tier │
|
|
139
|
+
└─────────────────────────────────────────────┘
|
|
65
140
|
|
|
66
|
-
|
|
141
|
+
Poor! Significant security issues found.
|
|
67
142
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
143
|
+
SECRETS (3)
|
|
144
|
+
├─ src/api.ts:12 sk-proj-****
|
|
145
|
+
│ Hardcoded API key - use environment variables
|
|
146
|
+
├─ src/lib.ts:5 eyJh****
|
|
147
|
+
│ Supabase service role key exposed
|
|
148
|
+
└─ .env.local:3 ANTH****
|
|
149
|
+
Anthropic API key in tracked file
|
|
71
150
|
|
|
72
|
-
|
|
73
|
-
├─ src/
|
|
74
|
-
|
|
75
|
-
└─ .
|
|
151
|
+
VULNERABILITIES (2)
|
|
152
|
+
├─ src/db.ts:45 `SELECT * FROM users WHERE id = ${userId}`
|
|
153
|
+
│ Potential SQL injection - use parameterized queries
|
|
154
|
+
└─ src/page.tsx:23 dangerouslySetInnerHTML={{ __html: content }}
|
|
155
|
+
XSS vulnerability - sanitize content first
|
|
76
156
|
|
|
77
|
-
|
|
78
|
-
- Use environment variables for secrets
|
|
79
|
-
- Never commit API keys to version control
|
|
157
|
+
─────────────────────────────────────────────
|
|
80
158
|
|
|
81
|
-
|
|
82
|
-
|
|
159
|
+
Summary
|
|
160
|
+
Files scanned: 142
|
|
161
|
+
Scan time: 89ms
|
|
162
|
+
|
|
163
|
+
Recommendations:
|
|
164
|
+
- Use environment variables for secrets
|
|
165
|
+
- Never commit API keys to version control
|
|
166
|
+
- Sanitize user input before rendering HTML
|
|
167
|
+
|
|
168
|
+
? Would you like Cencori to auto-fix these issues? (y/n)
|
|
83
169
|
```
|
|
84
170
|
|
|
85
171
|
## Programmatic Usage
|
|
@@ -89,10 +175,106 @@ import { scan } from '@cencori/scan';
|
|
|
89
175
|
|
|
90
176
|
const result = await scan('./my-project');
|
|
91
177
|
|
|
92
|
-
console.log(result.score);
|
|
93
|
-
console.log(result.issues);
|
|
178
|
+
console.log(result.score); // 'A' | 'B' | 'C' | 'D' | 'F'
|
|
179
|
+
console.log(result.issues); // Array of detected issues
|
|
180
|
+
console.log(result.filesScanned); // Number of files scanned
|
|
181
|
+
console.log(result.scanDuration); // Time in milliseconds
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### TypeScript Types
|
|
185
|
+
|
|
186
|
+
```typescript
|
|
187
|
+
interface ScanResult {
|
|
188
|
+
score: 'A' | 'B' | 'C' | 'D' | 'F';
|
|
189
|
+
tierDescription: string;
|
|
190
|
+
issues: ScanIssue[];
|
|
191
|
+
filesScanned: number;
|
|
192
|
+
scanDuration: number;
|
|
193
|
+
summary: {
|
|
194
|
+
critical: number;
|
|
195
|
+
high: number;
|
|
196
|
+
medium: number;
|
|
197
|
+
low: number;
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
interface ScanIssue {
|
|
202
|
+
type: 'secret' | 'pii' | 'route' | 'config' | 'vulnerability';
|
|
203
|
+
severity: 'critical' | 'high' | 'medium' | 'low';
|
|
204
|
+
name: string;
|
|
205
|
+
match: string;
|
|
206
|
+
file: string;
|
|
207
|
+
line: number;
|
|
208
|
+
description?: string;
|
|
209
|
+
}
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## CI/CD Integration
|
|
213
|
+
|
|
214
|
+
### GitHub Actions
|
|
215
|
+
|
|
216
|
+
```yaml
|
|
217
|
+
name: Security Scan
|
|
218
|
+
|
|
219
|
+
on: [push, pull_request]
|
|
220
|
+
|
|
221
|
+
jobs:
|
|
222
|
+
scan:
|
|
223
|
+
runs-on: ubuntu-latest
|
|
224
|
+
steps:
|
|
225
|
+
- uses: actions/checkout@v4
|
|
226
|
+
- name: Run Cencori Scan
|
|
227
|
+
run: npx @cencori/scan --json > scan-results.json
|
|
228
|
+
- name: Check for failures
|
|
229
|
+
run: |
|
|
230
|
+
SCORE=$(jq -r '.score' scan-results.json)
|
|
231
|
+
if [[ "$SCORE" == "F" ]]; then
|
|
232
|
+
echo "Security scan failed with F-Tier score"
|
|
233
|
+
exit 1
|
|
234
|
+
fi
|
|
94
235
|
```
|
|
95
236
|
|
|
237
|
+
### Pre-commit Hook
|
|
238
|
+
|
|
239
|
+
Add to `.husky/pre-commit`:
|
|
240
|
+
|
|
241
|
+
```bash
|
|
242
|
+
#!/bin/sh
|
|
243
|
+
npx @cencori/scan --quiet --no-prompt
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
## Configuration
|
|
247
|
+
|
|
248
|
+
### Environment Variables
|
|
249
|
+
|
|
250
|
+
| Variable | Description |
|
|
251
|
+
|----------|-------------|
|
|
252
|
+
| `CENCORI_API_KEY` | API key for AI features (optional) |
|
|
253
|
+
|
|
254
|
+
### Config File
|
|
255
|
+
|
|
256
|
+
API keys are automatically saved to `~/.cencorirc`:
|
|
257
|
+
|
|
258
|
+
```
|
|
259
|
+
api_key=your_cencori_api_key
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
## Privacy
|
|
263
|
+
|
|
264
|
+
Cencori Scan collects **anonymous usage metrics** to improve the product:
|
|
265
|
+
- Number of files scanned
|
|
266
|
+
- Number of issues found
|
|
267
|
+
- Security score
|
|
268
|
+
- Platform (macOS/Linux/Windows)
|
|
269
|
+
|
|
270
|
+
**No code, file paths, or sensitive data is ever transmitted.**
|
|
271
|
+
|
|
272
|
+
## Links
|
|
273
|
+
|
|
274
|
+
- **Documentation**: [cencori.com/docs](https://cencori.com/docs)
|
|
275
|
+
- **Dashboard**: [cencori.com/dashboard](https://cencori.com/dashboard)
|
|
276
|
+
- **Web Scanner**: [scan.cencori.com](https://scan.cencori.com)
|
|
277
|
+
|
|
96
278
|
## License
|
|
97
279
|
|
|
98
|
-
MIT - Cencori
|
|
280
|
+
MIT - [Cencori](https://cencori.com)
|
package/dist/cli.js
CHANGED
|
@@ -925,10 +925,58 @@ async function applyFixes(fixes, fileContents) {
|
|
|
925
925
|
return fixes;
|
|
926
926
|
}
|
|
927
927
|
|
|
928
|
+
// src/telemetry.ts
|
|
929
|
+
var TELEMETRY_URL = "https://cencori.com/api/v1/telemetry/scan";
|
|
930
|
+
var pendingTelemetry = null;
|
|
931
|
+
function sendTelemetry(data) {
|
|
932
|
+
pendingTelemetry = fetch(TELEMETRY_URL, {
|
|
933
|
+
method: "POST",
|
|
934
|
+
headers: {
|
|
935
|
+
"Content-Type": "application/json"
|
|
936
|
+
},
|
|
937
|
+
body: JSON.stringify(data)
|
|
938
|
+
}).then(() => {
|
|
939
|
+
}).catch(() => {
|
|
940
|
+
});
|
|
941
|
+
return pendingTelemetry;
|
|
942
|
+
}
|
|
943
|
+
async function flushTelemetry() {
|
|
944
|
+
if (pendingTelemetry) {
|
|
945
|
+
await pendingTelemetry;
|
|
946
|
+
pendingTelemetry = null;
|
|
947
|
+
}
|
|
948
|
+
}
|
|
949
|
+
function buildTelemetryData(result, version, hasApiKey) {
|
|
950
|
+
const breakdown = {
|
|
951
|
+
secrets: 0,
|
|
952
|
+
pii: 0,
|
|
953
|
+
routes: 0,
|
|
954
|
+
config: 0,
|
|
955
|
+
vulnerabilities: 0
|
|
956
|
+
};
|
|
957
|
+
for (const issue of result.issues) {
|
|
958
|
+
const type = issue.type;
|
|
959
|
+
if (type in breakdown) {
|
|
960
|
+
breakdown[type]++;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
return {
|
|
964
|
+
event: "scan_completed",
|
|
965
|
+
version,
|
|
966
|
+
platform: process.platform,
|
|
967
|
+
filesScanned: result.filesScanned,
|
|
968
|
+
issuesFound: result.issues.length,
|
|
969
|
+
score: result.score,
|
|
970
|
+
hasApiKey,
|
|
971
|
+
scanDuration: result.scanDuration,
|
|
972
|
+
issueBreakdown: breakdown
|
|
973
|
+
};
|
|
974
|
+
}
|
|
975
|
+
|
|
928
976
|
// src/cli.ts
|
|
929
977
|
var fs3 = __toESM(require("fs"));
|
|
930
978
|
var path3 = __toESM(require("path"));
|
|
931
|
-
var VERSION = "0.3.
|
|
979
|
+
var VERSION = "0.3.4";
|
|
932
980
|
var scoreStyles = {
|
|
933
981
|
A: { color: import_chalk.default.green },
|
|
934
982
|
B: { color: import_chalk.default.blue },
|
|
@@ -1192,7 +1240,9 @@ async function main() {
|
|
|
1192
1240
|
import_commander.program.name("cencori-scan").description("Security scanner for AI apps. Detect secrets, PII, and exposed routes.").version(VERSION).argument("[path]", "Path to scan", ".").option("-j, --json", "Output results as JSON").option("-q, --quiet", "Only output the score").option("--no-prompt", "Skip interactive prompts").option("--no-color", "Disable colored output").action(async (targetPath, options) => {
|
|
1193
1241
|
if (options.json) {
|
|
1194
1242
|
const result = await scan(targetPath);
|
|
1243
|
+
sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));
|
|
1195
1244
|
console.log(JSON.stringify(result, null, 2));
|
|
1245
|
+
await flushTelemetry();
|
|
1196
1246
|
process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
|
|
1197
1247
|
return;
|
|
1198
1248
|
}
|
|
@@ -1203,12 +1253,14 @@ async function main() {
|
|
|
1203
1253
|
}).start();
|
|
1204
1254
|
try {
|
|
1205
1255
|
const result = await scan(targetPath);
|
|
1256
|
+
sendTelemetry(buildTelemetryData(result, VERSION, !!getApiKey()));
|
|
1206
1257
|
spinner.succeed(`Scanned ${result.filesScanned} files`);
|
|
1207
1258
|
if (options.quiet) {
|
|
1208
1259
|
const style = scoreStyles[result.score];
|
|
1209
1260
|
console.log(`
|
|
1210
1261
|
Score: ${style.color.bold(result.score + "-Tier")}
|
|
1211
1262
|
`);
|
|
1263
|
+
await flushTelemetry();
|
|
1212
1264
|
process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
|
|
1213
1265
|
return;
|
|
1214
1266
|
}
|
|
@@ -1220,11 +1272,13 @@ async function main() {
|
|
|
1220
1272
|
await handleAutoFix(result, targetPath);
|
|
1221
1273
|
}
|
|
1222
1274
|
printFooter();
|
|
1275
|
+
await flushTelemetry();
|
|
1223
1276
|
process.exit(result.score === "A" || result.score === "B" ? 0 : 1);
|
|
1224
1277
|
} catch (error) {
|
|
1225
1278
|
spinner.fail("Scan failed");
|
|
1226
1279
|
console.error(import_chalk.default.red(`
|
|
1227
1280
|
Error: ${error instanceof Error ? error.message : "Unknown error"}`));
|
|
1281
|
+
await flushTelemetry();
|
|
1228
1282
|
process.exit(1);
|
|
1229
1283
|
}
|
|
1230
1284
|
});
|