agent-security-lens 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/.env.example +10 -0
- package/.mcp/server.json +42 -0
- package/CHANGELOG.md +17 -0
- package/LICENSE +17 -0
- package/PRIVACY.md +37 -0
- package/README.md +150 -0
- package/RELEASE-MANIFEST.json +449 -0
- package/SECURITY.md +24 -0
- package/apps/mcp-server/agent-security-lens-mcp.mjs +441 -0
- package/bin/agent-security-lens.mjs +117 -0
- package/data/ecosystems/agent-candidates.json +230 -0
- package/data/intelligence/components.json +22989 -0
- package/data/intelligence/security-evaluation-standard.json +221 -0
- package/data/recommendations/core/recommendations.json +256 -0
- package/data/trust/signal-taxonomy.json +107 -0
- package/docs/asl-agent-component-safety-standard-v0.2.md +56 -0
- package/examples/dot-hermes/.hermes/config.json +17 -0
- package/examples/dot-openclaw/.openclaw/openclaw.json +17 -0
- package/examples/hermes-like/.env.example +2 -0
- package/examples/hermes-like/config.json +37 -0
- package/examples/hermes-like/optional-mcps/github-tools.json +8 -0
- package/examples/hermes-like/skills/openclaw-imports/browser-skill/SKILL.md +8 -0
- package/examples/openclaw-like/.env.example +2 -0
- package/examples/openclaw-like/AGENTS.md +7 -0
- package/examples/openclaw-like/openclaw.json +28 -0
- package/examples/openclaw-like/workspace/skills/browser-control/SKILL.md +8 -0
- package/llms.txt +25 -0
- package/package.json +50 -0
- package/profiles/generic-agent/profile.json +19 -0
- package/profiles/hermes-like/profile.json +23 -0
- package/profiles/mcp-server/profile.json +18 -0
- package/profiles/openclaw-like/profile.json +22 -0
- package/profiles/skill-runtime/profile.json +19 -0
- package/rule-packs/core/rules.json +82 -0
- package/rule-packs/hermes/rules.json +44 -0
- package/rule-packs/mcp/rules.json +65 -0
- package/rule-packs/openclaw/rules.json +46 -0
- package/rule-packs/skills/rules.json +45 -0
- package/schemas/agent-install-decision.schema.json +432 -0
- package/schemas/agent-usage-event.schema.json +45 -0
- package/schemas/assessment-result.schema.json +361 -0
- package/schemas/comparison-result.schema.json +113 -0
- package/schemas/component-alternative-graph.schema.json +187 -0
- package/schemas/component-intelligence.schema.json +93 -0
- package/schemas/decision-feedback.schema.json +49 -0
- package/schemas/ecosystem-candidate-registry.schema.json +98 -0
- package/schemas/profile.schema.json +65 -0
- package/schemas/recommendation-pack.schema.json +114 -0
- package/schemas/rule-pack.schema.json +113 -0
- package/schemas/trust-signal-taxonomy.schema.json +68 -0
- package/scripts/verify-examples.mjs +121 -0
- package/scripts/verify-mcp-server.mjs +278 -0
- package/scripts/verify-registry.mjs +264 -0
- package/server.json +42 -0
- package/src/assessment/assess.mjs +108 -0
- package/src/assessment/discover-targets.mjs +127 -0
- package/src/assessment/risk-domains.mjs +83 -0
- package/src/assessment/summarize.mjs +57 -0
- package/src/core/files.mjs +74 -0
- package/src/intelligence/cloud-client.mjs +260 -0
- package/src/intelligence/component-intelligence.mjs +358 -0
- package/src/intelligence/decision-engine.mjs +772 -0
- package/src/intelligence/finding-context.mjs +180 -0
- package/src/intelligence/safety-score-v0.2.mjs +294 -0
- package/src/observations/json-observations.mjs +211 -0
- package/src/observations/observation-rules.mjs +157 -0
- package/src/profiles/load-profiles.mjs +130 -0
- package/src/recommendations/component-alternative-graph.mjs +94 -0
- package/src/recommendations/load-recommendations.mjs +17 -0
- package/src/recommendations/match-recommendations.mjs +79 -0
- package/src/report/comparison-console.mjs +71 -0
- package/src/report/console.mjs +103 -0
- package/src/report/markdown.mjs +145 -0
- package/src/results/compare-results.mjs +106 -0
- package/src/results/save-result.mjs +29 -0
- package/src/rules/load-rules.mjs +22 -0
- package/src/rules/match-rules.mjs +99 -0
- package/src/rules/supersedes.mjs +39 -0
- package/src/store/assessment-store.mjs +78 -0
- package/src/trust/derive-trust-signals.mjs +73 -0
- package/src/trust/load-trust-signals.mjs +17 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { assess } from "../../src/assessment/assess.mjs";
|
|
4
|
+
import { discoverTargets } from "../../src/assessment/discover-targets.mjs";
|
|
5
|
+
import { apiEndpoints, cloudIntelligenceEnabled, queryCloudStatus } from "../../src/intelligence/cloud-client.mjs";
|
|
6
|
+
import {
|
|
7
|
+
getResearchStatus,
|
|
8
|
+
recommendAlternatives,
|
|
9
|
+
recordUsageEvent,
|
|
10
|
+
reviewBeforeInstall,
|
|
11
|
+
submitDecisionFeedback,
|
|
12
|
+
submitUnknownComponent
|
|
13
|
+
} from "../../src/intelligence/component-intelligence.mjs";
|
|
14
|
+
|
|
15
|
+
const serverInfo = {
|
|
16
|
+
name: "agent-security-lens-mcp",
|
|
17
|
+
version: "0.1.0"
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const tools = [
|
|
21
|
+
{
|
|
22
|
+
name: "get_install_policy",
|
|
23
|
+
description:
|
|
24
|
+
"Return the ASL machine-readable policy an autonomous agent should follow before installing MCPs, Skills, tools, prompt bundles, workflows or memory components.",
|
|
25
|
+
inputSchema: {
|
|
26
|
+
type: "object",
|
|
27
|
+
properties: {},
|
|
28
|
+
additionalProperties: false
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
name: "get_intelligence_status",
|
|
33
|
+
description:
|
|
34
|
+
"Return whether ASL MCP is using online cloud intelligence or local fallback, and whether the cloud API is reachable.",
|
|
35
|
+
inputSchema: {
|
|
36
|
+
type: "object",
|
|
37
|
+
properties: {},
|
|
38
|
+
additionalProperties: false
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
name: "review_before_install",
|
|
43
|
+
description:
|
|
44
|
+
"Query ASL intelligence before an autonomous agent installs or enables an MCP, Skill, tool, prompt bundle, workflow, memory component or install command.",
|
|
45
|
+
inputSchema: {
|
|
46
|
+
type: "object",
|
|
47
|
+
properties: {
|
|
48
|
+
component_name: { type: "string", description: "Component name, package name or display name." },
|
|
49
|
+
component_type: {
|
|
50
|
+
type: "string",
|
|
51
|
+
enum: ["agent", "agent-framework", "mcp", "skill", "tool", "prompt", "prompt-bundle", "workflow", "memory", "unknown"]
|
|
52
|
+
},
|
|
53
|
+
source_url: { type: "string", description: "GitHub, registry, npm, Docker or documentation URL." },
|
|
54
|
+
install_command: { type: "string", description: "Command the agent plans to run, such as npx/uvx/docker." },
|
|
55
|
+
manifest: { type: ["object", "string"], description: "MCP/Skill manifest or config snippet." },
|
|
56
|
+
planned_use: { type: "string", description: "What the agent wants to use this component for." },
|
|
57
|
+
requested_permissions: {
|
|
58
|
+
type: "array",
|
|
59
|
+
items: { type: "string" },
|
|
60
|
+
description: "Permissions the agent expects this component to need, such as shell, filesystem, network or browser."
|
|
61
|
+
},
|
|
62
|
+
submit_if_unknown: { type: "boolean", description: "Queue unknown components for ASL research." }
|
|
63
|
+
},
|
|
64
|
+
additionalProperties: true
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
name: "check_component",
|
|
69
|
+
description: "Check ASL online intelligence for a component and return an agent-readable install decision.",
|
|
70
|
+
inputSchema: {
|
|
71
|
+
type: "object",
|
|
72
|
+
properties: {
|
|
73
|
+
component_name: { type: "string" },
|
|
74
|
+
component_type: { type: "string" },
|
|
75
|
+
source_url: { type: "string" },
|
|
76
|
+
install_command: { type: "string" },
|
|
77
|
+
manifest: { type: ["object", "string"] }
|
|
78
|
+
},
|
|
79
|
+
additionalProperties: true
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
name: "recommend_alternatives",
|
|
84
|
+
description: "Return safer alternatives and a safe install plan for a component.",
|
|
85
|
+
inputSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
component_name: { type: "string" },
|
|
89
|
+
component_type: { type: "string" },
|
|
90
|
+
source_url: { type: "string" },
|
|
91
|
+
install_command: { type: "string" },
|
|
92
|
+
manifest: { type: ["object", "string"] }
|
|
93
|
+
},
|
|
94
|
+
additionalProperties: true
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
name: "submit_unknown_component",
|
|
99
|
+
description:
|
|
100
|
+
"Submit public metadata for an unknown component to the ASL research queue. Do not submit private file contents or secrets.",
|
|
101
|
+
inputSchema: {
|
|
102
|
+
type: "object",
|
|
103
|
+
properties: {
|
|
104
|
+
component_name: { type: "string" },
|
|
105
|
+
component_type: { type: "string" },
|
|
106
|
+
source_url: { type: "string" },
|
|
107
|
+
install_command: { type: "string" },
|
|
108
|
+
manifest: { type: ["object", "string"] }
|
|
109
|
+
},
|
|
110
|
+
additionalProperties: true
|
|
111
|
+
}
|
|
112
|
+
},
|
|
113
|
+
{
|
|
114
|
+
name: "report_install_outcome",
|
|
115
|
+
description:
|
|
116
|
+
"Report what happened after an agent followed an ASL decision. Use this to improve ASL adoption metrics and recommendation quality.",
|
|
117
|
+
inputSchema: {
|
|
118
|
+
type: "object",
|
|
119
|
+
properties: {
|
|
120
|
+
component_name: { type: "string" },
|
|
121
|
+
component_type: { type: "string" },
|
|
122
|
+
source_url: { type: "string" },
|
|
123
|
+
decision: { type: "string", enum: ["allow", "allow_with_restrictions", "ask_user", "avoid", "unknown"] },
|
|
124
|
+
outcome: {
|
|
125
|
+
type: "string",
|
|
126
|
+
enum: ["installed", "blocked", "user_approved", "user_rejected", "restriction_applied", "failed", "skipped"]
|
|
127
|
+
},
|
|
128
|
+
restriction_applied: { type: "boolean" },
|
|
129
|
+
alternative_used: { type: "string" },
|
|
130
|
+
error_summary: { type: "string" }
|
|
131
|
+
},
|
|
132
|
+
additionalProperties: true
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
name: "submit_decision_feedback",
|
|
137
|
+
description:
|
|
138
|
+
"Submit feedback about whether an ASL decision was useful, wrong, missing an alternative, or too strict. Do not include secrets or private code.",
|
|
139
|
+
inputSchema: {
|
|
140
|
+
type: "object",
|
|
141
|
+
properties: {
|
|
142
|
+
component_name: { type: "string" },
|
|
143
|
+
component_type: { type: "string" },
|
|
144
|
+
decision: { type: "string" },
|
|
145
|
+
feedback_type: {
|
|
146
|
+
type: "string",
|
|
147
|
+
enum: ["helpful", "too_strict", "too_permissive", "missing_component", "missing_alternative", "incorrect_risk", "other"]
|
|
148
|
+
},
|
|
149
|
+
rating: { type: "number", minimum: 1, maximum: 5 },
|
|
150
|
+
comment: { type: "string" }
|
|
151
|
+
},
|
|
152
|
+
additionalProperties: true
|
|
153
|
+
}
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
name: "get_research_status",
|
|
157
|
+
description:
|
|
158
|
+
"Check whether an unknown component submitted to ASL has been collected, scanned, reviewed, published, archived or is still pending.",
|
|
159
|
+
inputSchema: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
submission_id: { type: "string" }
|
|
163
|
+
},
|
|
164
|
+
required: ["submission_id"],
|
|
165
|
+
additionalProperties: false
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
name: "discover_workspace",
|
|
170
|
+
description: "Discover Agent, MCP and Skill environments under a workspace path.",
|
|
171
|
+
inputSchema: {
|
|
172
|
+
type: "object",
|
|
173
|
+
properties: {
|
|
174
|
+
workspace_path: { type: "string" },
|
|
175
|
+
max_depth: { type: "number" }
|
|
176
|
+
},
|
|
177
|
+
required: ["workspace_path"],
|
|
178
|
+
additionalProperties: false
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
{
|
|
182
|
+
name: "scan_workspace",
|
|
183
|
+
description:
|
|
184
|
+
"Discover and assess every Agent, MCP and Skill environment under a workspace path. Use this for local context only.",
|
|
185
|
+
inputSchema: {
|
|
186
|
+
type: "object",
|
|
187
|
+
properties: {
|
|
188
|
+
workspace_path: { type: "string" },
|
|
189
|
+
max_depth: { type: "number" }
|
|
190
|
+
},
|
|
191
|
+
required: ["workspace_path"],
|
|
192
|
+
additionalProperties: false
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
];
|
|
196
|
+
|
|
197
|
+
function writeMessage(message) {
|
|
198
|
+
process.stdout.write(`${JSON.stringify(message)}\n`);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function toolResult(data) {
|
|
202
|
+
return {
|
|
203
|
+
content: [
|
|
204
|
+
{
|
|
205
|
+
type: "text",
|
|
206
|
+
text: JSON.stringify(data, null, 2)
|
|
207
|
+
}
|
|
208
|
+
]
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
async function callTool(name, args = {}) {
|
|
213
|
+
if (name === "get_install_policy") {
|
|
214
|
+
return toolResult({
|
|
215
|
+
schema_version: "0.1.0",
|
|
216
|
+
service: "AgentSecurityLens",
|
|
217
|
+
result_type: "agent_install_policy",
|
|
218
|
+
policy: {
|
|
219
|
+
protocol: {
|
|
220
|
+
decision_schema: "https://agentsecuritylens.dev/schemas/agent-install-decision.schema.json",
|
|
221
|
+
decision_contract_version: "asl-agent-decision-contract@0.2.0",
|
|
222
|
+
primary_tool: "review_before_install",
|
|
223
|
+
lifecycle_tools: [
|
|
224
|
+
"review_before_install",
|
|
225
|
+
"submit_unknown_component",
|
|
226
|
+
"get_research_status",
|
|
227
|
+
"report_install_outcome",
|
|
228
|
+
"submit_decision_feedback"
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
before_install: [
|
|
232
|
+
"Call review_before_install before installing or enabling any MCP, Skill, tool, prompt bundle, workflow or memory component.",
|
|
233
|
+
"Read agent_decision_contract. If blocks_install is true, do not run the install command automatically.",
|
|
234
|
+
"Do not install automatically when decision is ask_user or avoid.",
|
|
235
|
+
"Apply every safe_install_plan step before installation when decision is allow_with_restrictions.",
|
|
236
|
+
"Submit unknown public components for ASL review without sending private code, secrets, tokens, cookies or personal data.",
|
|
237
|
+
"Prefer recommended_alternatives when decision is avoid or the user rejects the requested component."
|
|
238
|
+
],
|
|
239
|
+
after_install: [
|
|
240
|
+
"Call report_install_outcome after the agent installs, blocks, skips or fails to install a component.",
|
|
241
|
+
"Call submit_decision_feedback when the decision was helpful, too strict, too permissive, missing an alternative or incorrect.",
|
|
242
|
+
"Call get_research_status before retrying an unknown component that was previously submitted."
|
|
243
|
+
],
|
|
244
|
+
expected_component_metadata: [
|
|
245
|
+
"component_name",
|
|
246
|
+
"component_type",
|
|
247
|
+
"source_url",
|
|
248
|
+
"install_command",
|
|
249
|
+
"manifest"
|
|
250
|
+
],
|
|
251
|
+
decisions: {
|
|
252
|
+
allow: "Install after recording exact source and version.",
|
|
253
|
+
allow_with_restrictions: "Apply restrictions first, then install.",
|
|
254
|
+
ask_user: "Ask the user before installation.",
|
|
255
|
+
avoid: "Do not install automatically."
|
|
256
|
+
},
|
|
257
|
+
machine_contract_fields: {
|
|
258
|
+
agent_decision_contract: "Boolean policy contract for automatic install, user confirmation, unknown submission, retry and reporting.",
|
|
259
|
+
agent_actions: "Ordered action list the agent should execute around installation.",
|
|
260
|
+
one_step_action: "Single highest-priority action for agents that need a compact answer.",
|
|
261
|
+
recommended_alternatives: "Structured alternative choices. Review each alternative with ASL before installing it.",
|
|
262
|
+
unknown_component: "Submission and retry instructions when ASL has no reviewed intelligence record."
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
if (name === "get_intelligence_status") {
|
|
268
|
+
const onlineMode = cloudIntelligenceEnabled();
|
|
269
|
+
const cloud = onlineMode ? await queryCloudStatus() : null;
|
|
270
|
+
return toolResult({
|
|
271
|
+
schema_version: "0.1.0",
|
|
272
|
+
service: "AgentSecurityLens",
|
|
273
|
+
result_type: "intelligence_status",
|
|
274
|
+
mode: onlineMode ? "online" : "local",
|
|
275
|
+
configured_api_endpoints: apiEndpoints(),
|
|
276
|
+
cloud: cloud
|
|
277
|
+
? {
|
|
278
|
+
reachable: cloud.ok,
|
|
279
|
+
api_url: cloud.api_url,
|
|
280
|
+
attempted_api_urls: cloud.attempted_api_urls || [],
|
|
281
|
+
fallback_used: cloud.fallback_used || false,
|
|
282
|
+
reason: cloud.reason || null,
|
|
283
|
+
status: cloud.data || null
|
|
284
|
+
}
|
|
285
|
+
: null,
|
|
286
|
+
agent_instruction: onlineMode
|
|
287
|
+
? "Use review_before_install before installing components. If cloud is unreachable, treat local fallback decisions as provisional."
|
|
288
|
+
: "ASL is in local mode. Treat decisions as offline fallback and do not assume full ASL intelligence coverage."
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
if (name === "review_before_install") {
|
|
292
|
+
const review = await reviewBeforeInstall(args);
|
|
293
|
+
if (
|
|
294
|
+
args.submit_if_unknown &&
|
|
295
|
+
(review.unknown_component?.should_submit || (review.component?.cataloged && !review.component?.reviewed))
|
|
296
|
+
) {
|
|
297
|
+
const submission = await submitUnknownComponent(args);
|
|
298
|
+
if (review.unknown_component) {
|
|
299
|
+
review.unknown_component.submission = submission;
|
|
300
|
+
} else {
|
|
301
|
+
review.catalog_research_submission = submission;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return toolResult(review);
|
|
305
|
+
}
|
|
306
|
+
if (name === "check_component") {
|
|
307
|
+
return toolResult(await reviewBeforeInstall(args));
|
|
308
|
+
}
|
|
309
|
+
if (name === "recommend_alternatives") {
|
|
310
|
+
return toolResult(await recommendAlternatives(args));
|
|
311
|
+
}
|
|
312
|
+
if (name === "submit_unknown_component") {
|
|
313
|
+
return toolResult(await submitUnknownComponent(args));
|
|
314
|
+
}
|
|
315
|
+
if (name === "report_install_outcome") {
|
|
316
|
+
return toolResult(
|
|
317
|
+
await recordUsageEvent({
|
|
318
|
+
event_type: "install_outcome",
|
|
319
|
+
recorded_at: new Date().toISOString(),
|
|
320
|
+
...args
|
|
321
|
+
})
|
|
322
|
+
);
|
|
323
|
+
}
|
|
324
|
+
if (name === "submit_decision_feedback") {
|
|
325
|
+
return toolResult(
|
|
326
|
+
await submitDecisionFeedback({
|
|
327
|
+
feedback_type: args.feedback_type || "other",
|
|
328
|
+
recorded_at: new Date().toISOString(),
|
|
329
|
+
...args
|
|
330
|
+
})
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
if (name === "get_research_status") {
|
|
334
|
+
return toolResult(await getResearchStatus(args.submission_id));
|
|
335
|
+
}
|
|
336
|
+
if (name === "discover_workspace") {
|
|
337
|
+
return toolResult(
|
|
338
|
+
await discoverTargets({
|
|
339
|
+
workspacePath: args.workspace_path,
|
|
340
|
+
maxDepth: Number(args.max_depth || 4)
|
|
341
|
+
})
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
if (name === "scan_workspace") {
|
|
345
|
+
const discovery = await discoverTargets({
|
|
346
|
+
workspacePath: args.workspace_path,
|
|
347
|
+
maxDepth: Number(args.max_depth || 4)
|
|
348
|
+
});
|
|
349
|
+
const results = [];
|
|
350
|
+
for (const target of discovery.targets) {
|
|
351
|
+
const result = await assess({ targetPath: target.path, requestedProfile: null });
|
|
352
|
+
results.push({
|
|
353
|
+
target,
|
|
354
|
+
decision_summary: {
|
|
355
|
+
trust_score: result.summary.trust_score,
|
|
356
|
+
total_findings: result.summary.total_findings,
|
|
357
|
+
selected_profile: result.lineage.profile_selection.selected_profile,
|
|
358
|
+
top_risks: result.findings.slice(0, 5).map((finding) => ({
|
|
359
|
+
title: finding.title,
|
|
360
|
+
severity: finding.severity,
|
|
361
|
+
category: finding.category
|
|
362
|
+
}))
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
return toolResult({
|
|
367
|
+
workspace_path: discovery.workspace_path,
|
|
368
|
+
targets_found: discovery.targets.length,
|
|
369
|
+
targets_assessed: results.length,
|
|
370
|
+
results
|
|
371
|
+
});
|
|
372
|
+
}
|
|
373
|
+
throw new Error(`Unknown tool: ${name}`);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
async function handleRequest(message) {
|
|
377
|
+
if (message.method === "initialize") {
|
|
378
|
+
return {
|
|
379
|
+
jsonrpc: "2.0",
|
|
380
|
+
id: message.id,
|
|
381
|
+
result: {
|
|
382
|
+
protocolVersion: message.params?.protocolVersion || "2024-11-05",
|
|
383
|
+
capabilities: {
|
|
384
|
+
tools: {}
|
|
385
|
+
},
|
|
386
|
+
serverInfo
|
|
387
|
+
}
|
|
388
|
+
};
|
|
389
|
+
}
|
|
390
|
+
if (message.method === "tools/list") {
|
|
391
|
+
return {
|
|
392
|
+
jsonrpc: "2.0",
|
|
393
|
+
id: message.id,
|
|
394
|
+
result: { tools }
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
if (message.method === "tools/call") {
|
|
398
|
+
const result = await callTool(message.params?.name, message.params?.arguments || {});
|
|
399
|
+
return {
|
|
400
|
+
jsonrpc: "2.0",
|
|
401
|
+
id: message.id,
|
|
402
|
+
result
|
|
403
|
+
};
|
|
404
|
+
}
|
|
405
|
+
if (message.method?.startsWith("notifications/")) return null;
|
|
406
|
+
return {
|
|
407
|
+
jsonrpc: "2.0",
|
|
408
|
+
id: message.id,
|
|
409
|
+
error: {
|
|
410
|
+
code: -32601,
|
|
411
|
+
message: `Method not found: ${message.method}`
|
|
412
|
+
}
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
let buffer = "";
|
|
417
|
+
process.stdin.setEncoding("utf8");
|
|
418
|
+
process.stdin.on("data", (chunk) => {
|
|
419
|
+
buffer += chunk;
|
|
420
|
+
const lines = buffer.split(/\r?\n/);
|
|
421
|
+
buffer = lines.pop() || "";
|
|
422
|
+
for (const line of lines) {
|
|
423
|
+
const trimmed = line.trim();
|
|
424
|
+
if (!trimmed) continue;
|
|
425
|
+
void (async () => {
|
|
426
|
+
try {
|
|
427
|
+
const response = await handleRequest(JSON.parse(trimmed));
|
|
428
|
+
if (response) writeMessage(response);
|
|
429
|
+
} catch (error) {
|
|
430
|
+
writeMessage({
|
|
431
|
+
jsonrpc: "2.0",
|
|
432
|
+
id: null,
|
|
433
|
+
error: {
|
|
434
|
+
code: -32000,
|
|
435
|
+
message: error?.message || String(error)
|
|
436
|
+
}
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
})();
|
|
440
|
+
}
|
|
441
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { assess } from "../src/assessment/assess.mjs";
|
|
4
|
+
import { renderComparisonConsole } from "../src/report/comparison-console.mjs";
|
|
5
|
+
import { renderConsole } from "../src/report/console.mjs";
|
|
6
|
+
import { renderMarkdown } from "../src/report/markdown.mjs";
|
|
7
|
+
import { compareAssessmentFiles } from "../src/results/compare-results.mjs";
|
|
8
|
+
import { saveAssessmentResult } from "../src/results/save-result.mjs";
|
|
9
|
+
|
|
10
|
+
function parseArgs(argv) {
|
|
11
|
+
const args = {
|
|
12
|
+
command: argv[2],
|
|
13
|
+
target: argv[3],
|
|
14
|
+
current: argv[4],
|
|
15
|
+
profile: null,
|
|
16
|
+
format: "console",
|
|
17
|
+
save: false,
|
|
18
|
+
outDir: null
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const firstOptionIndex = args.command === "compare" ? 5 : 4;
|
|
22
|
+
for (let i = firstOptionIndex; i < argv.length; i += 1) {
|
|
23
|
+
const arg = argv[i];
|
|
24
|
+
if (arg === "--profile") {
|
|
25
|
+
args.profile = argv[i + 1];
|
|
26
|
+
i += 1;
|
|
27
|
+
} else if (arg === "--format") {
|
|
28
|
+
args.format = argv[i + 1];
|
|
29
|
+
i += 1;
|
|
30
|
+
} else if (arg === "--save") {
|
|
31
|
+
args.save = true;
|
|
32
|
+
} else if (arg === "--out-dir") {
|
|
33
|
+
args.outDir = argv[i + 1];
|
|
34
|
+
i += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
return args;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function printHelp() {
|
|
42
|
+
console.log(`AgentSecurityLens
|
|
43
|
+
|
|
44
|
+
Usage:
|
|
45
|
+
agent-security-lens assess <target> [--profile <id>] [--format console|json|markdown] [--save] [--out-dir <dir>]
|
|
46
|
+
agent-security-lens compare <previous-result.json> <current-result.json> [--format console|json]
|
|
47
|
+
|
|
48
|
+
Examples:
|
|
49
|
+
agent-security-lens assess .
|
|
50
|
+
agent-security-lens assess ~/.openclaw --profile openclaw-like
|
|
51
|
+
agent-security-lens assess . --format markdown
|
|
52
|
+
agent-security-lens assess . --save --out-dir ./assessment-runs
|
|
53
|
+
agent-security-lens compare ./old.json ./new.json
|
|
54
|
+
`);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
async function main() {
|
|
58
|
+
const args = parseArgs(process.argv);
|
|
59
|
+
|
|
60
|
+
if (!args.command || args.command === "--help" || args.command === "-h") {
|
|
61
|
+
printHelp();
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (args.command === "compare") {
|
|
66
|
+
if (!args.target || !args.current) {
|
|
67
|
+
console.error("Missing comparison result paths.");
|
|
68
|
+
printHelp();
|
|
69
|
+
process.exitCode = 1;
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
const comparison = await compareAssessmentFiles(args.target, args.current);
|
|
73
|
+
if (args.format === "json") {
|
|
74
|
+
console.log(JSON.stringify(comparison, null, 2));
|
|
75
|
+
} else {
|
|
76
|
+
console.log(renderComparisonConsole(comparison));
|
|
77
|
+
}
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (args.command !== "assess") {
|
|
82
|
+
console.error(`Unknown command: ${args.command}`);
|
|
83
|
+
printHelp();
|
|
84
|
+
process.exitCode = 1;
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
if (!args.target) {
|
|
89
|
+
console.error("Missing target path.");
|
|
90
|
+
printHelp();
|
|
91
|
+
process.exitCode = 1;
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const result = await assess({
|
|
96
|
+
targetPath: args.target,
|
|
97
|
+
requestedProfile: args.profile
|
|
98
|
+
});
|
|
99
|
+
const saved = args.save
|
|
100
|
+
? await saveAssessmentResult({ result, outDir: args.outDir })
|
|
101
|
+
: null;
|
|
102
|
+
|
|
103
|
+
if (args.format === "json") {
|
|
104
|
+
console.log(JSON.stringify(saved ? { ...result, saved } : result, null, 2));
|
|
105
|
+
} else if (args.format === "markdown") {
|
|
106
|
+
console.log(renderMarkdown(result));
|
|
107
|
+
if (saved) console.log(`\nSaved result: ${saved.path}`);
|
|
108
|
+
} else {
|
|
109
|
+
console.log(renderConsole(result));
|
|
110
|
+
if (saved) console.log(`\nSaved result: ${saved.path}`);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
main().catch((error) => {
|
|
115
|
+
console.error(error?.stack || String(error));
|
|
116
|
+
process.exitCode = 1;
|
|
117
|
+
});
|