@musashimiyamoto/agent-guard 0.2.2 β†’ 0.3.1

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 CHANGED
@@ -52,8 +52,7 @@ Every skill should include `skill.manifest.json`:
52
52
  "metadata": {
53
53
  "name": "my-skill",
54
54
  "version": "1.0.0",
55
- "author": "agent:id",
56
- "isnad_root": "human:owner"
55
+ "author": "developer-name"
57
56
  },
58
57
  "permissions": {
59
58
  "network": {
@@ -73,15 +72,6 @@ Every skill should include `skill.manifest.json`:
73
72
  }
74
73
  ```
75
74
 
76
- ## Trust Tiers (Isnād)
77
-
78
- | Tier | Name | Meaning |
79
- |------|------|---------|
80
- | πŸ₯‡ | Thiqah | Audited by 3+ trusted agents, signed |
81
- | πŸ₯ˆ | Hasan | Reputable author, signed manifest |
82
- | πŸ₯‰ | Da'if | Unsigned/unaudited, sandbox required |
83
- | πŸ’€ | Matruk | Confirmed malicious, blocked |
84
-
85
75
  ## Exit Codes
86
76
 
87
77
  - `0` β€” No critical findings
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@musashimiyamoto/agent-guard",
3
- "version": "0.2.2",
3
+ "version": "0.3.1",
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": {
package/src/cli.js CHANGED
@@ -8,7 +8,7 @@ import { resolve } from 'path';
8
8
 
9
9
  import { createProxy } from './proxy/index.js';
10
10
 
11
- const VERSION = '0.2.2';
11
+ const VERSION = '0.3.1';
12
12
 
13
13
  const FEEDBACK_URL = 'https://github.com/MusashiMiyamoto1-cloud/agent-guard/issues/new';
14
14
  const REPO_URL = 'https://github.com/MusashiMiyamoto1-cloud/agent-guard';
@@ -74,10 +74,13 @@ export const rules = [
74
74
  severity: 'critical',
75
75
  description: 'Finds OpenAI API keys in agent files',
76
76
  patterns: [
77
- /sk-[a-zA-Z0-9]{20,}/g,
78
- /sk-proj-[a-zA-Z0-9]{20,}/g
77
+ /sk-proj-[a-zA-Z0-9_-]{10,}/g,
78
+ /sk-live-[a-zA-Z0-9_-]{10,}/g,
79
+ /sk-test-[a-zA-Z0-9_-]{10,}/g,
80
+ /sk-[a-zA-Z0-9_-]{20,}/g,
81
+ /OPENAI_API_KEY\s*[:=]\s*["']?[a-zA-Z0-9_-]{10,}["']?/g
79
82
  ],
80
- files: ['*.md', '*.yaml', '*.yml', '*.json', '*.txt', 'SOUL.md', 'HEARTBEAT.md']
83
+ files: ['*']
81
84
  },
82
85
  {
83
86
  id: 'SEC-002',
@@ -89,7 +92,7 @@ export const rules = [
89
92
  /gho_[a-zA-Z0-9]{36}/g,
90
93
  /github_pat_[a-zA-Z0-9]{22}_[a-zA-Z0-9]{59}/g
91
94
  ],
92
- files: ['*.md', '*.yaml', '*.yml', '*.json', '*.txt', '.env']
95
+ files: ['*']
93
96
  },
94
97
  {
95
98
  id: 'SEC-003',
@@ -120,11 +123,12 @@ export const rules = [
120
123
  severity: 'high',
121
124
  description: 'Finds generic hardcoded secrets',
122
125
  patterns: [
123
- /api[_-]?key\s*[:=]\s*["'][a-zA-Z0-9]{16,}["']/gi,
124
- /secret\s*[:=]\s*["'][a-zA-Z0-9]{16,}["']/gi,
125
- /token\s*[:=]\s*["'][a-zA-Z0-9]{16,}["']/gi
126
+ /api[_-]?key\s*[:=]\s*["'][a-zA-Z0-9_-]{10,}["']/gi,
127
+ /secret\s*[:=]\s*["'][a-zA-Z0-9_-]{10,}["']/gi,
128
+ /token\s*[:=]\s*["'][a-zA-Z0-9_-]{10,}["']/gi,
129
+ /["']?apiKey["']?\s*[:=]\s*["'][a-zA-Z0-9_-]{10,}["']/gi
126
130
  ],
127
- files: ['*.md', '*.yaml', '*.yml', '*.json', 'SOUL.md']
131
+ files: ['*']
128
132
  },
129
133
 
130
134
  {
@@ -211,9 +215,11 @@ export const rules = [
211
215
  severity: 'critical',
212
216
  description: 'Finds Stripe API keys',
213
217
  patterns: [
214
- /sk_live_[0-9a-zA-Z]{24,}/g,
215
- /sk_test_[0-9a-zA-Z]{24,}/g,
216
- /rk_live_[0-9a-zA-Z]{24,}/g
218
+ /sk_live_[0-9a-zA-Z]{8,}/g,
219
+ /sk_test_[0-9a-zA-Z]{8,}/g,
220
+ /rk_live_[0-9a-zA-Z]{8,}/g,
221
+ /pk_live_[0-9a-zA-Z]{8,}/g,
222
+ /pk_test_[0-9a-zA-Z]{8,}/g
217
223
  ],
218
224
  files: ['*']
219
225
  },
@@ -375,12 +381,57 @@ export const rules = [
375
381
  severity: 'critical',
376
382
  description: 'Tool allows unrestricted filesystem or network access',
377
383
  patterns: [
378
- /allow_all_tools\s*[:=]\s*true/gi,
379
- /tool_restrictions\s*[:=]\s*false/gi,
380
- /sandbox\s*[:=]\s*false/gi,
381
- /unrestricted\s*[:=]\s*true/gi
384
+ /allow_all_tools["']?\s*[:=]\s*true/gi,
385
+ /tool_restrictions["']?\s*[:=]\s*false/gi,
386
+ /["']?sandbox["']?\s*[:=]\s*["']?false["']?/gi,
387
+ /["']?unrestricted["']?\s*[:=]\s*["']?true["']?/gi
388
+ ],
389
+ files: ['*.yaml', '*.yml', '*.json', 'config.*', 'AGENTS.md', 'openclaw.json']
390
+ },
391
+
392
+ // Wildcard Allowlist
393
+ {
394
+ id: 'TOOL-002',
395
+ name: 'Wildcard Allowlist',
396
+ severity: 'critical',
397
+ description: 'Tool or skill has wildcard allowlist permitting all operations',
398
+ patterns: [
399
+ /allowlist["']?\s*[:=]\s*\[\s*["']\*["']\s*\]/gi,
400
+ /allowlist["']?\s*[:=]\s*["']\*["']/gi,
401
+ /allow["']?\s*[:=]\s*\[\s*["']\*["']\s*\]/gi,
402
+ /permissions["']?\s*[:=]\s*["']\*["']/gi
403
+ ],
404
+ files: ['*.yaml', '*.yml', '*.json', 'config.*', 'openclaw.json']
405
+ },
406
+
407
+ // Database URL with embedded credentials
408
+ {
409
+ id: 'SEC-017',
410
+ name: 'Database URL with Credentials',
411
+ severity: 'critical',
412
+ description: 'Database connection string with embedded password',
413
+ patterns: [
414
+ /(?:postgres|mysql|mongodb|redis|amqp)(?:ql)?:\/\/[^:]+:[^@]+@[^\s"']+/gi,
415
+ /DATABASE_URL\s*[:=]\s*["']?[^\s"']+:\/\/[^:]+:[^@]+@/gi
416
+ ],
417
+ files: ['*']
418
+ },
419
+
420
+ // Prompt Injection Vulnerability
421
+ {
422
+ id: 'INJ-002',
423
+ name: 'Prompt Injection Risk',
424
+ severity: 'high',
425
+ description: 'System prompt contains patterns vulnerable to injection attacks',
426
+ patterns: [
427
+ /follow\s+(?:all|any)\s+instructions/gi,
428
+ /do\s+whatever\s+(?:the\s+)?(?:user|they|anyone)\s+asks/gi,
429
+ /obey\s+(?:all|any)\s+(?:commands|instructions|requests)/gi,
430
+ /no\s+restrictions/gi,
431
+ /ignore\s+(?:previous|prior|all)\s+(?:instructions|rules|guidelines)/gi,
432
+ /you\s+have\s+no\s+(?:limits|boundaries|restrictions)/gi
382
433
  ],
383
- files: ['*.yaml', '*.yml', '*.json', 'config.*', 'AGENTS.md']
434
+ files: ['SOUL.md', 'SYSTEM.md', '*.md', 'system_prompt.*']
384
435
  }
385
436
  ];
386
437
 
package/src/scanner.js CHANGED
@@ -105,6 +105,8 @@ export class Scanner {
105
105
  if (pattern.startsWith('*.')) {
106
106
  const ext = pattern.slice(1);
107
107
  if (fileName.endsWith(ext)) return true;
108
+ } else if (pattern === '.env' && (fileName === '.env' || fileName.startsWith('.env.'))) {
109
+ return true;
108
110
  } else if (fileName === pattern) {
109
111
  return true;
110
112
  } else if (pattern.includes('*')) {
@@ -175,6 +177,12 @@ export class Scanner {
175
177
  }
176
178
  }
177
179
 
180
+ // Deduplicate: skip if same rule+file+line already recorded
181
+ const isDuplicate = this.findings.some(f =>
182
+ f.rule === rule.id && f.file === filePath && f.line === lineNum
183
+ );
184
+ if (isDuplicate) return;
185
+
178
186
  // Redact sensitive values
179
187
  const redactedMatch = this.redactSensitive(match);
180
188
 
@@ -207,11 +215,16 @@ export class Scanner {
207
215
  const low = this.findings.filter(f => f.severity === 'low');
208
216
 
209
217
  // Calculate score (0-100)
210
- // Weights: Critical=30, High=20, Medium=5, Low=2
211
- // 1 High = 80 (B), 1 Critical = 70 (C)
218
+ // Weights: Critical=30, High=15, Medium=5, Low=2
219
+ // But multiple criticals compound: 2+ criticals = max 30, 4+ = max 10
220
+ const criticalPenalty = critical.length === 0 ? 0 :
221
+ critical.length === 1 ? 30 :
222
+ critical.length <= 3 ? 30 + (critical.length - 1) * 20 :
223
+ 90 + (critical.length - 3) * 3; // 4+ criticals β†’ near zero
224
+
212
225
  const score = Math.max(0, 100 - (
213
- critical.length * 30 +
214
- high.length * 20 +
226
+ criticalPenalty +
227
+ high.length * 15 +
215
228
  medium.length * 5 +
216
229
  low.length * 2
217
230
  ));