@musashimiyamoto/agent-guard 0.1.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 ADDED
@@ -0,0 +1,93 @@
1
+ # πŸ›‘ Agent Guard
2
+
3
+ Security scanner for AI agent configurations. Detects misconfigurations, exposed secrets, and unsafe skill patterns.
4
+
5
+ ## Quick Start
6
+
7
+ ```bash
8
+ # Scan current directory
9
+ npx agent-guard scan .
10
+
11
+ # Scan specific agent
12
+ npx agent-guard scan ./my-agent
13
+
14
+ # JSON output for CI/CD
15
+ npx agent-guard scan . --json
16
+ ```
17
+
18
+ ## What It Finds
19
+
20
+ | Category | Examples |
21
+ |----------|----------|
22
+ | **Secrets** | OpenAI keys, GitHub tokens, AWS credentials, private keys |
23
+ | **Network** | Public binds (0.0.0.0), CORS misconfig, exfiltration URLs |
24
+ | **Auth** | Missing authentication, default credentials |
25
+ | **Skills** | Missing manifests, shell execution, eval usage |
26
+ | **Injection** | Hidden unicode characters (RTL attacks) |
27
+
28
+ ## Security Score
29
+
30
+ ```
31
+ Score: 90/100 (A)
32
+ β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘
33
+
34
+ Critical: 0
35
+ High: 1
36
+ Medium: 0
37
+ Low: 0
38
+ ```
39
+
40
+ - **A (90-100)**: Production ready
41
+ - **B (80-89)**: Minor issues
42
+ - **C (70-79)**: Needs attention
43
+ - **D (60-69)**: Significant issues
44
+ - **F (<60)**: Critical vulnerabilities
45
+
46
+ ## Skill Manifests
47
+
48
+ Every skill should include `skill.manifest.json`:
49
+
50
+ ```json
51
+ {
52
+ "metadata": {
53
+ "name": "my-skill",
54
+ "version": "1.0.0",
55
+ "author": "agent:id",
56
+ "isnad_root": "human:owner"
57
+ },
58
+ "permissions": {
59
+ "network": {
60
+ "egress": [{"domain": "api.example.com", "purpose": "core"}],
61
+ "block_all_other": true
62
+ },
63
+ "filesystem": {
64
+ "read": ["./data/"],
65
+ "write": ["./output/"],
66
+ "deny": ["~/.env", "~/.ssh"]
67
+ }
68
+ },
69
+ "runtime_policy": {
70
+ "enforcement": "hard_block",
71
+ "log_violations": true
72
+ }
73
+ }
74
+ ```
75
+
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
+ ## Exit Codes
86
+
87
+ - `0` β€” No critical findings
88
+ - `1` β€” Critical findings detected
89
+ - `2` β€” Scan error
90
+
91
+ ## License
92
+
93
+ MIT
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "@musashimiyamoto/agent-guard",
3
+ "version": "0.1.0",
4
+ "description": "Security scanner for AI agent configurations. Detects misconfigurations, exposed secrets, and unsafe skill patterns.",
5
+ "main": "src/index.js",
6
+ "bin": {
7
+ "agent-guard": "./src/cli.js"
8
+ },
9
+ "scripts": {
10
+ "scan": "node src/cli.js scan",
11
+ "test": "node src/cli.js scan test/fixtures/vulnerable-agent --json"
12
+ },
13
+ "keywords": [
14
+ "ai",
15
+ "agent",
16
+ "security",
17
+ "scanner",
18
+ "openclaw",
19
+ "moltbook",
20
+ "llm",
21
+ "vulnerability",
22
+ "secrets",
23
+ "audit"
24
+ ],
25
+ "author": "Agent Guard <security@agentguard.dev>",
26
+ "license": "MIT",
27
+ "type": "module",
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/agentguard/agent-guard"
34
+ },
35
+ "homepage": "https://agentguard.dev",
36
+ "bugs": {
37
+ "url": "https://github.com/agentguard/agent-guard/issues"
38
+ },
39
+ "files": [
40
+ "src/",
41
+ "README.md"
42
+ ],
43
+ "dependencies": {
44
+ "yaml": "^2.3.0"
45
+ }
46
+ }
package/src/cli.js ADDED
@@ -0,0 +1,252 @@
1
+ #!/usr/bin/env node
2
+
3
+ // Agent Guard CLI
4
+ // Security scanner for AI agent configurations
5
+
6
+ import { scan } from './scanner.js';
7
+ import { resolve } from 'path';
8
+
9
+ import { createProxy } from './proxy/index.js';
10
+
11
+ const VERSION = '0.1.0';
12
+
13
+ const COLORS = {
14
+ reset: '\x1b[0m',
15
+ bold: '\x1b[1m',
16
+ red: '\x1b[31m',
17
+ green: '\x1b[32m',
18
+ yellow: '\x1b[33m',
19
+ blue: '\x1b[34m',
20
+ magenta: '\x1b[35m',
21
+ cyan: '\x1b[36m',
22
+ gray: '\x1b[90m'
23
+ };
24
+
25
+ const SEVERITY_COLORS = {
26
+ critical: COLORS.red,
27
+ high: COLORS.magenta,
28
+ medium: COLORS.yellow,
29
+ low: COLORS.cyan
30
+ };
31
+
32
+ const SEVERITY_ICONS = {
33
+ critical: '🚨',
34
+ high: '⚠️ ',
35
+ medium: 'πŸ“‹',
36
+ low: 'ℹ️ '
37
+ };
38
+
39
+ function printBanner() {
40
+ console.log(`
41
+ ${COLORS.cyan}╔═══════════════════════════════════════════════════╗
42
+ β•‘ β•‘
43
+ β•‘ ${COLORS.bold}πŸ›‘ AGENT GUARD${COLORS.reset}${COLORS.cyan} v${VERSION} β•‘
44
+ β•‘ Security Scanner for AI Agents β•‘
45
+ β•‘ β•‘
46
+ β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•${COLORS.reset}
47
+ `);
48
+ }
49
+
50
+ function printHelp() {
51
+ console.log(`
52
+ ${COLORS.bold}Usage:${COLORS.reset}
53
+ agent-guard scan [path] Scan directory for security issues
54
+ agent-guard proxy [port] Start runtime protection proxy
55
+ agent-guard --help Show this help message
56
+ agent-guard --version Show version
57
+
58
+ ${COLORS.bold}Examples:${COLORS.reset}
59
+ npx agent-guard scan . Scan current directory
60
+ npx agent-guard scan ./my-agent Scan specific agent directory
61
+ npx agent-guard proxy Start proxy on port 18800
62
+ npx agent-guard proxy 8080 Start proxy on custom port
63
+
64
+ ${COLORS.bold}Options:${COLORS.reset}
65
+ --json Output results as JSON
66
+ --quiet Only show findings (no banner)
67
+ --policy <file> Use custom policy file
68
+
69
+ ${COLORS.bold}Environment:${COLORS.reset}
70
+ HTTP_PROXY=http://127.0.0.1:18800 Route agent through proxy
71
+
72
+ ${COLORS.bold}Exit Codes:${COLORS.reset}
73
+ 0 No critical findings
74
+ 1 Critical findings detected
75
+ 2 Error during scan
76
+ `);
77
+ }
78
+
79
+ function printScore(report) {
80
+ const { score, grade } = report;
81
+
82
+ const gradeColor =
83
+ grade === 'A' ? COLORS.green :
84
+ grade === 'B' ? COLORS.cyan :
85
+ grade === 'C' ? COLORS.yellow :
86
+ grade === 'D' ? COLORS.magenta : COLORS.red;
87
+
88
+ const bar = 'β–ˆ'.repeat(Math.floor(score / 5)) + 'β–‘'.repeat(20 - Math.floor(score / 5));
89
+
90
+ console.log(`
91
+ ${COLORS.bold}Security Score:${COLORS.reset} ${gradeColor}${score}/100 (${grade})${COLORS.reset}
92
+
93
+ ${gradeColor}${bar}${COLORS.reset}
94
+
95
+ ${COLORS.bold}Summary:${COLORS.reset}
96
+ ${COLORS.red}Critical:${COLORS.reset} ${report.summary.critical}
97
+ ${COLORS.magenta}High:${COLORS.reset} ${report.summary.high}
98
+ ${COLORS.yellow}Medium:${COLORS.reset} ${report.summary.medium}
99
+ ${COLORS.cyan}Low:${COLORS.reset} ${report.summary.low}
100
+
101
+ Files scanned: ${report.scannedFiles}
102
+ `);
103
+ }
104
+
105
+ function printFindings(findings) {
106
+ if (findings.length === 0) {
107
+ console.log(`${COLORS.green}βœ“ No security issues found!${COLORS.reset}\n`);
108
+ return;
109
+ }
110
+
111
+ console.log(`${COLORS.bold}Findings:${COLORS.reset}\n`);
112
+
113
+ // Group by severity
114
+ const grouped = {
115
+ critical: findings.filter(f => f.severity === 'critical'),
116
+ high: findings.filter(f => f.severity === 'high'),
117
+ medium: findings.filter(f => f.severity === 'medium'),
118
+ low: findings.filter(f => f.severity === 'low')
119
+ };
120
+
121
+ for (const severity of ['critical', 'high', 'medium', 'low']) {
122
+ const items = grouped[severity];
123
+ if (items.length === 0) continue;
124
+
125
+ const color = SEVERITY_COLORS[severity];
126
+ const icon = SEVERITY_ICONS[severity];
127
+
128
+ console.log(`${color}${COLORS.bold}${icon} ${severity.toUpperCase()} (${items.length})${COLORS.reset}\n`);
129
+
130
+ for (const finding of items) {
131
+ const lineInfo = finding.line ? `:${finding.line}` : '';
132
+ console.log(` ${color}[${finding.rule}]${COLORS.reset} ${finding.name}`);
133
+ console.log(` ${COLORS.gray}${finding.file}${lineInfo}${COLORS.reset}`);
134
+ console.log(` ${finding.description}`);
135
+ console.log(` Match: ${COLORS.yellow}${finding.match}${COLORS.reset}`);
136
+ console.log();
137
+ }
138
+ }
139
+ }
140
+
141
+ function printRecommendations(report) {
142
+ if (report.totalFindings === 0) return;
143
+
144
+ console.log(`${COLORS.bold}Recommendations:${COLORS.reset}\n`);
145
+
146
+ const hasSecrets = report.findings.some(f => f.rule.startsWith('SEC-'));
147
+ const hasNetwork = report.findings.some(f => f.rule.startsWith('NET-'));
148
+ const hasAuth = report.findings.some(f => f.rule.startsWith('AUTH-'));
149
+ const hasSkill = report.findings.some(f => f.rule.startsWith('SKILL-'));
150
+
151
+ if (hasSecrets) {
152
+ console.log(` ${COLORS.red}1.${COLORS.reset} Rotate exposed API keys immediately`);
153
+ console.log(` Move secrets to environment variables or a vault\n`);
154
+ }
155
+
156
+ if (hasNetwork) {
157
+ console.log(` ${COLORS.red}2.${COLORS.reset} Fix network exposure`);
158
+ console.log(` Bind to 127.0.0.1 instead of 0.0.0.0`);
159
+ console.log(` Use a reverse proxy with authentication\n`);
160
+ }
161
+
162
+ if (hasAuth) {
163
+ console.log(` ${COLORS.red}3.${COLORS.reset} Enable authentication`);
164
+ console.log(` Configure JWT or API key authentication\n`);
165
+ }
166
+
167
+ if (hasSkill) {
168
+ console.log(` ${COLORS.yellow}4.${COLORS.reset} Review skill permissions`);
169
+ console.log(` Add skill.manifest.json with explicit permissions`);
170
+ console.log(` Audit skills using shell execution\n`);
171
+ }
172
+ }
173
+
174
+ async function main() {
175
+ const args = process.argv.slice(2);
176
+
177
+ const jsonOutput = args.includes('--json');
178
+ const quiet = args.includes('--quiet');
179
+ const filteredArgs = args.filter(a => !a.startsWith('--'));
180
+
181
+ if (args.includes('--help') || args.includes('-h')) {
182
+ printBanner();
183
+ printHelp();
184
+ process.exit(0);
185
+ }
186
+
187
+ if (args.includes('--version') || args.includes('-v')) {
188
+ console.log(`agent-guard v${VERSION}`);
189
+ process.exit(0);
190
+ }
191
+
192
+ const command = filteredArgs[0];
193
+
194
+ if (command === 'proxy') {
195
+ const port = parseInt(filteredArgs[1]) || 18800;
196
+ if (!quiet) printBanner();
197
+ console.log(`${COLORS.cyan}Starting runtime protection proxy...${COLORS.reset}\n`);
198
+
199
+ try {
200
+ const proxy = createProxy({ port });
201
+ proxy.start();
202
+
203
+ console.log(`${COLORS.green}Proxy ready.${COLORS.reset}`);
204
+ console.log(`${COLORS.gray}Set HTTP_PROXY=http://127.0.0.1:${port} to route agent traffic${COLORS.reset}\n`);
205
+
206
+ // Keep running
207
+ process.on('SIGINT', () => {
208
+ console.log(`\n${COLORS.yellow}Shutting down...${COLORS.reset}`);
209
+ proxy.stop();
210
+ process.exit(0);
211
+ });
212
+ } catch (err) {
213
+ console.error(`${COLORS.red}Error: ${err.message}${COLORS.reset}`);
214
+ process.exit(2);
215
+ }
216
+ return;
217
+ }
218
+
219
+ if (command !== 'scan') {
220
+ if (!quiet) printBanner();
221
+ printHelp();
222
+ process.exit(0);
223
+ }
224
+
225
+ const targetPath = resolve(filteredArgs[1] || '.');
226
+
227
+ if (!quiet && !jsonOutput) {
228
+ printBanner();
229
+ console.log(`${COLORS.gray}Scanning: ${targetPath}${COLORS.reset}\n`);
230
+ }
231
+
232
+ try {
233
+ const report = await scan(targetPath);
234
+
235
+ if (jsonOutput) {
236
+ console.log(JSON.stringify(report, null, 2));
237
+ } else {
238
+ printScore(report);
239
+ printFindings(report.findings);
240
+ printRecommendations(report);
241
+ }
242
+
243
+ // Exit with 1 if critical findings
244
+ process.exit(report.summary.critical > 0 ? 1 : 0);
245
+
246
+ } catch (err) {
247
+ console.error(`${COLORS.red}Error: ${err.message}${COLORS.reset}`);
248
+ process.exit(2);
249
+ }
250
+ }
251
+
252
+ main();
@@ -0,0 +1,112 @@
1
+ // Egress Control Module
2
+ // Checks if outbound requests are allowed by policy
3
+
4
+ // Known malicious/exfiltration domains
5
+ const BLOCKED_DOMAINS = [
6
+ 'webhook.site',
7
+ 'requestbin.com',
8
+ 'ngrok.io',
9
+ 'pipedream.net',
10
+ 'hookbin.com',
11
+ 'burpcollaborator.net',
12
+ 'oastify.com',
13
+ 'interact.sh'
14
+ ];
15
+
16
+ // Default allowed domains (LLM APIs)
17
+ const DEFAULT_ALLOWED = [
18
+ 'api.openai.com',
19
+ 'api.anthropic.com',
20
+ 'generativelanguage.googleapis.com',
21
+ 'api.mistral.ai',
22
+ 'api.cohere.ai',
23
+ 'api.together.xyz',
24
+ 'openrouter.ai'
25
+ ];
26
+
27
+ export function checkEgress(url, policy = {}) {
28
+ const hostname = url.hostname.toLowerCase();
29
+
30
+ // Check blocked domains first (always blocked)
31
+ for (const blocked of BLOCKED_DOMAINS) {
32
+ if (hostname === blocked || hostname.endsWith('.' + blocked)) {
33
+ return {
34
+ allowed: false,
35
+ reason: `Domain "${blocked}" is on the blocklist (known exfiltration endpoint)`
36
+ };
37
+ }
38
+ }
39
+
40
+ // Check policy-specific blocks
41
+ if (policy.egress?.rules) {
42
+ for (const rule of policy.egress.rules) {
43
+ if (matchDomain(hostname, rule.domain || rule.pattern)) {
44
+ if (rule.allow === false) {
45
+ return {
46
+ allowed: false,
47
+ reason: rule.reason || `Blocked by policy rule: ${rule.pattern || rule.domain}`
48
+ };
49
+ }
50
+ if (rule.allow === true) {
51
+ return {
52
+ allowed: true,
53
+ reason: rule.purpose || 'Allowed by policy'
54
+ };
55
+ }
56
+ }
57
+ }
58
+ }
59
+
60
+ // Check default allowed
61
+ for (const allowed of DEFAULT_ALLOWED) {
62
+ if (hostname === allowed || hostname.endsWith('.' + allowed)) {
63
+ return {
64
+ allowed: true,
65
+ reason: 'Default allowed (LLM API)'
66
+ };
67
+ }
68
+ }
69
+
70
+ // Check policy default action
71
+ const defaultAction = policy.egress?.default || 'deny';
72
+
73
+ if (defaultAction === 'allow') {
74
+ return {
75
+ allowed: true,
76
+ reason: 'Allowed by default policy'
77
+ };
78
+ }
79
+
80
+ return {
81
+ allowed: false,
82
+ reason: 'Not in allowlist (default deny)'
83
+ };
84
+ }
85
+
86
+ function matchDomain(hostname, pattern) {
87
+ if (!pattern) return false;
88
+
89
+ // Exact match
90
+ if (hostname === pattern) return true;
91
+
92
+ // Wildcard match (*.example.com)
93
+ if (pattern.startsWith('*.')) {
94
+ const suffix = pattern.slice(2);
95
+ return hostname === suffix || hostname.endsWith('.' + suffix);
96
+ }
97
+
98
+ // Subdomain match (matches example.com and *.example.com)
99
+ return hostname.endsWith('.' + pattern);
100
+ }
101
+
102
+ export function addToBlocklist(domain) {
103
+ if (!BLOCKED_DOMAINS.includes(domain)) {
104
+ BLOCKED_DOMAINS.push(domain);
105
+ }
106
+ }
107
+
108
+ export function addToAllowlist(domain) {
109
+ if (!DEFAULT_ALLOWED.includes(domain)) {
110
+ DEFAULT_ALLOWED.push(domain);
111
+ }
112
+ }
@@ -0,0 +1,132 @@
1
+ // Agent Guard Runtime Proxy
2
+ // Phase 2: Inference-level protection
3
+
4
+ import { createServer } from 'http';
5
+ import { request as httpRequest } from 'http';
6
+ import { request as httpsRequest } from 'https';
7
+ import { URL } from 'url';
8
+ import { loadPolicy } from './policy.js';
9
+ import { checkEgress } from './egress.js';
10
+ import { logEvent } from './logger.js';
11
+
12
+ const DEFAULT_PORT = 18800;
13
+
14
+ export class AgentGuardProxy {
15
+ constructor(options = {}) {
16
+ this.port = options.port || DEFAULT_PORT;
17
+ this.policy = options.policy || loadPolicy();
18
+ this.server = null;
19
+ }
20
+
21
+ start() {
22
+ this.server = createServer((req, res) => {
23
+ this.handleRequest(req, res);
24
+ });
25
+
26
+ this.server.listen(this.port, '127.0.0.1', () => {
27
+ console.log(`πŸ›‘ Agent Guard Proxy listening on http://127.0.0.1:${this.port}`);
28
+ });
29
+
30
+ return this;
31
+ }
32
+
33
+ stop() {
34
+ if (this.server) {
35
+ this.server.close();
36
+ }
37
+ }
38
+
39
+ async handleRequest(clientReq, clientRes) {
40
+ const startTime = Date.now();
41
+
42
+ try {
43
+ // Parse target URL from proxy request
44
+ const targetUrl = new URL(clientReq.url);
45
+
46
+ // Check egress policy
47
+ const egressResult = checkEgress(targetUrl, this.policy);
48
+
49
+ if (!egressResult.allowed) {
50
+ logEvent({
51
+ type: 'egress_blocked',
52
+ target: targetUrl.hostname,
53
+ reason: egressResult.reason,
54
+ violation: true
55
+ });
56
+
57
+ clientRes.writeHead(403, { 'Content-Type': 'application/json' });
58
+ clientRes.end(JSON.stringify({
59
+ error: 'EGRESS_DENIED',
60
+ message: `Access to ${targetUrl.hostname} blocked by security policy`,
61
+ reason: egressResult.reason
62
+ }));
63
+ return;
64
+ }
65
+
66
+ logEvent({
67
+ type: 'egress_allowed',
68
+ target: targetUrl.hostname,
69
+ violation: false
70
+ });
71
+
72
+ // Forward request to target
73
+ const response = await this.forwardRequest(clientReq, targetUrl);
74
+
75
+ // Log response (for intent verification later)
76
+ const latency = Date.now() - startTime;
77
+ logEvent({
78
+ type: 'request_complete',
79
+ target: targetUrl.hostname,
80
+ status: response.statusCode,
81
+ latency
82
+ });
83
+
84
+ // Forward response to client
85
+ clientRes.writeHead(response.statusCode, response.headers);
86
+ response.pipe(clientRes);
87
+
88
+ } catch (err) {
89
+ logEvent({
90
+ type: 'proxy_error',
91
+ error: err.message,
92
+ violation: false
93
+ });
94
+
95
+ clientRes.writeHead(502, { 'Content-Type': 'application/json' });
96
+ clientRes.end(JSON.stringify({
97
+ error: 'PROXY_ERROR',
98
+ message: err.message
99
+ }));
100
+ }
101
+ }
102
+
103
+ forwardRequest(clientReq, targetUrl) {
104
+ return new Promise((resolve, reject) => {
105
+ const requestFn = targetUrl.protocol === 'https:' ? httpsRequest : httpRequest;
106
+
107
+ const options = {
108
+ hostname: targetUrl.hostname,
109
+ port: targetUrl.port || (targetUrl.protocol === 'https:' ? 443 : 80),
110
+ path: targetUrl.pathname + targetUrl.search,
111
+ method: clientReq.method,
112
+ headers: {
113
+ ...clientReq.headers,
114
+ host: targetUrl.host
115
+ }
116
+ };
117
+
118
+ const proxyReq = requestFn(options, (proxyRes) => {
119
+ resolve(proxyRes);
120
+ });
121
+
122
+ proxyReq.on('error', reject);
123
+
124
+ // Forward request body
125
+ clientReq.pipe(proxyReq);
126
+ });
127
+ }
128
+ }
129
+
130
+ export function createProxy(options) {
131
+ return new AgentGuardProxy(options);
132
+ }