hyperclaw 4.0.0 → 4.0.2
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 -18
- package/dist/a2ui-protocol-CT_jDEU9.js +75 -0
- package/dist/agents-routing-683Q2JGp.js +129 -0
- package/dist/agents-routing-BpZBswBH.js +4 -0
- package/dist/api-keys-guide-Bzig1R5W.js +149 -0
- package/dist/api-keys-guide-Dq5Obbp4.js +149 -0
- package/dist/audit-BYxPlnTQ.js +248 -0
- package/dist/bounty-tools-C6LyzxM-.js +211 -0
- package/dist/browser-tools-CQBSbIuO.js +5 -0
- package/dist/browser-tools-YQmwRLLM.js +179 -0
- package/dist/claw-tasks-BRLUvFRD.js +80 -0
- package/dist/connector-3HnyH8fn.js +167 -0
- package/dist/connector-6PMZo5Ky.js +189 -0
- package/dist/connector-B6eoF3DD.js +181 -0
- package/dist/connector-B9tLG8UZ.js +196 -0
- package/dist/connector-BOlqjXWP.js +182 -0
- package/dist/connector-BP8zsbP8.js +189 -0
- package/dist/connector-BPoSevxp.js +286 -0
- package/dist/connector-BRHj773i.js +163 -0
- package/dist/connector-BToxU-jV.js +267 -0
- package/dist/connector-BliDVsJQ.js +239 -0
- package/dist/connector-Bv6s9oP7.js +88 -0
- package/dist/connector-By5wWGTR.js +343 -0
- package/dist/connector-C1BaFFgN.js +213 -0
- package/dist/connector-CRRWY5Wv.js +167 -0
- package/dist/connector-CXPQVGyI.js +85 -0
- package/dist/connector-Cdk1CXKi.js +194 -0
- package/dist/connector-CwlgFgjx.js +181 -0
- package/dist/connector-DFchk6l7.js +178 -0
- package/dist/connector-DKw7tRAy.js +192 -0
- package/dist/connector-DRv1ahC_.js +343 -0
- package/dist/connector-DU63KW94.js +165 -0
- package/dist/connector-Dbvb1Cj9.js +280 -0
- package/dist/connector-DcZdQcgR.js +173 -0
- package/dist/connector-DxKL8VvZ.js +182 -0
- package/dist/connector-T_YdZtzv.js +162 -0
- package/dist/connector-i4gOS9xL.js +154 -0
- package/dist/connector-rHXE1ZD2.js +167 -0
- package/dist/connector-wdUXChwa.js +172 -0
- package/dist/cost-tracker-pVE15Yq4.js +103 -0
- package/dist/credentials-store-BvnMPJwi.js +4 -0
- package/dist/credentials-store-sb-TRLwR.js +77 -0
- package/dist/cron-tasks-BvDFNyiE.js +82 -0
- package/dist/delivery-B-SJqXLn.js +95 -0
- package/dist/delivery-D5Z98EVq.js +95 -0
- package/dist/delivery-DCOXhXEO.js +5 -0
- package/dist/delivery-VgFeuu2J.js +5 -0
- package/dist/destructive-gate-m-dWqUFg.js +101 -0
- package/dist/developer-keys-JaJK3T27.js +127 -0
- package/dist/developer-keys-kyqmtWK3.js +8 -0
- package/dist/doctor-3oi89QIc.js +175 -0
- package/dist/doctor-Cf1XSfp9.js +4 -0
- package/dist/engine-B4eMiTgl.js +7 -0
- package/dist/engine-B8M7dYul.js +7 -0
- package/dist/engine-BhT-1M9W.js +256 -0
- package/dist/engine-D49jnSd_.js +256 -0
- package/dist/env-resolve-DWOQ45jG.js +9 -0
- package/dist/env-resolve-szSWl0UF.js +94 -0
- package/dist/extraction-tools-D3qDFBJ1.js +91 -0
- package/dist/extraction-tools-DLr_AEwq.js +5 -0
- package/dist/form_data-B_hIUrxU.js +8657 -0
- package/dist/gmail-watch-setup-Czt8rXaX.js +40 -0
- package/dist/heartbeat-engine-CRqfPcFM.js +83 -0
- package/dist/hub-DTsqe5Bt.js +6 -0
- package/dist/hub-FrPTA33j.js +515 -0
- package/dist/hyperclawbot-D9KCtc4P.js +480 -0
- package/dist/hyperclawbot-DfMGowZC.js +480 -0
- package/dist/hyperclawbot-Dw27pJo4.js +480 -0
- package/dist/inference-CTWJeX9Q.js +922 -0
- package/dist/inference-ix607p7k.js +6 -0
- package/dist/knowledge-graph-DqA-Fztl.js +131 -0
- package/dist/loader-CISCqBto.js +400 -0
- package/dist/loader-CYMQ8VOS.js +4 -0
- package/dist/logger-8tEtAd3y.js +83 -0
- package/dist/manager-CPjeRe-6.js +4 -0
- package/dist/manager-Cwzj7w5R.js +105 -0
- package/dist/manager-DLmZI-9R.js +6 -0
- package/dist/manager-DSGhn5i3.js +117 -0
- package/dist/manager-DgyF52mg.js +218 -0
- package/dist/manager-Dm8nrMFx.js +40 -0
- package/dist/mcp-B_9Ber63.js +139 -0
- package/dist/mcp-loader-DSM5UiFG.js +94 -0
- package/dist/mcp-loader-j5ZLLw5O.js +94 -0
- package/dist/memory-BI1kPkAN.js +4 -0
- package/dist/memory-BVFGkxxK.js +270 -0
- package/dist/memory-auto-Bc7euou4.js +306 -0
- package/dist/memory-auto-DPfbkMVt.js +5 -0
- package/dist/memory-integration-DZExqWr4.js +91 -0
- package/dist/moltbook-B6ZeGN5_.js +81 -0
- package/dist/node-pwL6O_KX.js +222 -0
- package/dist/nodes-registry-CsPm_-CJ.js +52 -0
- package/dist/oauth-flow-CpWlgvNB.js +150 -0
- package/dist/oauth-provider-BZb6qOw5.js +110 -0
- package/dist/observability-B43YvNQV.js +89 -0
- package/dist/onboard-3q20ZyHj.js +9 -0
- package/dist/onboard-Bd_wsYdi.js +4086 -0
- package/dist/onboard-CAN7x3me.js +3026 -0
- package/dist/onboard-DnegOHMh.js +3026 -0
- package/dist/onboard-RYtDlYBw.js +9 -0
- package/dist/onboard-aTwlQs-4.js +9 -0
- package/dist/orchestrator-BSp2M5EU.js +189 -0
- package/dist/orchestrator-C7ko5tWa.js +6 -0
- package/dist/orchestrator-DfPkIx2Z.js +6 -0
- package/dist/orchestrator-NJQsmiBE.js +189 -0
- package/dist/pairing-DU0_J28n.js +87 -0
- package/dist/pairing-DWllbSbO.js +4 -0
- package/dist/pc-access-Ly-uA8mn.js +8 -0
- package/dist/pc-access-NxBvTrRj.js +819 -0
- package/dist/pending-approval-DIHvwwWS.js +22 -0
- package/dist/puppeteer-2o3QOwAy.js +2 -2
- package/dist/puppeteer-BYTMp3BI.js +2 -2
- package/dist/puppeteer-DQ45qwWk.js +2 -2
- package/dist/reminders-store-D79qdfN0.js +58 -0
- package/dist/renderer-pqlDRKbH.js +225 -0
- package/dist/rules-BooT_qFP.js +103 -0
- package/dist/run-main.js +366 -1109
- package/dist/runner-Bu--_RXw.js +810 -0
- package/dist/runner-D1rjuMTJ.js +810 -0
- package/dist/sdk/index.js +2 -2
- package/dist/sdk/index.mjs +2 -2
- package/dist/security-C-5URby1.js +73 -0
- package/dist/security-_xve79aq.js +4 -0
- package/dist/server-0kgyELx4.js +1047 -0
- package/dist/server-BIuTobTC.js +4 -0
- package/dist/server-BRlCEjyT.js +1047 -0
- package/dist/server-CCI1hv45.js +1047 -0
- package/dist/server-DU9POoWc.js +4 -0
- package/dist/server-RBqwE_GN.js +4 -0
- package/dist/session-store-CujxByI6.js +113 -0
- package/dist/session-store-qpJUg2M1.js +5 -0
- package/dist/sessions-tools-CB2qbwIk.js +5 -0
- package/dist/sessions-tools-DHMaTZIs.js +95 -0
- package/dist/skill-loader-BkceKkIg.js +7 -0
- package/dist/skill-loader-DhgIwK4J.js +159 -0
- package/dist/skill-runtime--LqxWrp5.js +102 -0
- package/dist/skill-runtime-C5l0Tgt-.js +5 -0
- package/dist/skill-runtime-DsXK_HYG.js +102 -0
- package/dist/skill-runtime-IVTiqrMR.js +5 -0
- package/dist/src-BEVLgaF1.js +63 -0
- package/dist/src-Bgu_OxTQ.js +458 -0
- package/dist/src-Bq-oKt7Z.js +458 -0
- package/dist/src-DWCUhnD4.js +20 -0
- package/dist/src-cfRTjFef.js +63 -0
- package/dist/sub-agent-tools-BD9DF8_g.js +39 -0
- package/dist/sub-agent-tools-V7b3T9_s.js +39 -0
- package/dist/tool-policy-DNvNRnve.js +189 -0
- package/dist/tts-elevenlabs-BUOGKL-k.js +61 -0
- package/dist/update-check-BD4qH7Am.js +81 -0
- package/dist/vision-DRq-f-Dj.js +121 -0
- package/dist/vision-tools-CFZEpQKm.js +5 -0
- package/dist/vision-tools-CQnBI9aa.js +51 -0
- package/dist/voice-transcription-CbQBToY0.js +138 -0
- package/dist/voice-transcription-CgWq54hn.js +138 -0
- package/dist/website-watch-tools-Bk_TnwuE.js +5 -0
- package/dist/website-watch-tools-DraMPxdl.js +139 -0
- package/package.json +1 -1
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const chalk = require_chunk.__toESM(require("chalk"));
|
|
3
|
+
const ora = require_chunk.__toESM(require("ora"));
|
|
4
|
+
const fs_extra = require_chunk.__toESM(require("fs-extra"));
|
|
5
|
+
const path = require_chunk.__toESM(require("path"));
|
|
6
|
+
const os = require_chunk.__toESM(require("os"));
|
|
7
|
+
|
|
8
|
+
//#region src/security/audit.ts
|
|
9
|
+
const HC_DIR = path.default.join(os.default.homedir(), ".hyperclaw");
|
|
10
|
+
async function checkFilePermissions() {
|
|
11
|
+
const findings = [];
|
|
12
|
+
const sensitiveFiles = [
|
|
13
|
+
path.default.join(HC_DIR, "hyperclaw.json"),
|
|
14
|
+
path.default.join(HC_DIR, "auth.json"),
|
|
15
|
+
path.default.join(HC_DIR, ".env"),
|
|
16
|
+
path.default.join(HC_DIR, "AGENTS.md")
|
|
17
|
+
];
|
|
18
|
+
for (const f of sensitiveFiles) if (await fs_extra.default.pathExists(f)) {
|
|
19
|
+
const stat = await fs_extra.default.stat(f);
|
|
20
|
+
if ((stat.mode & 63) !== 0) findings.push({
|
|
21
|
+
severity: "high",
|
|
22
|
+
category: "File Permissions",
|
|
23
|
+
title: `Unsafe permissions on ${path.default.basename(f)}`,
|
|
24
|
+
detail: `Mode ${(stat.mode & 511).toString(8)} allows group/other read`,
|
|
25
|
+
remediation: `chmod 600 ${f}`,
|
|
26
|
+
cvss: 7.5
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
const credsDir = path.default.join(HC_DIR, "credentials");
|
|
30
|
+
if (await fs_extra.default.pathExists(credsDir)) {
|
|
31
|
+
const stat = await fs_extra.default.stat(credsDir);
|
|
32
|
+
if ((stat.mode & 63) !== 0) findings.push({
|
|
33
|
+
severity: "critical",
|
|
34
|
+
category: "File Permissions",
|
|
35
|
+
title: "credentials/ directory is world-readable",
|
|
36
|
+
detail: `Mode ${(stat.mode & 511).toString(8)} — all credential files are exposed`,
|
|
37
|
+
remediation: `chmod 700 ${credsDir}`,
|
|
38
|
+
cvss: 9.1
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
return findings;
|
|
42
|
+
}
|
|
43
|
+
async function checkGatewayConfig() {
|
|
44
|
+
const findings = [];
|
|
45
|
+
let cfg = null;
|
|
46
|
+
try {
|
|
47
|
+
cfg = await fs_extra.default.readJson(path.default.join(HC_DIR, "hyperclaw.json"));
|
|
48
|
+
} catch {
|
|
49
|
+
return findings;
|
|
50
|
+
}
|
|
51
|
+
const token = cfg.gateway?.authToken;
|
|
52
|
+
if (!token) findings.push({
|
|
53
|
+
severity: "critical",
|
|
54
|
+
category: "Authentication",
|
|
55
|
+
title: "Gateway auth token not set",
|
|
56
|
+
detail: "Any client can connect to the gateway without authentication",
|
|
57
|
+
remediation: "hyperclaw gateway config --set-token",
|
|
58
|
+
cvss: 9.8
|
|
59
|
+
});
|
|
60
|
+
else if (token.length < 32) findings.push({
|
|
61
|
+
severity: "high",
|
|
62
|
+
category: "Authentication",
|
|
63
|
+
title: "Gateway auth token is too short",
|
|
64
|
+
detail: `Token is ${token.length} chars — minimum recommended is 32`,
|
|
65
|
+
remediation: "hyperclaw gateway config --regenerate-token",
|
|
66
|
+
cvss: 7.3
|
|
67
|
+
});
|
|
68
|
+
if (cfg.gateway?.bind === "0.0.0.0") findings.push({
|
|
69
|
+
severity: "medium",
|
|
70
|
+
category: "Network Exposure",
|
|
71
|
+
title: "Gateway bound to all interfaces (0.0.0.0)",
|
|
72
|
+
detail: "Gateway is accessible from the local network. Ensure auth token is strong.",
|
|
73
|
+
remediation: "Use 127.0.0.1 unless you need LAN access",
|
|
74
|
+
cvss: 5.3
|
|
75
|
+
});
|
|
76
|
+
if (cfg.gateway?.tailscaleExposure === "funnel") findings.push({
|
|
77
|
+
severity: "medium",
|
|
78
|
+
category: "Network Exposure",
|
|
79
|
+
title: "Gateway exposed via Tailscale Funnel (public internet)",
|
|
80
|
+
detail: "Your gateway is reachable from the public internet via Tailscale Funnel",
|
|
81
|
+
remediation: "Ensure auth token is strong and DM policies are restrictive",
|
|
82
|
+
cvss: 5.8
|
|
83
|
+
});
|
|
84
|
+
for (const [ch, chCfg] of Object.entries(cfg.channelConfigs || {})) {
|
|
85
|
+
const policy = chCfg?.dmPolicy?.policy;
|
|
86
|
+
if (policy === "open") findings.push({
|
|
87
|
+
severity: "high",
|
|
88
|
+
category: "DM Policy",
|
|
89
|
+
title: `DM policy is "open" on ${ch}`,
|
|
90
|
+
detail: "Anyone can send DMs to your agent. This is a prompt injection risk.",
|
|
91
|
+
remediation: `hyperclaw channels add ${ch} # and choose pairing or allowlist`,
|
|
92
|
+
cvss: 7.1
|
|
93
|
+
});
|
|
94
|
+
if (policy === "allowlist") {
|
|
95
|
+
const allowFrom = chCfg?.dmPolicy?.allowFrom || [];
|
|
96
|
+
if (allowFrom.length === 0) findings.push({
|
|
97
|
+
severity: "high",
|
|
98
|
+
category: "DM Policy",
|
|
99
|
+
title: `Empty allowlist on ${ch} — DMs are silently dropped`,
|
|
100
|
+
detail: `allowFrom is [] — no one can reach your agent on ${ch}`,
|
|
101
|
+
remediation: `hyperclaw pairing approve ${ch} <code>`,
|
|
102
|
+
cvss: 4
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return findings;
|
|
107
|
+
}
|
|
108
|
+
async function checkSecretsInPrompts() {
|
|
109
|
+
const findings = [];
|
|
110
|
+
const secretPatterns = [
|
|
111
|
+
{
|
|
112
|
+
pattern: /sk-[a-zA-Z0-9]{20,}/,
|
|
113
|
+
name: "OpenAI API key"
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
pattern: /tvly-[a-zA-Z0-9]{20,}/,
|
|
117
|
+
name: "Tavily API key"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
pattern: /xai-[a-zA-Z0-9]{20,}/,
|
|
121
|
+
name: "xAI API key"
|
|
122
|
+
},
|
|
123
|
+
{
|
|
124
|
+
pattern: /ghp_[a-zA-Z0-9]{36}/,
|
|
125
|
+
name: "GitHub PAT"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
pattern: /or-[a-zA-Z0-9]{20,}/,
|
|
129
|
+
name: "OpenRouter API key"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
pattern: /[a-f0-9]{64}/,
|
|
133
|
+
name: "Potential hex secret"
|
|
134
|
+
}
|
|
135
|
+
];
|
|
136
|
+
const filesToScan = [
|
|
137
|
+
path.default.join(HC_DIR, "AGENTS.md"),
|
|
138
|
+
path.default.join(HC_DIR, "MEMORY.md"),
|
|
139
|
+
path.default.join(HC_DIR, "hyperclaw.json")
|
|
140
|
+
];
|
|
141
|
+
for (const f of filesToScan) {
|
|
142
|
+
if (!await fs_extra.default.pathExists(f)) continue;
|
|
143
|
+
const content = await fs_extra.default.readFile(f, "utf8");
|
|
144
|
+
for (const { pattern, name } of secretPatterns) if (pattern.test(content)) findings.push({
|
|
145
|
+
severity: "critical",
|
|
146
|
+
category: "Secret Exposure",
|
|
147
|
+
title: `${name} potentially embedded in ${path.default.basename(f)}`,
|
|
148
|
+
detail: `Found pattern matching ${name} in plaintext file. Secrets in prompts are a serious risk.`,
|
|
149
|
+
remediation: `Remove the secret and use: hyperclaw secrets set KEY=value`,
|
|
150
|
+
cvss: 9.1
|
|
151
|
+
});
|
|
152
|
+
}
|
|
153
|
+
return findings;
|
|
154
|
+
}
|
|
155
|
+
async function deepScan() {
|
|
156
|
+
const findings = [];
|
|
157
|
+
const hubState = path.default.join(HC_DIR, "hub-state.json");
|
|
158
|
+
if (await fs_extra.default.pathExists(hubState)) {
|
|
159
|
+
const state = await fs_extra.default.readJson(hubState);
|
|
160
|
+
const dangerous = state.installed?.filter((s) => s.risk === "dangerous") || [];
|
|
161
|
+
for (const s of dangerous) findings.push({
|
|
162
|
+
severity: "critical",
|
|
163
|
+
category: "Installed Skills",
|
|
164
|
+
title: `Dangerous skill installed: ${s.name}`,
|
|
165
|
+
detail: s.riskReason || "Skill is flagged as dangerous",
|
|
166
|
+
remediation: `hyperclaw hub --uninstall ${s.id}`,
|
|
167
|
+
cvss: 8.5
|
|
168
|
+
});
|
|
169
|
+
}
|
|
170
|
+
let cfg = null;
|
|
171
|
+
try {
|
|
172
|
+
cfg = await fs_extra.default.readJson(path.default.join(HC_DIR, "hyperclaw.json"));
|
|
173
|
+
} catch {}
|
|
174
|
+
if (cfg?.gateway?.authToken) {
|
|
175
|
+
const token = cfg.gateway.authToken;
|
|
176
|
+
const entropy = estimateEntropy(token);
|
|
177
|
+
if (entropy < 3.5) findings.push({
|
|
178
|
+
severity: "high",
|
|
179
|
+
category: "Token Quality",
|
|
180
|
+
title: "Gateway auth token has low entropy",
|
|
181
|
+
detail: `Estimated entropy: ${entropy.toFixed(2)} bits/char — token may be guessable`,
|
|
182
|
+
remediation: "hyperclaw gateway config --regenerate-token",
|
|
183
|
+
cvss: 6.5
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
return findings;
|
|
187
|
+
}
|
|
188
|
+
function estimateEntropy(str) {
|
|
189
|
+
const freq = /* @__PURE__ */ new Map();
|
|
190
|
+
for (const ch of str) freq.set(ch, (freq.get(ch) || 0) + 1);
|
|
191
|
+
let entropy = 0;
|
|
192
|
+
for (const count of freq.values()) {
|
|
193
|
+
const p = count / str.length;
|
|
194
|
+
entropy -= p * Math.log2(p);
|
|
195
|
+
}
|
|
196
|
+
return entropy;
|
|
197
|
+
}
|
|
198
|
+
const SEVERITY_ORDER = [
|
|
199
|
+
"critical",
|
|
200
|
+
"high",
|
|
201
|
+
"medium",
|
|
202
|
+
"low",
|
|
203
|
+
"info"
|
|
204
|
+
];
|
|
205
|
+
const SEVERITY_COLOR = {
|
|
206
|
+
critical: chalk.default.bgRed.white.bold,
|
|
207
|
+
high: chalk.default.red,
|
|
208
|
+
medium: chalk.default.yellow,
|
|
209
|
+
low: chalk.default.cyan,
|
|
210
|
+
info: chalk.default.gray
|
|
211
|
+
};
|
|
212
|
+
async function runSecurityAudit(deep = false) {
|
|
213
|
+
console.log(chalk.default.bold.cyan("\n 🔐 HYPERCLAW SECURITY AUDIT\n"));
|
|
214
|
+
const spinner = (0, ora.default)("Running security checks...").start();
|
|
215
|
+
const allFindings = [
|
|
216
|
+
...await checkFilePermissions(),
|
|
217
|
+
...await checkGatewayConfig(),
|
|
218
|
+
...await checkSecretsInPrompts(),
|
|
219
|
+
...deep ? await deepScan() : []
|
|
220
|
+
];
|
|
221
|
+
spinner.stop();
|
|
222
|
+
if (deep) console.log(chalk.default.gray(" Mode: DEEP SCAN\n"));
|
|
223
|
+
else console.log(chalk.default.gray(" Mode: standard (run with --deep for full scan)\n"));
|
|
224
|
+
if (allFindings.length === 0) {
|
|
225
|
+
console.log(chalk.default.green(" ✔ No security issues found!\n"));
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
allFindings.sort((a, b) => SEVERITY_ORDER.indexOf(a.severity) - SEVERITY_ORDER.indexOf(b.severity));
|
|
229
|
+
for (const f of allFindings) {
|
|
230
|
+
const color = SEVERITY_COLOR[f.severity];
|
|
231
|
+
const badge = color(` ${f.severity.toUpperCase()} `);
|
|
232
|
+
const cvss = f.cvss ? chalk.default.gray(` CVSS ${f.cvss}`) : "";
|
|
233
|
+
console.log(` ${badge}${cvss} ${chalk.default.white(f.title)}`);
|
|
234
|
+
console.log(` ${chalk.default.gray(`Category: ${f.category}`)}`);
|
|
235
|
+
console.log(` ${chalk.default.gray(f.detail)}`);
|
|
236
|
+
console.log(` ${chalk.default.cyan("Fix: " + f.remediation)}`);
|
|
237
|
+
console.log();
|
|
238
|
+
}
|
|
239
|
+
const counts = SEVERITY_ORDER.reduce((acc, sev) => {
|
|
240
|
+
acc[sev] = allFindings.filter((f) => f.severity === sev).length;
|
|
241
|
+
return acc;
|
|
242
|
+
}, {});
|
|
243
|
+
console.log(` ${chalk.default.bold("Summary:")} ${chalk.default.bgRed.white.bold(` ${counts.critical} CRITICAL `)} ${chalk.default.red(`${counts.high} high`)} ${chalk.default.yellow(`${counts.medium} medium`)} ${chalk.default.cyan(`${counts.low} low`)}\n`);
|
|
244
|
+
if (!deep) console.log(chalk.default.gray(" Run: hyperclaw security audit --deep for full credential entropy scan\n"));
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//#endregion
|
|
248
|
+
exports.runSecurityAudit = runSecurityAudit;
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
const require_env_resolve = require('./env-resolve-szSWl0UF.js');
|
|
3
|
+
const https = require_chunk.__toESM(require("https"));
|
|
4
|
+
|
|
5
|
+
//#region packages/core/src/agent/bounty-tools.ts
|
|
6
|
+
function fetchJson(url, auth) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
const u = new URL(url);
|
|
9
|
+
const opts = {
|
|
10
|
+
hostname: u.hostname,
|
|
11
|
+
port: 443,
|
|
12
|
+
path: u.pathname + u.search,
|
|
13
|
+
method: "GET",
|
|
14
|
+
headers: { "Accept": "application/json" }
|
|
15
|
+
};
|
|
16
|
+
if (auth) {
|
|
17
|
+
const h = opts.headers;
|
|
18
|
+
if (auth.startsWith("Basic ") || auth.startsWith("Token ") || auth.startsWith("Bearer ")) h["Authorization"] = auth;
|
|
19
|
+
else h["Authorization"] = `Basic ${Buffer.from(auth).toString("base64")}`;
|
|
20
|
+
}
|
|
21
|
+
https.default.get(opts, (res) => {
|
|
22
|
+
let data = "";
|
|
23
|
+
res.on("data", (c) => data += c);
|
|
24
|
+
res.on("end", () => {
|
|
25
|
+
try {
|
|
26
|
+
resolve(JSON.parse(data || "{}"));
|
|
27
|
+
} catch {
|
|
28
|
+
resolve({});
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
}).on("error", reject);
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
function getBountyTools(cfg) {
|
|
35
|
+
const hackeroneKey = require_env_resolve.resolveServiceApiKey("hackerone", cfg);
|
|
36
|
+
const bugcrowdKey = require_env_resolve.resolveServiceApiKey("bugcrowd", cfg);
|
|
37
|
+
const synackKey = require_env_resolve.resolveServiceApiKey("synack", cfg);
|
|
38
|
+
const tools = [];
|
|
39
|
+
if (hackeroneKey) tools.push({
|
|
40
|
+
name: "hackerone_list_programs",
|
|
41
|
+
description: "List HackerOne programs you have access to. Use for bug bounty research.",
|
|
42
|
+
input_schema: {
|
|
43
|
+
type: "object",
|
|
44
|
+
properties: { limit: {
|
|
45
|
+
type: "string",
|
|
46
|
+
description: "Max programs (default 20)"
|
|
47
|
+
} }
|
|
48
|
+
},
|
|
49
|
+
handler: async (input) => {
|
|
50
|
+
try {
|
|
51
|
+
const auth = Buffer.from(hackeroneKey.includes(":") ? hackeroneKey : `:${hackeroneKey}`).toString("base64");
|
|
52
|
+
const body = await fetchJson("https://api.hackerone.com/v1/hackers/programs", `Basic ${auth}`);
|
|
53
|
+
const progs = body.data || [];
|
|
54
|
+
const limit = parseInt(input.limit || "20", 10);
|
|
55
|
+
return progs.slice(0, limit).map((p) => {
|
|
56
|
+
const attrs = p.attributes;
|
|
57
|
+
return `- ${attrs?.name || p.id} (${attrs?.state || "?"})`;
|
|
58
|
+
}).join("\n") || "No programs found.";
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return `Error: ${e.message}. Check HackerOne API key (username:token).`;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
});
|
|
64
|
+
if (bugcrowdKey) tools.push({
|
|
65
|
+
name: "bugcrowd_list_programs",
|
|
66
|
+
description: "List Bugcrowd programs. Use for bug bounty research.",
|
|
67
|
+
input_schema: {
|
|
68
|
+
type: "object",
|
|
69
|
+
properties: { limit: {
|
|
70
|
+
type: "string",
|
|
71
|
+
description: "Max programs (default 20)"
|
|
72
|
+
} }
|
|
73
|
+
},
|
|
74
|
+
handler: async (input) => {
|
|
75
|
+
try {
|
|
76
|
+
const auth = bugcrowdKey.startsWith("Token ") ? bugcrowdKey : `Token ${bugcrowdKey}`;
|
|
77
|
+
const body = await fetchJson("https://api.bugcrowd.com/programs", auth);
|
|
78
|
+
const progs = (body && typeof body === "object" && "data" in body ? body.data : body) || [];
|
|
79
|
+
const arr = Array.isArray(progs) ? progs : [progs];
|
|
80
|
+
const limit = parseInt(input.limit || "20", 10);
|
|
81
|
+
return arr.slice(0, limit).map((p) => {
|
|
82
|
+
const attrs = p.attributes;
|
|
83
|
+
const name = attrs?.name || p.name || p.id || "?";
|
|
84
|
+
const links = p.links;
|
|
85
|
+
const linkUrl = links?.self || p.url || "";
|
|
86
|
+
return `- ${name} ${linkUrl ? `(${linkUrl})` : ""}`;
|
|
87
|
+
}).join("\n") || JSON.stringify(body).slice(0, 500);
|
|
88
|
+
} catch (e) {
|
|
89
|
+
return `Error: ${e.message}. Check Bugcrowd API token.`;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
if (synackKey) tools.push({
|
|
94
|
+
name: "synack_list_targets",
|
|
95
|
+
description: "List Synack targets you have access to. Use for bug bounty research.",
|
|
96
|
+
input_schema: {
|
|
97
|
+
type: "object",
|
|
98
|
+
properties: {}
|
|
99
|
+
},
|
|
100
|
+
handler: async () => {
|
|
101
|
+
try {
|
|
102
|
+
const body = await fetchJson("https://api.synack.com/api/targets", `Bearer ${synackKey}`);
|
|
103
|
+
const targets = body.targets || body || [];
|
|
104
|
+
const arr = Array.isArray(targets) ? targets : [targets];
|
|
105
|
+
return arr.slice(0, 20).map((t) => `- ${t.name || t.slug || t.id || "?"}`).join("\n") || "No targets or check Synack API.";
|
|
106
|
+
} catch (e) {
|
|
107
|
+
return `Error: ${e.message}. Check Synack API token.`;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
const apiKeys = cfg?.skills?.apiKeys ?? {};
|
|
112
|
+
const genericServiceIds = Object.keys(apiKeys).filter((id) => !KNOWN_BOUNTY_SERVICES.includes(id.toLowerCase()));
|
|
113
|
+
if (genericServiceIds.length > 0) tools.push({
|
|
114
|
+
name: "call_service_api",
|
|
115
|
+
description: `Call external API for services with configured keys. Available: ${genericServiceIds.join(", ")}. Use when the user needs to query an API for a service they've added (Stripe, GitHub, custom APIs, etc).`,
|
|
116
|
+
input_schema: {
|
|
117
|
+
type: "object",
|
|
118
|
+
properties: {
|
|
119
|
+
service_id: {
|
|
120
|
+
type: "string",
|
|
121
|
+
description: `Service id from configured keys: ${genericServiceIds.join(", ")}`
|
|
122
|
+
},
|
|
123
|
+
url: {
|
|
124
|
+
type: "string",
|
|
125
|
+
description: "Full API URL (e.g. https://api.example.com/v1/resource) or path if baseUrl configured"
|
|
126
|
+
},
|
|
127
|
+
method: {
|
|
128
|
+
type: "string",
|
|
129
|
+
description: "HTTP method",
|
|
130
|
+
enum: [
|
|
131
|
+
"GET",
|
|
132
|
+
"POST",
|
|
133
|
+
"PUT",
|
|
134
|
+
"PATCH",
|
|
135
|
+
"DELETE"
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
body: {
|
|
139
|
+
type: "object",
|
|
140
|
+
description: "Request body for POST/PUT/PATCH (optional)"
|
|
141
|
+
}
|
|
142
|
+
},
|
|
143
|
+
required: ["service_id", "url"]
|
|
144
|
+
},
|
|
145
|
+
handler: async (input) => {
|
|
146
|
+
const serviceId = String(input.service_id || "").trim().toLowerCase();
|
|
147
|
+
const url = String(input.url || "");
|
|
148
|
+
const method = String(input.method || "GET").toUpperCase();
|
|
149
|
+
const key = require_env_resolve.resolveServiceApiKey(serviceId, cfg);
|
|
150
|
+
if (!key) return `Error: No API key for service "${serviceId}". Configure via: hyperclaw config set-service-key ${serviceId} <key>`;
|
|
151
|
+
if (!url || !url.startsWith("http")) return "Error: url must be a full URL (https://...).";
|
|
152
|
+
try {
|
|
153
|
+
const body = await genericHttpRequest(url, method, key, input.body);
|
|
154
|
+
return typeof body === "string" ? body : JSON.stringify(body, null, 2).slice(0, 8e3);
|
|
155
|
+
} catch (e) {
|
|
156
|
+
return `Error: ${e.message}`;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return tools;
|
|
161
|
+
}
|
|
162
|
+
function genericHttpRequest(url, method, apiKey, body) {
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const u = new URL(url);
|
|
165
|
+
const authHeader = apiKey.startsWith("Bearer ") || apiKey.startsWith("Token ") || apiKey.startsWith("Basic ") ? apiKey : `Bearer ${apiKey}`;
|
|
166
|
+
const bodyStr = body && method !== "GET" ? JSON.stringify(body) : void 0;
|
|
167
|
+
const opts = {
|
|
168
|
+
hostname: u.hostname,
|
|
169
|
+
port: u.port || 443,
|
|
170
|
+
path: u.pathname + u.search,
|
|
171
|
+
method,
|
|
172
|
+
headers: {
|
|
173
|
+
"Accept": "application/json",
|
|
174
|
+
"Authorization": authHeader,
|
|
175
|
+
...bodyStr ? {
|
|
176
|
+
"Content-Type": "application/json",
|
|
177
|
+
"Content-Length": Buffer.byteLength(bodyStr)
|
|
178
|
+
} : {}
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const req = https.default.request(opts, (res) => {
|
|
182
|
+
let data = "";
|
|
183
|
+
res.on("data", (c) => {
|
|
184
|
+
data += c.toString();
|
|
185
|
+
});
|
|
186
|
+
res.on("end", () => {
|
|
187
|
+
try {
|
|
188
|
+
resolve(JSON.parse(data || "{}"));
|
|
189
|
+
} catch {
|
|
190
|
+
resolve(data || "{}");
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
});
|
|
194
|
+
req.on("error", reject);
|
|
195
|
+
if (bodyStr) req.write(bodyStr);
|
|
196
|
+
req.end();
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
var KNOWN_BOUNTY_SERVICES;
|
|
200
|
+
var init_bounty_tools = require_chunk.__esm({ "packages/core/src/agent/bounty-tools.ts"() {
|
|
201
|
+
require_env_resolve.init_env_resolve();
|
|
202
|
+
KNOWN_BOUNTY_SERVICES = [
|
|
203
|
+
"hackerone",
|
|
204
|
+
"bugcrowd",
|
|
205
|
+
"synack"
|
|
206
|
+
];
|
|
207
|
+
} });
|
|
208
|
+
|
|
209
|
+
//#endregion
|
|
210
|
+
init_bounty_tools();
|
|
211
|
+
exports.getBountyTools = getBountyTools;
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
const require_chunk = require('./chunk-jS-bbMI5.js');
|
|
2
|
+
|
|
3
|
+
//#region packages/core/src/agent/browser-tools.ts
|
|
4
|
+
async function getBrowser() {
|
|
5
|
+
try {
|
|
6
|
+
const puppeteer = await import("puppeteer").catch(() => null);
|
|
7
|
+
if (!puppeteer) return null;
|
|
8
|
+
if (!sharedBrowser || !sharedBrowser.connected) {
|
|
9
|
+
const execPath = process.env.PUPPETEER_EXECUTABLE_PATH;
|
|
10
|
+
sharedBrowser = await puppeteer.default.launch({
|
|
11
|
+
headless: true,
|
|
12
|
+
executablePath: execPath || void 0,
|
|
13
|
+
args: [
|
|
14
|
+
"--no-sandbox",
|
|
15
|
+
"--disable-setuid-sandbox",
|
|
16
|
+
"--disable-dev-shm-usage"
|
|
17
|
+
]
|
|
18
|
+
});
|
|
19
|
+
sharedPage = await sharedBrowser.newPage();
|
|
20
|
+
await sharedPage.setViewport({
|
|
21
|
+
width: 1280,
|
|
22
|
+
height: 800
|
|
23
|
+
});
|
|
24
|
+
await sharedPage.setUserAgent("Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36");
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
browser: sharedBrowser,
|
|
28
|
+
page: sharedPage
|
|
29
|
+
};
|
|
30
|
+
} catch {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function getBrowserTools() {
|
|
35
|
+
return [{
|
|
36
|
+
name: "browser_snapshot",
|
|
37
|
+
description: "Capture a snapshot of the current browser page (DOM, text, links). Use before interacting. Pass url to navigate first.",
|
|
38
|
+
input_schema: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: { url: {
|
|
41
|
+
type: "string",
|
|
42
|
+
description: "URL to navigate to before snapshot (optional if already on page)"
|
|
43
|
+
} },
|
|
44
|
+
required: []
|
|
45
|
+
},
|
|
46
|
+
handler: async (input) => {
|
|
47
|
+
const bw = await getBrowser();
|
|
48
|
+
if (!bw) return BROWSER_SETUP;
|
|
49
|
+
const { page } = bw;
|
|
50
|
+
try {
|
|
51
|
+
if (input.url) await page.goto(input.url, {
|
|
52
|
+
waitUntil: "domcontentloaded",
|
|
53
|
+
timeout: 15e3
|
|
54
|
+
});
|
|
55
|
+
const snapshot = await page.evaluate(() => {
|
|
56
|
+
const text = document.body?.innerText?.slice(0, 12e3) || "";
|
|
57
|
+
const links = Array.from(document.querySelectorAll("a[href]")).map((a) => ({
|
|
58
|
+
text: (a.textContent || "").trim().slice(0, 80),
|
|
59
|
+
href: a.getAttribute("href")
|
|
60
|
+
})).filter((l) => l.href?.startsWith("http")).slice(0, 50);
|
|
61
|
+
const title = document.title || "";
|
|
62
|
+
const url = window.location.href;
|
|
63
|
+
return JSON.stringify({
|
|
64
|
+
title,
|
|
65
|
+
url,
|
|
66
|
+
text,
|
|
67
|
+
links
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
return snapshot;
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return `Browser snapshot error: ${e.message}`;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
}, {
|
|
76
|
+
name: "browser_action",
|
|
77
|
+
description: "Perform an action in the browser: click, type, scroll, navigate. Use after browser_snapshot.",
|
|
78
|
+
input_schema: {
|
|
79
|
+
type: "object",
|
|
80
|
+
properties: {
|
|
81
|
+
action: {
|
|
82
|
+
type: "string",
|
|
83
|
+
description: "Action: click, type, scroll, navigate",
|
|
84
|
+
enum: [
|
|
85
|
+
"click",
|
|
86
|
+
"type",
|
|
87
|
+
"scroll",
|
|
88
|
+
"navigate"
|
|
89
|
+
]
|
|
90
|
+
},
|
|
91
|
+
selector: {
|
|
92
|
+
type: "string",
|
|
93
|
+
description: "CSS selector or link text to match (for click/type)"
|
|
94
|
+
},
|
|
95
|
+
value: {
|
|
96
|
+
type: "string",
|
|
97
|
+
description: "For type: text to type. For navigate: URL. For scroll: \"up\" or \"down\""
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
required: ["action"]
|
|
101
|
+
},
|
|
102
|
+
handler: async (input) => {
|
|
103
|
+
const action = String(input?.action ?? "");
|
|
104
|
+
const selector = input?.selector;
|
|
105
|
+
const value = input?.value;
|
|
106
|
+
const bw = await getBrowser();
|
|
107
|
+
if (!bw) return BROWSER_SETUP;
|
|
108
|
+
const { page } = bw;
|
|
109
|
+
try {
|
|
110
|
+
switch (action) {
|
|
111
|
+
case "navigate":
|
|
112
|
+
if (!value) return "navigate requires value (URL)";
|
|
113
|
+
await page.goto(value, {
|
|
114
|
+
waitUntil: "domcontentloaded",
|
|
115
|
+
timeout: 15e3
|
|
116
|
+
});
|
|
117
|
+
return `Navigated to ${value}`;
|
|
118
|
+
case "click":
|
|
119
|
+
if (!selector) return "click requires selector";
|
|
120
|
+
const sel = selector.trim();
|
|
121
|
+
const isCss = /^[#.\w\[\]="'\s-:>+~]+$/.test(sel) && (sel.includes("#") || sel.includes(".") || sel.includes("[") || /^[a-z][a-z0-9]*$/i.test(sel));
|
|
122
|
+
if (isCss) {
|
|
123
|
+
await page.waitForSelector(sel, { timeout: 5e3 });
|
|
124
|
+
await page.click(sel);
|
|
125
|
+
return `Clicked ${sel}`;
|
|
126
|
+
}
|
|
127
|
+
const safe = sel.replace(/["']/g, "").slice(0, 100);
|
|
128
|
+
const xpath = `//a[contains(text(),"${safe}")]`;
|
|
129
|
+
const [el] = await page.$x(xpath);
|
|
130
|
+
if (el) {
|
|
131
|
+
await el.click();
|
|
132
|
+
return `Clicked link "${sel}"`;
|
|
133
|
+
}
|
|
134
|
+
const btnXpath = `//button[contains(text(),"${safe}")]`;
|
|
135
|
+
const [btn] = await page.$x(btnXpath);
|
|
136
|
+
if (btn) {
|
|
137
|
+
await btn.click();
|
|
138
|
+
return `Clicked button "${sel}"`;
|
|
139
|
+
}
|
|
140
|
+
throw new Error(`Element not found: ${sel}`);
|
|
141
|
+
case "type":
|
|
142
|
+
if (!selector) return "type requires selector";
|
|
143
|
+
await page.waitForSelector(selector, { timeout: 5e3 });
|
|
144
|
+
await page.type(selector, value || "");
|
|
145
|
+
return `Typed "${value || ""}" into ${selector}`;
|
|
146
|
+
case "scroll":
|
|
147
|
+
const dir = (value || "down").toLowerCase();
|
|
148
|
+
await page.evaluate((d) => {
|
|
149
|
+
window.scrollBy(0, d === "up" ? -400 : 400);
|
|
150
|
+
}, dir);
|
|
151
|
+
return `Scrolled ${dir}`;
|
|
152
|
+
default: return `Unknown action: ${action}`;
|
|
153
|
+
}
|
|
154
|
+
} catch (e) {
|
|
155
|
+
return `Browser action error: ${e.message}`;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}];
|
|
159
|
+
}
|
|
160
|
+
var BROWSER_SETUP, sharedBrowser, sharedPage;
|
|
161
|
+
var init_browser_tools = require_chunk.__esm({ "packages/core/src/agent/browser-tools.ts"() {
|
|
162
|
+
BROWSER_SETUP = "Browser control requires Puppeteer. Install: npm i puppeteer. Then enable in config: browser.enabled: true";
|
|
163
|
+
sharedBrowser = null;
|
|
164
|
+
sharedPage = null;
|
|
165
|
+
} });
|
|
166
|
+
|
|
167
|
+
//#endregion
|
|
168
|
+
Object.defineProperty(exports, 'getBrowserTools', {
|
|
169
|
+
enumerable: true,
|
|
170
|
+
get: function () {
|
|
171
|
+
return getBrowserTools;
|
|
172
|
+
}
|
|
173
|
+
});
|
|
174
|
+
Object.defineProperty(exports, 'init_browser_tools', {
|
|
175
|
+
enumerable: true,
|
|
176
|
+
get: function () {
|
|
177
|
+
return init_browser_tools;
|
|
178
|
+
}
|
|
179
|
+
});
|