@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@musashimiyamoto/agent-guard",
3
- "version": "0.3.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.3.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] Scan directory for security issues
57
- agent-guard proxy [port] Start runtime protection proxy
58
- agent-guard feedback [msg] Submit feedback or report an issue
59
- agent-guard --help Show this help message
60
- agent-guard --version Show version
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 . Scan current directory
64
- npx agent-guard scan ./my-agent Scan specific agent directory
65
- npx agent-guard proxy Start proxy on port 18800
66
- npx agent-guard proxy 8080 Start proxy on custom port
67
- npx agent-guard feedback Open feedback form
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(report.findings);
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 rules) {
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
- return scanner.scan(targetPath);
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
  }