hackmyagent-core 0.3.8 → 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/README.md +53 -10
- package/dist/hardening/scanner.d.ts +4 -0
- package/dist/hardening/scanner.d.ts.map +1 -1
- package/dist/hardening/scanner.js +308 -0
- package/dist/hardening/scanner.js.map +1 -1
- package/dist/hardening/scanner.test.js +181 -0
- package/dist/hardening/scanner.test.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -223,7 +223,7 @@ Only `id` and `payload` are required. See `--help` for all defaults.
|
|
|
223
223
|
|
|
224
224
|
### `hackmyagent secure --benchmark`
|
|
225
225
|
|
|
226
|
-
Run the OASB-1 (Open Agent Security Benchmark)
|
|
226
|
+
Run the [OASB-1](https://oasb.ai/oasb-1) (Open Agent Security Benchmark) — 46 controls across 10 categories that measure how secure your AI agent setup is.
|
|
227
227
|
|
|
228
228
|
```bash
|
|
229
229
|
# Run benchmark (L1 by default)
|
|
@@ -233,9 +233,15 @@ hackmyagent secure --benchmark oasb-1
|
|
|
233
233
|
hackmyagent secure ./my-project --benchmark oasb-1
|
|
234
234
|
|
|
235
235
|
# Different maturity levels
|
|
236
|
-
hackmyagent secure -b oasb-1 -l L1 # Essential (
|
|
237
|
-
hackmyagent secure -b oasb-1 -l L2 # Standard
|
|
238
|
-
hackmyagent secure -b oasb-1 -l L3 # Hardened
|
|
236
|
+
hackmyagent secure -b oasb-1 -l L1 # Essential (26 controls)
|
|
237
|
+
hackmyagent secure -b oasb-1 -l L2 # Standard (44 controls)
|
|
238
|
+
hackmyagent secure -b oasb-1 -l L3 # Hardened (46 controls)
|
|
239
|
+
|
|
240
|
+
# Verbose — see every control with pass/fail/unverified status
|
|
241
|
+
hackmyagent secure -b oasb-1 -v
|
|
242
|
+
|
|
243
|
+
# Filter by category
|
|
244
|
+
hackmyagent secure -b oasb-1 --category "Credential Protection"
|
|
239
245
|
|
|
240
246
|
# Output formats
|
|
241
247
|
hackmyagent secure -b oasb-1 -f json
|
|
@@ -243,16 +249,53 @@ hackmyagent secure -b oasb-1 -f sarif -o results.sarif
|
|
|
243
249
|
hackmyagent secure -b oasb-1 -f html -o report.html
|
|
244
250
|
hackmyagent secure -b oasb-1 -f asp -o profile.asp.json
|
|
245
251
|
|
|
246
|
-
# CI/CD
|
|
252
|
+
# CI/CD gate — exit 1 if compliance is below threshold
|
|
247
253
|
hackmyagent secure -b oasb-1 --fail-below 70
|
|
248
254
|
```
|
|
249
255
|
|
|
256
|
+
**OASB-1 Categories (46 controls):**
|
|
257
|
+
|
|
258
|
+
| # | Category | Controls | What it checks |
|
|
259
|
+
|---|----------|----------|----------------|
|
|
260
|
+
| 1 | Identity & Provenance | 4 | Cryptographic identity, ownership, provenance chain |
|
|
261
|
+
| 2 | Capability & Authorization | 5 | Least privilege, capability boundaries, human-in-the-loop |
|
|
262
|
+
| 3 | Input Security | 5 | Prompt injection, input validation, URL/SSRF protection |
|
|
263
|
+
| 4 | Output Security | 4 | Output validation, destructive op confirmation, exfiltration prevention |
|
|
264
|
+
| 5 | Credential Protection | 5 | Hardcoded secrets, context window isolation, log redaction |
|
|
265
|
+
| 6 | Supply Chain Integrity | 5 | Dependency scanning, lockfiles, rug pull protection, SBOM |
|
|
266
|
+
| 7 | Agent-to-Agent Security | 4 | Mutual auth, message integrity, trust boundaries |
|
|
267
|
+
| 8 | Memory & Context Integrity | 4 | Context injection, memory isolation, summarization security |
|
|
268
|
+
| 9 | Operational Security | 5 | Non-root execution, sandboxing, network isolation, resource limits |
|
|
269
|
+
| 10 | Monitoring & Response | 5 | Security logging, anomaly detection, kill switch, incident response |
|
|
270
|
+
|
|
271
|
+
**Maturity Levels:**
|
|
272
|
+
|
|
273
|
+
| Level | Controls | Purpose |
|
|
274
|
+
|-------|----------|---------|
|
|
275
|
+
| L1 - Essential | 26 | Baseline security every agent should meet |
|
|
276
|
+
| L2 - Standard | 44 (L1 + 18) | Production-grade agent security |
|
|
277
|
+
| L3 - Hardened | 46 (L2 + 2) | High-security environments, multi-modal threats |
|
|
278
|
+
|
|
279
|
+
**Rating System:**
|
|
280
|
+
|
|
281
|
+
| Rating | L1 Criteria | L2 Criteria | L3 Criteria |
|
|
282
|
+
|--------|-------------|-------------|-------------|
|
|
283
|
+
| Certified | 100% | L1=100% + L2=100% | All 100% |
|
|
284
|
+
| Compliant | — | L1=100% + L2≥90% | L1=100% + L2≥90% |
|
|
285
|
+
| Passing | ≥90% | L1≥90% | L1≥90% |
|
|
286
|
+
| Needs Improvement | ≥70% | L1≥70% | L1≥70% |
|
|
287
|
+
| Failing | <70% | L1<70% | L1<70% |
|
|
288
|
+
|
|
250
289
|
**Output Formats:**
|
|
251
|
-
- `text`
|
|
252
|
-
- `json`
|
|
253
|
-
- `sarif`
|
|
254
|
-
- `html`
|
|
255
|
-
- `asp`
|
|
290
|
+
- `text` — Terminal report with category breakdown (default)
|
|
291
|
+
- `json` — Machine-readable JSON with full control details
|
|
292
|
+
- `sarif` — SARIF 2.1.0 for GitHub Security tab and IDE integration
|
|
293
|
+
- `html` — Standalone HTML report with donut chart, radar chart, and grades
|
|
294
|
+
- `asp` — Agent Security Profile (portable security posture document)
|
|
295
|
+
|
|
296
|
+
**Exit Codes:**
|
|
297
|
+
- `0` — Rating is Passing or better (or compliance above `--fail-below` threshold)
|
|
298
|
+
- `1` — Rating is Failing or Needs Improvement (or compliance below threshold)
|
|
256
299
|
|
|
257
300
|
### `hackmyagent secure-openclaw`
|
|
258
301
|
|
|
@@ -135,5 +135,9 @@ export declare class HardeningScanner {
|
|
|
135
135
|
* OpenClaw supply chain security checks (SUPPLY-001 to SUPPLY-004)
|
|
136
136
|
*/
|
|
137
137
|
private checkOpenclawSupplyChain;
|
|
138
|
+
/**
|
|
139
|
+
* OpenClaw CVE-specific checks (CVE-001, CVE-002)
|
|
140
|
+
*/
|
|
141
|
+
private checkOpenclawCVE;
|
|
138
142
|
}
|
|
139
143
|
//# sourceMappingURL=scanner.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAA0C,MAAM,kBAAkB,CAAC;
|
|
1
|
+
{"version":3,"file":"scanner.d.ts","sourceRoot":"","sources":["../../src/hardening/scanner.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAIH,OAAO,KAAK,EAAE,UAAU,EAA0C,MAAM,kBAAkB,CAAC;AAyD3F,MAAM,WAAW,WAAW;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,0DAA0D;IAC1D,MAAM,CAAC,EAAE,MAAM,EAAE,CAAC;IAClB,wEAAwE;IACxE,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;CACxB;AA8HD,qBAAa,gBAAgB;IAE3B,OAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,YAAY,CAelC;IAEF;;OAEG;IACH,OAAO,CAAC,qBAAqB;IAMvB,IAAI,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,UAAU,CAAC;YA8NvC,cAAc;IAsE5B;;OAEG;YACW,iBAAiB;IA+F/B;;OAEG;IACH,OAAO,CAAC,gBAAgB;YAeV,uBAAuB;YAmGvB,aAAa;YAgDb,cAAc;YA+Fd,oBAAoB;YAwDpB,gBAAgB;YA0IhB,oBAAoB;YAgFpB,gBAAgB;YA2IhB,mBAAmB;YA4EnB,iBAAiB;YAyCjB,iBAAiB;YA+DjB,wBAAwB;YA0FxB,wBAAwB;YAmExB,wBAAwB;YAqHxB,oBAAoB;YA+GpB,uBAAuB;YA8HvB,iBAAiB;YA8GjB,oBAAoB;YAuGpB,mBAAmB;YAiGnB,gBAAgB;YAmIhB,oBAAoB;YAoIpB,gBAAgB;YAyHhB,qBAAqB;YA+GrB,eAAe;IAiI7B;;OAEG;YACW,mBAAmB;IA8GjC;;OAEG;YACW,oBAAoB;IAiKlC;;OAEG;YACW,iBAAiB;IA4I/B;;OAEG;YACW,oBAAoB;IAwIlC;;OAEG;YACW,eAAe;IAqJ7B;;OAEG;YACW,eAAe;IAuI7B;;OAEG;YACW,eAAe;IAyG7B;;OAEG;YACW,mBAAmB;IAmHjC,OAAO,CAAC,cAAc;IAsBtB;;OAEG;YACW,YAAY;IAkD1B;;OAEG;IACG,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IA6DhD;;;OAGG;YACW,cAAc;IAgD5B;;OAEG;YACW,mBAAmB;IAoUjC;;;OAGG;YACW,kBAAkB;IAgDhC;;OAEG;YACW,sBAAsB;IA2LpC;;OAEG;YACW,sBAAsB;IA+BpC;;OAEG;YACW,oBAAoB;IAqVlC;;OAEG;IACH,OAAO,CAAC,mBAAmB;IA4B3B;;OAEG;YACW,iBAAiB;IA8D/B;;OAEG;YACW,mBAAmB;IA6VjC;;OAEG;YACW,wBAAwB;IA4OtC;;OAEG;YACW,gBAAgB;CAmG/B"}
|
|
@@ -71,6 +71,7 @@ const CHECK_PROJECT_TYPES = {
|
|
|
71
71
|
'GATEWAY-': ['openclaw'], // Gateway configuration security
|
|
72
72
|
'CONFIG-': ['openclaw', 'mcp'], // Configuration file security
|
|
73
73
|
'SUPPLY-': ['openclaw', 'mcp'], // Supply chain security
|
|
74
|
+
'CVE-': ['openclaw'], // CVE-specific detection
|
|
74
75
|
'API-': ['api'], // API security headers
|
|
75
76
|
'RATE-': ['webapp', 'api'], // Rate limiting
|
|
76
77
|
'PROC-': ['webapp', 'api'], // Process security (headers, etc.)
|
|
@@ -165,6 +166,19 @@ const HEARTBEAT_DANGEROUS_CAPS = [
|
|
|
165
166
|
'filesystem:/',
|
|
166
167
|
'network:*',
|
|
167
168
|
];
|
|
169
|
+
// ClawHavoc campaign IOCs (Koi Security research, Jan 2026)
|
|
170
|
+
const CLAWHAVOC_C2_IPS = ['91.92.242.30'];
|
|
171
|
+
const CLAWHAVOC_MALICIOUS_FILES = [
|
|
172
|
+
'openclaw-agent.exe', 'openclaw-agent.zip', 'openclawcli.zip',
|
|
173
|
+
'agent-setup.exe', 'openclaw-installer.dmg',
|
|
174
|
+
];
|
|
175
|
+
const CLAWHAVOC_CLICKFIX_PATTERNS = [
|
|
176
|
+
/download.*paste.*terminal/i,
|
|
177
|
+
/copy.*(?:command|script).*terminal/i,
|
|
178
|
+
/right[- ]click.*open/i,
|
|
179
|
+
/run.*\.exe/i,
|
|
180
|
+
];
|
|
181
|
+
const CLAWHAVOC_ARCHIVE_PASSWORD = /password\s*[:=]\s*["']?(openclaw|claw|agent|setup)["']?/i;
|
|
168
182
|
const PROMPT_INJECTION_PATTERNS = [
|
|
169
183
|
/ignore\s+(all\s+)?(previous|prior|above)/gi,
|
|
170
184
|
/disregard\s+(all\s+)?(previous|prior)/gi,
|
|
@@ -320,6 +334,9 @@ class HardeningScanner {
|
|
|
320
334
|
// OpenClaw supply chain checks
|
|
321
335
|
const supplyFindings = await this.checkOpenclawSupplyChain(targetDir, shouldFix);
|
|
322
336
|
findings.push(...supplyFindings);
|
|
337
|
+
// OpenClaw CVE-specific checks
|
|
338
|
+
const cveFindings = await this.checkOpenclawCVE(targetDir, shouldFix);
|
|
339
|
+
findings.push(...cveFindings);
|
|
323
340
|
// Filter findings to only show real, actionable issues:
|
|
324
341
|
// 1. Only failed checks (passed: false)
|
|
325
342
|
// 2. Only checks with a file path (concrete findings, not generic advice)
|
|
@@ -4495,6 +4512,58 @@ dist/
|
|
|
4495
4512
|
fix: 'Manually disable privileged mode and remove sensitive host mounts - requires careful review',
|
|
4496
4513
|
});
|
|
4497
4514
|
}
|
|
4515
|
+
// GATEWAY-007: Open DM Policy with Wildcard
|
|
4516
|
+
const channels = config.channels;
|
|
4517
|
+
const dm = config.dm;
|
|
4518
|
+
let hasOpenDmWildcard = false;
|
|
4519
|
+
if (channels) {
|
|
4520
|
+
for (const [, channelConfig] of Object.entries(channels)) {
|
|
4521
|
+
if (channelConfig.dmPolicy === 'open' &&
|
|
4522
|
+
Array.isArray(channelConfig.allowFrom) &&
|
|
4523
|
+
channelConfig.allowFrom.includes('*')) {
|
|
4524
|
+
hasOpenDmWildcard = true;
|
|
4525
|
+
break;
|
|
4526
|
+
}
|
|
4527
|
+
}
|
|
4528
|
+
}
|
|
4529
|
+
if (!hasOpenDmWildcard && dm?.policy === 'open') {
|
|
4530
|
+
const allowList = dm.allowFrom;
|
|
4531
|
+
if (Array.isArray(allowList) && allowList.includes('*')) {
|
|
4532
|
+
hasOpenDmWildcard = true;
|
|
4533
|
+
}
|
|
4534
|
+
}
|
|
4535
|
+
if (hasOpenDmWildcard) {
|
|
4536
|
+
findings.push({
|
|
4537
|
+
checkId: 'GATEWAY-007',
|
|
4538
|
+
name: 'Open DM Policy with Wildcard',
|
|
4539
|
+
description: 'Direct message policy allows messages from any source',
|
|
4540
|
+
category: 'gateway',
|
|
4541
|
+
severity: 'critical',
|
|
4542
|
+
passed: false,
|
|
4543
|
+
message: 'DM policy is open with wildcard allowFrom - anyone can message the agent',
|
|
4544
|
+
file: relativePath,
|
|
4545
|
+
fixable: false,
|
|
4546
|
+
fix: 'Replace wildcard "*" in allowFrom with specific allowed sender IDs or domains',
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
// GATEWAY-008: Tailscale Funnel Exposure
|
|
4550
|
+
const tailscale = gateway?.tailscale;
|
|
4551
|
+
const tailscaleRoot = config.tailscale;
|
|
4552
|
+
const funnelEnabled = tailscale?.funnel === true || tailscaleRoot?.funnel === true;
|
|
4553
|
+
if (funnelEnabled) {
|
|
4554
|
+
findings.push({
|
|
4555
|
+
checkId: 'GATEWAY-008',
|
|
4556
|
+
name: 'Tailscale Funnel Exposure',
|
|
4557
|
+
description: 'Tailscale Funnel is enabled, exposing the agent to the public internet',
|
|
4558
|
+
category: 'gateway',
|
|
4559
|
+
severity: 'high',
|
|
4560
|
+
passed: false,
|
|
4561
|
+
message: 'Tailscale Funnel enabled - agent is publicly accessible from the internet',
|
|
4562
|
+
file: relativePath,
|
|
4563
|
+
fixable: false,
|
|
4564
|
+
fix: 'Disable Tailscale Funnel unless public access is intentional. Use Tailscale ACLs to restrict access.',
|
|
4565
|
+
});
|
|
4566
|
+
}
|
|
4498
4567
|
// Write modified config back to file if any fixes were applied
|
|
4499
4568
|
if (configModified) {
|
|
4500
4569
|
try {
|
|
@@ -4873,6 +4942,71 @@ dist/
|
|
|
4873
4942
|
fix: 'Disable autoFollow or review moltbook security settings',
|
|
4874
4943
|
});
|
|
4875
4944
|
}
|
|
4945
|
+
// CONFIG-007: Unrestricted Elevated Execution
|
|
4946
|
+
const tools = config.tools;
|
|
4947
|
+
const elevated = tools?.elevated;
|
|
4948
|
+
const exec = config.exec;
|
|
4949
|
+
const execApprovals = exec?.approvals;
|
|
4950
|
+
const hasUnrestrictedExec = elevated?.defaultLevel === 'full' ||
|
|
4951
|
+
execApprovals?.set === 'off';
|
|
4952
|
+
if (hasUnrestrictedExec) {
|
|
4953
|
+
findings.push({
|
|
4954
|
+
checkId: 'CONFIG-007',
|
|
4955
|
+
name: 'Unrestricted Elevated Execution',
|
|
4956
|
+
description: 'Elevated execution is set to full access without restrictions or approvals are bypassed',
|
|
4957
|
+
category: 'config',
|
|
4958
|
+
severity: 'critical',
|
|
4959
|
+
passed: false,
|
|
4960
|
+
message: elevated?.defaultLevel === 'full'
|
|
4961
|
+
? 'tools.elevated.defaultLevel is "full" - all tools run with maximum privileges'
|
|
4962
|
+
: 'exec.approvals.set is "off" - execution approval is bypassed',
|
|
4963
|
+
file: relativePath,
|
|
4964
|
+
fixable: false,
|
|
4965
|
+
fix: 'Set tools.elevated.defaultLevel to "restricted" and enable exec.approvals',
|
|
4966
|
+
});
|
|
4967
|
+
}
|
|
4968
|
+
// CONFIG-008: Sandbox Disabled
|
|
4969
|
+
const sandbox = config.sandbox;
|
|
4970
|
+
const toolExec = tools?.exec;
|
|
4971
|
+
const sandboxDisabled = sandbox?.enabled === false ||
|
|
4972
|
+
toolExec?.sandbox === false;
|
|
4973
|
+
if (sandboxDisabled) {
|
|
4974
|
+
findings.push({
|
|
4975
|
+
checkId: 'CONFIG-008',
|
|
4976
|
+
name: 'Sandbox Disabled',
|
|
4977
|
+
description: 'Sandbox execution environment is explicitly disabled in config',
|
|
4978
|
+
category: 'config',
|
|
4979
|
+
severity: 'high',
|
|
4980
|
+
passed: false,
|
|
4981
|
+
message: sandbox?.enabled === false
|
|
4982
|
+
? 'sandbox.enabled is false - code runs without isolation'
|
|
4983
|
+
: 'tools.exec.sandbox is false - tool execution is not sandboxed',
|
|
4984
|
+
file: relativePath,
|
|
4985
|
+
fixable: false,
|
|
4986
|
+
fix: 'Enable sandbox: set sandbox.enabled to true or tools.exec.sandbox to true',
|
|
4987
|
+
});
|
|
4988
|
+
}
|
|
4989
|
+
// CONFIG-009: Weak Gateway Token
|
|
4990
|
+
const gatewayConfig = config.gateway;
|
|
4991
|
+
const gatewayAuth = gatewayConfig?.auth;
|
|
4992
|
+
const tokenValue = gatewayAuth?.token || config.token;
|
|
4993
|
+
if (typeof tokenValue === 'string' &&
|
|
4994
|
+
tokenValue.length > 0 &&
|
|
4995
|
+
tokenValue.length < 24 &&
|
|
4996
|
+
!tokenValue.startsWith('${')) {
|
|
4997
|
+
findings.push({
|
|
4998
|
+
checkId: 'CONFIG-009',
|
|
4999
|
+
name: 'Weak Gateway Token',
|
|
5000
|
+
description: 'Gateway authentication token is too short (< 24 characters)',
|
|
5001
|
+
category: 'config',
|
|
5002
|
+
severity: 'high',
|
|
5003
|
+
passed: false,
|
|
5004
|
+
message: `Token is only ${tokenValue.length} characters - minimum 24 recommended`,
|
|
5005
|
+
file: relativePath,
|
|
5006
|
+
fixable: false,
|
|
5007
|
+
fix: 'Generate a stronger token: openssl rand -base64 32',
|
|
5008
|
+
});
|
|
5009
|
+
}
|
|
4876
5010
|
}
|
|
4877
5011
|
return findings;
|
|
4878
5012
|
}
|
|
@@ -4891,6 +5025,16 @@ dist/
|
|
|
4891
5025
|
'phantom-wallet',
|
|
4892
5026
|
'youtube-downloader',
|
|
4893
5027
|
'clawhub',
|
|
5028
|
+
'clawhub1',
|
|
5029
|
+
'clawhubb',
|
|
5030
|
+
'cllawhub',
|
|
5031
|
+
'clawhub-official',
|
|
5032
|
+
'openclaw-official',
|
|
5033
|
+
'openclaw1',
|
|
5034
|
+
'opennclaw',
|
|
5035
|
+
'insiderwallet',
|
|
5036
|
+
'wallet-finder',
|
|
5037
|
+
'crypto-insider',
|
|
4894
5038
|
];
|
|
4895
5039
|
for (const skillFile of skillFiles) {
|
|
4896
5040
|
const relativePath = path.relative(targetDir, skillFile);
|
|
@@ -5008,6 +5152,170 @@ dist/
|
|
|
5008
5152
|
fixable: false,
|
|
5009
5153
|
fix: 'Add installed_hash: with SHA-256 hash of the original skill content',
|
|
5010
5154
|
});
|
|
5155
|
+
// SUPPLY-005: ClawHavoc C2 IP
|
|
5156
|
+
for (const ip of CLAWHAVOC_C2_IPS) {
|
|
5157
|
+
if (content.includes(ip)) {
|
|
5158
|
+
findings.push({
|
|
5159
|
+
checkId: 'SUPPLY-005',
|
|
5160
|
+
name: 'ClawHavoc C2 IP Detected',
|
|
5161
|
+
description: 'Skill contains known ClawHavoc command-and-control IP address',
|
|
5162
|
+
category: 'supply',
|
|
5163
|
+
severity: 'critical',
|
|
5164
|
+
passed: false,
|
|
5165
|
+
message: `Known C2 IP address found: ${ip}`,
|
|
5166
|
+
file: relativePath,
|
|
5167
|
+
fixable: false,
|
|
5168
|
+
fix: 'Remove this skill immediately - contains known malware C2 infrastructure',
|
|
5169
|
+
});
|
|
5170
|
+
break;
|
|
5171
|
+
}
|
|
5172
|
+
}
|
|
5173
|
+
// SUPPLY-006: Malware Filenames
|
|
5174
|
+
for (const filename of CLAWHAVOC_MALICIOUS_FILES) {
|
|
5175
|
+
if (content.toLowerCase().includes(filename.toLowerCase())) {
|
|
5176
|
+
findings.push({
|
|
5177
|
+
checkId: 'SUPPLY-006',
|
|
5178
|
+
name: 'ClawHavoc Malware Filename',
|
|
5179
|
+
description: 'Skill references known ClawHavoc malware payload filename',
|
|
5180
|
+
category: 'supply',
|
|
5181
|
+
severity: 'critical',
|
|
5182
|
+
passed: false,
|
|
5183
|
+
message: `Known malware filename referenced: "${filename}"`,
|
|
5184
|
+
file: relativePath,
|
|
5185
|
+
fixable: false,
|
|
5186
|
+
fix: 'Remove this skill immediately - references known malware payload',
|
|
5187
|
+
});
|
|
5188
|
+
break;
|
|
5189
|
+
}
|
|
5190
|
+
}
|
|
5191
|
+
// SUPPLY-007: ClickFix Pattern
|
|
5192
|
+
for (const pattern of CLAWHAVOC_CLICKFIX_PATTERNS) {
|
|
5193
|
+
const match = content.match(pattern);
|
|
5194
|
+
if (match) {
|
|
5195
|
+
findings.push({
|
|
5196
|
+
checkId: 'SUPPLY-007',
|
|
5197
|
+
name: 'ClawHavoc ClickFix Pattern',
|
|
5198
|
+
description: 'Skill contains social engineering instructions to execute malware',
|
|
5199
|
+
category: 'supply',
|
|
5200
|
+
severity: 'high',
|
|
5201
|
+
passed: false,
|
|
5202
|
+
message: `ClickFix social engineering pattern detected: "${match[0]}"`,
|
|
5203
|
+
file: relativePath,
|
|
5204
|
+
fixable: false,
|
|
5205
|
+
fix: 'Review and remove suspicious download/execute instructions',
|
|
5206
|
+
});
|
|
5207
|
+
break;
|
|
5208
|
+
}
|
|
5209
|
+
}
|
|
5210
|
+
// SUPPLY-008: Suspicious Archive Password
|
|
5211
|
+
const archiveMatch = content.match(CLAWHAVOC_ARCHIVE_PASSWORD);
|
|
5212
|
+
if (archiveMatch) {
|
|
5213
|
+
findings.push({
|
|
5214
|
+
checkId: 'SUPPLY-008',
|
|
5215
|
+
name: 'Suspicious Archive Password',
|
|
5216
|
+
description: 'Skill contains password-protected archive reference typical of malware distribution',
|
|
5217
|
+
category: 'supply',
|
|
5218
|
+
severity: 'high',
|
|
5219
|
+
passed: false,
|
|
5220
|
+
message: `Suspicious archive password pattern: "${archiveMatch[0]}"`,
|
|
5221
|
+
file: relativePath,
|
|
5222
|
+
fixable: false,
|
|
5223
|
+
fix: 'Investigate password-protected archive reference - common malware distribution technique',
|
|
5224
|
+
});
|
|
5225
|
+
}
|
|
5226
|
+
}
|
|
5227
|
+
return findings;
|
|
5228
|
+
}
|
|
5229
|
+
/**
|
|
5230
|
+
* OpenClaw CVE-specific checks (CVE-001, CVE-002)
|
|
5231
|
+
*/
|
|
5232
|
+
async checkOpenclawCVE(targetDir, _autoFix) {
|
|
5233
|
+
const findings = [];
|
|
5234
|
+
// CVE-001: Vulnerable OpenClaw Version
|
|
5235
|
+
const pkgJsonPath = path.join(targetDir, 'package.json');
|
|
5236
|
+
try {
|
|
5237
|
+
const pkgContent = await fs.readFile(pkgJsonPath, 'utf-8');
|
|
5238
|
+
const pkg = JSON.parse(pkgContent);
|
|
5239
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
5240
|
+
const openclawVersion = deps?.openclaw || deps?.['@openclaw/core'];
|
|
5241
|
+
if (openclawVersion) {
|
|
5242
|
+
// Extract numeric version (strip ^ ~ >= etc.)
|
|
5243
|
+
const versionMatch = openclawVersion.match(/(\d{4})\.(\d{1,2})\.(\d{1,2})/);
|
|
5244
|
+
if (versionMatch) {
|
|
5245
|
+
const year = parseInt(versionMatch[1], 10);
|
|
5246
|
+
const month = parseInt(versionMatch[2], 10);
|
|
5247
|
+
const day = parseInt(versionMatch[3], 10);
|
|
5248
|
+
// Patch release: v2026.1.29
|
|
5249
|
+
const isVulnerable = year < 2026 ||
|
|
5250
|
+
(year === 2026 && month < 1) ||
|
|
5251
|
+
(year === 2026 && month === 1 && day < 29);
|
|
5252
|
+
findings.push({
|
|
5253
|
+
checkId: 'CVE-001',
|
|
5254
|
+
name: 'CVE-2026-25253: WebSocket Hijacking RCE',
|
|
5255
|
+
description: 'OpenClaw version vulnerable to CVE-2026-25253 (CVSS 8.8) - WebSocket hijacking enables 1-click RCE',
|
|
5256
|
+
category: 'cve',
|
|
5257
|
+
severity: 'critical',
|
|
5258
|
+
passed: !isVulnerable,
|
|
5259
|
+
message: isVulnerable
|
|
5260
|
+
? `OpenClaw ${openclawVersion} is vulnerable to CVE-2026-25253 - upgrade to v2026.1.29+`
|
|
5261
|
+
: `OpenClaw ${openclawVersion} includes CVE-2026-25253 fix`,
|
|
5262
|
+
file: 'package.json',
|
|
5263
|
+
fixable: false,
|
|
5264
|
+
fix: 'Upgrade openclaw to v2026.1.29 or later: npm install openclaw@latest',
|
|
5265
|
+
});
|
|
5266
|
+
}
|
|
5267
|
+
}
|
|
5268
|
+
}
|
|
5269
|
+
catch {
|
|
5270
|
+
// No package.json or parse error - skip CVE-001
|
|
5271
|
+
}
|
|
5272
|
+
// CVE-002: Missing Control UI Origin Restrictions
|
|
5273
|
+
const configFiles = await this.findGatewayConfigFiles(targetDir);
|
|
5274
|
+
for (const configFile of configFiles) {
|
|
5275
|
+
const relativePath = path.relative(targetDir, configFile);
|
|
5276
|
+
try {
|
|
5277
|
+
const stats = await fs.stat(configFile);
|
|
5278
|
+
if (stats.size > MAX_FILE_SIZE)
|
|
5279
|
+
continue;
|
|
5280
|
+
const content = await fs.readFile(configFile, 'utf-8');
|
|
5281
|
+
const config = JSON.parse(content);
|
|
5282
|
+
const gateway = config.gateway;
|
|
5283
|
+
const controlUi = gateway?.controlUi;
|
|
5284
|
+
const hasAllowedOrigins = controlUi?.allowedOrigins && Array.isArray(controlUi.allowedOrigins) && controlUi.allowedOrigins.length > 0;
|
|
5285
|
+
// Only flag if auth is configured (no auth = lower risk)
|
|
5286
|
+
const hasAuth = gateway?.auth || config.auth || config.token || gateway?.token;
|
|
5287
|
+
if (hasAuth && !hasAllowedOrigins) {
|
|
5288
|
+
findings.push({
|
|
5289
|
+
checkId: 'CVE-002',
|
|
5290
|
+
name: 'CVE-2026-25253: Missing Control UI Origin Restrictions',
|
|
5291
|
+
description: 'Auth is configured but controlUi.allowedOrigins is missing - vulnerable to cross-origin WebSocket hijacking',
|
|
5292
|
+
category: 'cve',
|
|
5293
|
+
severity: 'critical',
|
|
5294
|
+
passed: false,
|
|
5295
|
+
message: 'Auth configured without controlUi.allowedOrigins - cross-origin requests can steal auth tokens',
|
|
5296
|
+
file: relativePath,
|
|
5297
|
+
fixable: false,
|
|
5298
|
+
fix: 'Add gateway.controlUi.allowedOrigins with your allowed origins (e.g., ["http://localhost:3000"])',
|
|
5299
|
+
});
|
|
5300
|
+
}
|
|
5301
|
+
else if (hasAuth && hasAllowedOrigins) {
|
|
5302
|
+
findings.push({
|
|
5303
|
+
checkId: 'CVE-002',
|
|
5304
|
+
name: 'CVE-2026-25253: Control UI Origin Restrictions',
|
|
5305
|
+
description: 'Control UI origin restrictions are configured',
|
|
5306
|
+
category: 'cve',
|
|
5307
|
+
severity: 'critical',
|
|
5308
|
+
passed: true,
|
|
5309
|
+
message: 'controlUi.allowedOrigins is configured',
|
|
5310
|
+
file: relativePath,
|
|
5311
|
+
fixable: false,
|
|
5312
|
+
fix: 'No action needed',
|
|
5313
|
+
});
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
catch {
|
|
5317
|
+
continue;
|
|
5318
|
+
}
|
|
5011
5319
|
}
|
|
5012
5320
|
return findings;
|
|
5013
5321
|
}
|