@musashimiyamoto/agent-guard 0.3.0 → 0.4.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/package.json +2 -1
- package/src/cli.js +144 -16
- package/src/license.js +227 -0
- package/src/scanner.js +35 -2
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@musashimiyamoto/agent-guard",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Security scanner for AI agent configurations. Detects misconfigurations, exposed secrets, and unsafe skill patterns.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -39,6 +39,7 @@
|
|
|
39
39
|
"files": [
|
|
40
40
|
"src/cli.js",
|
|
41
41
|
"src/scanner.js",
|
|
42
|
+
"src/license.js",
|
|
42
43
|
"src/rules/",
|
|
43
44
|
"src/proxy/",
|
|
44
45
|
"README.md"
|
package/src/cli.js
CHANGED
|
@@ -7,8 +7,9 @@ import { scan } from './scanner.js';
|
|
|
7
7
|
import { resolve } from 'path';
|
|
8
8
|
|
|
9
9
|
import { createProxy } from './proxy/index.js';
|
|
10
|
+
import { getLicense, getUpgradePrompt } from './license.js';
|
|
10
11
|
|
|
11
|
-
const VERSION = '0.
|
|
12
|
+
const VERSION = '0.4.0';
|
|
12
13
|
|
|
13
14
|
const FEEDBACK_URL = 'https://github.com/MusashiMiyamoto1-cloud/agent-guard/issues/new';
|
|
14
15
|
const REPO_URL = 'https://github.com/MusashiMiyamoto1-cloud/agent-guard';
|
|
@@ -53,27 +54,36 @@ ${COLORS.cyan}╔═════════════════════
|
|
|
53
54
|
function printHelp() {
|
|
54
55
|
console.log(`
|
|
55
56
|
${COLORS.bold}Usage:${COLORS.reset}
|
|
56
|
-
agent-guard scan [path]
|
|
57
|
-
agent-guard proxy [port]
|
|
58
|
-
agent-guard
|
|
59
|
-
agent-guard
|
|
60
|
-
agent-guard
|
|
57
|
+
agent-guard scan [path] Scan directory for security issues
|
|
58
|
+
agent-guard proxy [port] Start runtime protection proxy (Pro)
|
|
59
|
+
agent-guard license activate KEY Activate Pro license
|
|
60
|
+
agent-guard license status Show license status
|
|
61
|
+
agent-guard license deactivate Remove license
|
|
62
|
+
agent-guard feedback [msg] Submit feedback or report an issue
|
|
63
|
+
agent-guard --help Show this help message
|
|
64
|
+
agent-guard --version Show version
|
|
61
65
|
|
|
62
66
|
${COLORS.bold}Examples:${COLORS.reset}
|
|
63
|
-
npx agent-guard scan .
|
|
64
|
-
npx agent-guard scan ./my-agent
|
|
65
|
-
npx agent-guard
|
|
66
|
-
npx agent-guard proxy
|
|
67
|
-
npx agent-guard feedback
|
|
68
|
-
npx agent-guard feedback "Bug: ..." Submit inline feedback
|
|
67
|
+
npx agent-guard scan . Scan current directory
|
|
68
|
+
npx agent-guard scan ./my-agent Scan specific agent directory
|
|
69
|
+
npx agent-guard license activate XXXX-XXXX Activate Pro license
|
|
70
|
+
npx agent-guard proxy Start proxy on port 18800 (Pro)
|
|
71
|
+
npx agent-guard feedback "Bug: ..." Submit inline feedback
|
|
69
72
|
|
|
70
73
|
${COLORS.bold}Options:${COLORS.reset}
|
|
71
|
-
--json Output results as JSON
|
|
74
|
+
--json Output results as JSON (Pro)
|
|
72
75
|
--quiet Only show findings (no banner)
|
|
73
76
|
--policy <file> Use custom policy file
|
|
74
77
|
|
|
78
|
+
${COLORS.bold}Free vs Pro:${COLORS.reset}
|
|
79
|
+
Free: 50 files, 10 findings, 5 basic rules
|
|
80
|
+
Pro: Unlimited, all 20+ rules, JSON, proxy, dashboard
|
|
81
|
+
|
|
82
|
+
Get Pro: https://agentguard.co/pro
|
|
83
|
+
|
|
75
84
|
${COLORS.bold}Environment:${COLORS.reset}
|
|
76
|
-
HTTP_PROXY=http://127.0.0.1:18800 Route agent through proxy
|
|
85
|
+
HTTP_PROXY=http://127.0.0.1:18800 Route agent through proxy (Pro)
|
|
86
|
+
AGENT_GUARD_LICENSE=KEY License key (alternative to activate)
|
|
77
87
|
|
|
78
88
|
${COLORS.bold}Exit Codes:${COLORS.reset}
|
|
79
89
|
0 No critical findings
|
|
@@ -225,6 +235,65 @@ async function handleFeedback(message) {
|
|
|
225
235
|
console.log(`\n${COLORS.gray}Or visit: ${COLORS.cyan}${FEEDBACK_URL}${COLORS.reset}\n`);
|
|
226
236
|
}
|
|
227
237
|
|
|
238
|
+
async function handleLicense(subcommand, args, quiet) {
|
|
239
|
+
const license = await getLicense();
|
|
240
|
+
|
|
241
|
+
if (subcommand === 'activate') {
|
|
242
|
+
const key = args[0] || process.env.AGENT_GUARD_LICENSE;
|
|
243
|
+
if (!key) {
|
|
244
|
+
console.log(`${COLORS.red}Error: License key required${COLORS.reset}`);
|
|
245
|
+
console.log(`Usage: agent-guard license activate YOUR-LICENSE-KEY`);
|
|
246
|
+
console.log(`\nGet a license at: ${COLORS.cyan}https://agentguard.co/pro${COLORS.reset}`);
|
|
247
|
+
process.exit(2);
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(`${COLORS.gray}Validating license...${COLORS.reset}`);
|
|
251
|
+
const result = await license.activate(key);
|
|
252
|
+
|
|
253
|
+
if (result.success) {
|
|
254
|
+
console.log(`${COLORS.green}✓ ${result.message}${COLORS.reset}`);
|
|
255
|
+
console.log(`\n${COLORS.bold}License Details:${COLORS.reset}`);
|
|
256
|
+
console.log(` Product: ${license.data.product}`);
|
|
257
|
+
console.log(` Email: ${license.data.email || 'N/A'}`);
|
|
258
|
+
if (license.data.expiresAt) {
|
|
259
|
+
console.log(` Expires: ${license.data.expiresAt}`);
|
|
260
|
+
}
|
|
261
|
+
console.log(`\n${COLORS.green}All Pro features unlocked!${COLORS.reset}`);
|
|
262
|
+
} else {
|
|
263
|
+
console.log(`${COLORS.red}✗ ${result.message}${COLORS.reset}`);
|
|
264
|
+
console.log(`\nNeed a license? Visit: ${COLORS.cyan}https://agentguard.co/pro${COLORS.reset}`);
|
|
265
|
+
process.exit(2);
|
|
266
|
+
}
|
|
267
|
+
} else if (subcommand === 'status') {
|
|
268
|
+
if (license.isPro()) {
|
|
269
|
+
console.log(`${COLORS.green}✓ Pro License Active${COLORS.reset}\n`);
|
|
270
|
+
console.log(`${COLORS.bold}License Details:${COLORS.reset}`);
|
|
271
|
+
console.log(` Product: ${license.data.product}`);
|
|
272
|
+
console.log(` Email: ${license.data.email || 'N/A'}`);
|
|
273
|
+
console.log(` Since: ${license.data.activatedAt}`);
|
|
274
|
+
if (license.data.expiresAt) {
|
|
275
|
+
console.log(` Expires: ${license.data.expiresAt}`);
|
|
276
|
+
}
|
|
277
|
+
} else {
|
|
278
|
+
console.log(`${COLORS.yellow}Free Tier${COLORS.reset}\n`);
|
|
279
|
+
console.log(`${COLORS.bold}Limits:${COLORS.reset}`);
|
|
280
|
+
console.log(` Files: 50 max`);
|
|
281
|
+
console.log(` Findings: 10 shown`);
|
|
282
|
+
console.log(` Rules: 5 basic`);
|
|
283
|
+
console.log(` JSON: ✗`);
|
|
284
|
+
console.log(` Proxy: ✗`);
|
|
285
|
+
console.log(getUpgradePrompt());
|
|
286
|
+
}
|
|
287
|
+
} else if (subcommand === 'deactivate') {
|
|
288
|
+
const result = await license.deactivate();
|
|
289
|
+
console.log(`${COLORS.green}✓ ${result.message}${COLORS.reset}`);
|
|
290
|
+
} else {
|
|
291
|
+
console.log(`Unknown license command: ${subcommand}`);
|
|
292
|
+
console.log(`Usage: agent-guard license [activate|status|deactivate]`);
|
|
293
|
+
process.exit(2);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
228
297
|
async function main() {
|
|
229
298
|
const args = process.argv.slice(2);
|
|
230
299
|
|
|
@@ -232,6 +301,14 @@ async function main() {
|
|
|
232
301
|
const quiet = args.includes('--quiet');
|
|
233
302
|
const filteredArgs = args.filter(a => !a.startsWith('--'));
|
|
234
303
|
|
|
304
|
+
// Load license early
|
|
305
|
+
const license = await getLicense();
|
|
306
|
+
|
|
307
|
+
// Check for env var license
|
|
308
|
+
if (!license.isPro() && process.env.AGENT_GUARD_LICENSE) {
|
|
309
|
+
await license.activate(process.env.AGENT_GUARD_LICENSE);
|
|
310
|
+
}
|
|
311
|
+
|
|
235
312
|
if (args.includes('--help') || args.includes('-h')) {
|
|
236
313
|
printBanner();
|
|
237
314
|
printHelp();
|
|
@@ -252,7 +329,22 @@ async function main() {
|
|
|
252
329
|
process.exit(0);
|
|
253
330
|
}
|
|
254
331
|
|
|
332
|
+
if (command === 'license') {
|
|
333
|
+
const subcommand = filteredArgs[1] || 'status';
|
|
334
|
+
const subArgs = filteredArgs.slice(2);
|
|
335
|
+
if (!quiet) printBanner();
|
|
336
|
+
await handleLicense(subcommand, subArgs, quiet);
|
|
337
|
+
process.exit(0);
|
|
338
|
+
}
|
|
339
|
+
|
|
255
340
|
if (command === 'proxy') {
|
|
341
|
+
if (!license.isPro()) {
|
|
342
|
+
if (!quiet) printBanner();
|
|
343
|
+
console.log(`${COLORS.red}Runtime proxy requires Pro license.${COLORS.reset}\n`);
|
|
344
|
+
console.log(getUpgradePrompt('proxy'));
|
|
345
|
+
process.exit(2);
|
|
346
|
+
}
|
|
347
|
+
|
|
256
348
|
const port = parseInt(filteredArgs[1]) || 18800;
|
|
257
349
|
if (!quiet) printBanner();
|
|
258
350
|
console.log(`${COLORS.cyan}Starting runtime protection proxy...${COLORS.reset}\n`);
|
|
@@ -291,17 +383,53 @@ async function main() {
|
|
|
291
383
|
}
|
|
292
384
|
|
|
293
385
|
try {
|
|
294
|
-
const report = await scan(targetPath);
|
|
386
|
+
const report = await scan(targetPath, { license });
|
|
387
|
+
|
|
388
|
+
// Apply license limits
|
|
389
|
+
const limits = license.getLimits();
|
|
390
|
+
let findings = report.findings;
|
|
391
|
+
let truncated = false;
|
|
392
|
+
let hitFileLimit = report.hitFileLimit;
|
|
393
|
+
|
|
394
|
+
if (!license.isPro() && findings.length > limits.maxFindings) {
|
|
395
|
+
truncated = true;
|
|
396
|
+
findings = findings.slice(0, limits.maxFindings);
|
|
397
|
+
}
|
|
295
398
|
|
|
296
399
|
if (jsonOutput) {
|
|
400
|
+
if (!license.isPro()) {
|
|
401
|
+
console.log(`${COLORS.red}JSON output requires Pro license.${COLORS.reset}\n`);
|
|
402
|
+
console.log(getUpgradePrompt('json'));
|
|
403
|
+
process.exit(2);
|
|
404
|
+
}
|
|
297
405
|
// Add feedback URL to JSON output for agent consumption
|
|
298
406
|
report.feedback_url = FEEDBACK_URL;
|
|
299
407
|
report.repo_url = REPO_URL;
|
|
300
408
|
console.log(JSON.stringify(report, null, 2));
|
|
301
409
|
} else {
|
|
410
|
+
// Show rules info for free tier
|
|
411
|
+
if (!license.isPro()) {
|
|
412
|
+
console.log(`${COLORS.gray}Using ${report.rulesUsed}/${report.totalRules} rules (Free tier)${COLORS.reset}\n`);
|
|
413
|
+
}
|
|
414
|
+
|
|
302
415
|
printScore(report);
|
|
303
|
-
printFindings(
|
|
416
|
+
printFindings(findings);
|
|
417
|
+
|
|
418
|
+
if (hitFileLimit) {
|
|
419
|
+
console.log(`${COLORS.yellow}${getUpgradePrompt('files')}${COLORS.reset}\n`);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (truncated) {
|
|
423
|
+
console.log(`${COLORS.yellow}${getUpgradePrompt('findings').replace('{total}', report.findings.length)}${COLORS.reset}\n`);
|
|
424
|
+
}
|
|
425
|
+
|
|
304
426
|
printRecommendations(report);
|
|
427
|
+
|
|
428
|
+
// Show upgrade prompt for free users with findings
|
|
429
|
+
if (!license.isPro() && report.totalFindings > 0) {
|
|
430
|
+
console.log(getUpgradePrompt());
|
|
431
|
+
}
|
|
432
|
+
|
|
305
433
|
printFeedbackFooter();
|
|
306
434
|
}
|
|
307
435
|
|
package/src/license.js
ADDED
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Agent Guard License System
|
|
2
|
+
// Integrates with LemonSqueezy for payment processing
|
|
3
|
+
|
|
4
|
+
import { homedir } from 'os';
|
|
5
|
+
import { join } from 'path';
|
|
6
|
+
import { readFile, writeFile, mkdir } from 'fs/promises';
|
|
7
|
+
|
|
8
|
+
const CONFIG_DIR = join(homedir(), '.agent-guard');
|
|
9
|
+
const LICENSE_FILE = join(CONFIG_DIR, 'license.json');
|
|
10
|
+
const LEMON_API = 'https://api.lemonsqueezy.com/v1/licenses/validate';
|
|
11
|
+
|
|
12
|
+
// Product IDs (set these after creating LemonSqueezy products)
|
|
13
|
+
const PRODUCT_IDS = {
|
|
14
|
+
'agent-guard-pro': null, // Will be set after LemonSqueezy setup
|
|
15
|
+
'musashi-suite': null,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
// Feature limits
|
|
19
|
+
export const LIMITS = {
|
|
20
|
+
free: {
|
|
21
|
+
maxFiles: 50,
|
|
22
|
+
maxFindings: 10, // Only show first 10 findings
|
|
23
|
+
rules: 'basic', // Only basic rules (SEC-001 to SEC-005)
|
|
24
|
+
jsonOutput: false,
|
|
25
|
+
proxy: false,
|
|
26
|
+
dashboard: false,
|
|
27
|
+
},
|
|
28
|
+
pro: {
|
|
29
|
+
maxFiles: Infinity,
|
|
30
|
+
maxFindings: Infinity,
|
|
31
|
+
rules: 'all',
|
|
32
|
+
jsonOutput: true,
|
|
33
|
+
proxy: true,
|
|
34
|
+
dashboard: true,
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Basic rule IDs (free tier)
|
|
39
|
+
const BASIC_RULES = ['SEC-001', 'SEC-002', 'SEC-003', 'SEC-004', 'SEC-005'];
|
|
40
|
+
|
|
41
|
+
export class License {
|
|
42
|
+
constructor() {
|
|
43
|
+
this.data = null;
|
|
44
|
+
this.tier = 'free';
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async load() {
|
|
48
|
+
try {
|
|
49
|
+
const content = await readFile(LICENSE_FILE, 'utf-8');
|
|
50
|
+
this.data = JSON.parse(content);
|
|
51
|
+
|
|
52
|
+
// Check if still valid
|
|
53
|
+
if (this.data.valid && this.data.expiresAt) {
|
|
54
|
+
if (new Date(this.data.expiresAt) < new Date()) {
|
|
55
|
+
// Expired, try to revalidate
|
|
56
|
+
if (this.data.key) {
|
|
57
|
+
await this.activate(this.data.key);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
this.tier = 'pro';
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
// No license file or invalid
|
|
65
|
+
this.data = null;
|
|
66
|
+
this.tier = 'free';
|
|
67
|
+
}
|
|
68
|
+
return this;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
async save() {
|
|
72
|
+
try {
|
|
73
|
+
await mkdir(CONFIG_DIR, { recursive: true });
|
|
74
|
+
await writeFile(LICENSE_FILE, JSON.stringify(this.data, null, 2));
|
|
75
|
+
} catch (err) {
|
|
76
|
+
// Ignore save errors
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async activate(licenseKey) {
|
|
81
|
+
// Clean the key
|
|
82
|
+
const key = licenseKey.trim();
|
|
83
|
+
|
|
84
|
+
// Try LemonSqueezy validation
|
|
85
|
+
try {
|
|
86
|
+
const response = await fetch(LEMON_API, {
|
|
87
|
+
method: 'POST',
|
|
88
|
+
headers: {
|
|
89
|
+
'Accept': 'application/json',
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
},
|
|
92
|
+
body: JSON.stringify({
|
|
93
|
+
license_key: key,
|
|
94
|
+
instance_name: `agent-guard-${homedir().split('/').pop()}`
|
|
95
|
+
})
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
const result = await response.json();
|
|
99
|
+
|
|
100
|
+
if (result.valid || result.license_key?.status === 'active') {
|
|
101
|
+
this.data = {
|
|
102
|
+
key,
|
|
103
|
+
valid: true,
|
|
104
|
+
activatedAt: new Date().toISOString(),
|
|
105
|
+
expiresAt: result.license_key?.expires_at || null,
|
|
106
|
+
email: result.meta?.customer_email || null,
|
|
107
|
+
product: result.meta?.product_name || 'Agent Guard Pro',
|
|
108
|
+
instanceId: result.instance?.id || null,
|
|
109
|
+
};
|
|
110
|
+
this.tier = 'pro';
|
|
111
|
+
await this.save();
|
|
112
|
+
return { success: true, message: 'License activated!' };
|
|
113
|
+
} else {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
message: result.error || 'Invalid license key'
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
// Network error - check for offline activation
|
|
121
|
+
// For now, allow a grace period with cached validation
|
|
122
|
+
if (this.data?.key === key && this.data?.valid) {
|
|
123
|
+
this.tier = 'pro';
|
|
124
|
+
return { success: true, message: 'License valid (cached)' };
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
success: false,
|
|
128
|
+
message: `Validation failed: ${err.message}`
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async deactivate() {
|
|
134
|
+
if (this.data?.instanceId) {
|
|
135
|
+
// Optionally deactivate on LemonSqueezy
|
|
136
|
+
try {
|
|
137
|
+
await fetch(`https://api.lemonsqueezy.com/v1/licenses/deactivate`, {
|
|
138
|
+
method: 'POST',
|
|
139
|
+
headers: {
|
|
140
|
+
'Accept': 'application/json',
|
|
141
|
+
'Content-Type': 'application/json',
|
|
142
|
+
},
|
|
143
|
+
body: JSON.stringify({
|
|
144
|
+
license_key: this.data.key,
|
|
145
|
+
instance_id: this.data.instanceId
|
|
146
|
+
})
|
|
147
|
+
});
|
|
148
|
+
} catch {
|
|
149
|
+
// Ignore deactivation errors
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
this.data = null;
|
|
154
|
+
this.tier = 'free';
|
|
155
|
+
await this.save();
|
|
156
|
+
return { success: true, message: 'License deactivated' };
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
getLimits() {
|
|
160
|
+
return LIMITS[this.tier];
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
isPro() {
|
|
164
|
+
return this.tier === 'pro';
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
filterRules(rules) {
|
|
168
|
+
if (this.isPro()) return rules;
|
|
169
|
+
// Free tier: only basic rules
|
|
170
|
+
return rules.filter(r => BASIC_RULES.includes(r.id));
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
filterFindings(findings) {
|
|
174
|
+
const limits = this.getLimits();
|
|
175
|
+
if (limits.maxFindings === Infinity) return findings;
|
|
176
|
+
return findings.slice(0, limits.maxFindings);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
checkFileLimit(count) {
|
|
180
|
+
const limits = this.getLimits();
|
|
181
|
+
return count <= limits.maxFiles;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
canUseFeature(feature) {
|
|
185
|
+
const limits = this.getLimits();
|
|
186
|
+
return limits[feature] === true;
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Singleton
|
|
191
|
+
let licenseInstance = null;
|
|
192
|
+
|
|
193
|
+
export async function getLicense() {
|
|
194
|
+
if (!licenseInstance) {
|
|
195
|
+
licenseInstance = new License();
|
|
196
|
+
await licenseInstance.load();
|
|
197
|
+
}
|
|
198
|
+
return licenseInstance;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Upgrade prompt
|
|
202
|
+
export function getUpgradePrompt(feature = null) {
|
|
203
|
+
const baseUrl = 'https://agentguard.co/pro';
|
|
204
|
+
|
|
205
|
+
const messages = {
|
|
206
|
+
default: `
|
|
207
|
+
┌─────────────────────────────────────────────────────┐
|
|
208
|
+
│ ⭐ Upgrade to Agent Guard Pro │
|
|
209
|
+
│ │
|
|
210
|
+
│ ✓ Unlimited files & findings │
|
|
211
|
+
│ ✓ All 20+ security rules │
|
|
212
|
+
│ ✓ JSON output for CI/CD │
|
|
213
|
+
│ ✓ Runtime protection proxy │
|
|
214
|
+
│ ✓ Dashboard integration │
|
|
215
|
+
│ │
|
|
216
|
+
│ ${baseUrl} │
|
|
217
|
+
└─────────────────────────────────────────────────────┘`,
|
|
218
|
+
|
|
219
|
+
files: `⚠️ Free tier limited to 50 files. Upgrade for unlimited: ${baseUrl}`,
|
|
220
|
+
findings: `⚠️ Showing 10 of {total} findings. Upgrade to see all: ${baseUrl}`,
|
|
221
|
+
json: `⚠️ JSON output requires Pro license: ${baseUrl}`,
|
|
222
|
+
proxy: `⚠️ Runtime proxy requires Pro license: ${baseUrl}`,
|
|
223
|
+
rules: `ℹ️ Free tier uses 5 basic rules. Pro includes 20+ rules: ${baseUrl}`,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
return messages[feature] || messages.default;
|
|
227
|
+
}
|
package/src/scanner.js
CHANGED
|
@@ -14,6 +14,9 @@ const IGNORE_FILES = [
|
|
|
14
14
|
'.DS_Store', 'package-lock.json', 'yarn.lock', 'pnpm-lock.yaml'
|
|
15
15
|
];
|
|
16
16
|
|
|
17
|
+
// Basic rules available in free tier
|
|
18
|
+
const BASIC_RULE_IDS = ['SEC-001', 'SEC-002', 'SEC-003', 'SEC-004', 'SEC-005'];
|
|
19
|
+
|
|
17
20
|
export class Scanner {
|
|
18
21
|
constructor(options = {}) {
|
|
19
22
|
this.findings = [];
|
|
@@ -21,8 +24,14 @@ export class Scanner {
|
|
|
21
24
|
this.options = {
|
|
22
25
|
maxFileSize: options.maxFileSize || 1024 * 1024, // 1MB
|
|
23
26
|
followSymlinks: options.followSymlinks || false,
|
|
27
|
+
maxFiles: options.maxFiles || Infinity,
|
|
24
28
|
...options
|
|
25
29
|
};
|
|
30
|
+
|
|
31
|
+
// Filter rules based on license
|
|
32
|
+
this.activeRules = options.license?.isPro()
|
|
33
|
+
? rules
|
|
34
|
+
: rules.filter(r => BASIC_RULE_IDS.includes(r.id));
|
|
26
35
|
}
|
|
27
36
|
|
|
28
37
|
async scan(targetPath) {
|
|
@@ -64,6 +73,11 @@ export class Scanner {
|
|
|
64
73
|
}
|
|
65
74
|
|
|
66
75
|
async scanFile(filePath) {
|
|
76
|
+
// Check file limit
|
|
77
|
+
if (this.scannedFiles >= this.options.maxFiles) {
|
|
78
|
+
return; // Hit free tier limit
|
|
79
|
+
}
|
|
80
|
+
|
|
67
81
|
try {
|
|
68
82
|
const stats = await stat(filePath);
|
|
69
83
|
|
|
@@ -76,7 +90,7 @@ export class Scanner {
|
|
|
76
90
|
|
|
77
91
|
this.scannedFiles++;
|
|
78
92
|
|
|
79
|
-
for (const rule of
|
|
93
|
+
for (const rule of this.activeRules) {
|
|
80
94
|
if (!rule.patterns) continue; // Skip non-pattern rules
|
|
81
95
|
|
|
82
96
|
if (!this.matchesFilePattern(fileName, rule.files)) {
|
|
@@ -177,6 +191,12 @@ export class Scanner {
|
|
|
177
191
|
}
|
|
178
192
|
}
|
|
179
193
|
|
|
194
|
+
// Deduplicate: skip if same rule+file+line already recorded
|
|
195
|
+
const isDuplicate = this.findings.some(f =>
|
|
196
|
+
f.rule === rule.id && f.file === filePath && f.line === lineNum
|
|
197
|
+
);
|
|
198
|
+
if (isDuplicate) return;
|
|
199
|
+
|
|
180
200
|
// Redact sensitive values
|
|
181
201
|
const redactedMatch = this.redactSensitive(match);
|
|
182
202
|
|
|
@@ -246,6 +266,19 @@ export class Scanner {
|
|
|
246
266
|
}
|
|
247
267
|
|
|
248
268
|
export async function scan(targetPath, options = {}) {
|
|
269
|
+
// Apply license limits
|
|
270
|
+
if (options.license) {
|
|
271
|
+
const limits = options.license.getLimits();
|
|
272
|
+
options.maxFiles = options.maxFiles || limits.maxFiles;
|
|
273
|
+
}
|
|
274
|
+
|
|
249
275
|
const scanner = new Scanner(options);
|
|
250
|
-
|
|
276
|
+
const report = await scanner.scan(targetPath);
|
|
277
|
+
|
|
278
|
+
// Add metadata for license handling
|
|
279
|
+
report.hitFileLimit = scanner.scannedFiles >= (options.maxFiles || Infinity);
|
|
280
|
+
report.rulesUsed = scanner.activeRules?.length || rules.length;
|
|
281
|
+
report.totalRules = rules.length;
|
|
282
|
+
|
|
283
|
+
return report;
|
|
251
284
|
}
|