@poolzin/pool-bot 2026.3.7 → 2026.3.9
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/CHANGELOG.md +16 -0
- package/dist/.buildstamp +1 -1
- package/dist/agents/error-classifier.js +302 -0
- package/dist/agents/skills/security.js +217 -0
- package/dist/build-info.json +3 -3
- package/dist/cli/lazy-commands.example.js +113 -0
- package/dist/cli/lazy-commands.js +329 -0
- package/dist/cli/program/command-registry.js +13 -0
- package/dist/cli/program/register.skills.js +4 -0
- package/dist/config/config.js +1 -0
- package/dist/config/secrets-integration.js +88 -0
- package/dist/context-engine/index.js +33 -0
- package/dist/context-engine/legacy.js +181 -0
- package/dist/context-engine/registry.js +86 -0
- package/dist/context-engine/summarizing.js +293 -0
- package/dist/context-engine/types.js +7 -0
- package/dist/infra/abort-pattern.js +106 -0
- package/dist/infra/retry.js +94 -0
- package/dist/secrets/index.js +28 -0
- package/dist/secrets/resolver.js +185 -0
- package/dist/secrets/runtime.js +142 -0
- package/dist/secrets/types.js +11 -0
- package/dist/security/dangerous-tools.js +80 -0
- package/dist/security/types.js +12 -0
- package/dist/skills/commands.js +351 -0
- package/dist/skills/index.js +167 -0
- package/dist/skills/loader.js +282 -0
- package/dist/skills/parser.js +461 -0
- package/dist/skills/registry.js +397 -0
- package/dist/skills/security.js +318 -0
- package/dist/skills/types.js +21 -0
- package/dist/test-utils/index.js +219 -0
- package/dist/tui/index.js +595 -0
- package/docs/INTEGRATION_PLAN.md +475 -0
- package/docs/INTEGRATION_SUMMARY.md +215 -0
- package/docs/integrations/HEXSTRIKE_PLAN.md +796 -0
- package/docs/integrations/INTEGRATION_PLAN.md +424 -0
- package/docs/integrations/PAGE_AGENT_PLAN.md +370 -0
- package/docs/integrations/XYOPS_PLAN.md +978 -0
- package/docs/skills/IMPLEMENTATION_SUMMARY.md +145 -0
- package/docs/skills/SKILL.md +524 -0
- package/docs/skills.md +405 -0
- package/package.json +1 -1
- package/skills/example-skill/SKILL.md +195 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import { resolve as resolvePath } from "node:path";
|
|
3
|
+
/**
|
|
4
|
+
* Parse a secret reference string
|
|
5
|
+
* Formats:
|
|
6
|
+
* - env:default:KEY - Environment variable
|
|
7
|
+
* - env:production:KEY - Environment variable from specific source
|
|
8
|
+
* - file:/path/to/secrets.json:KEY - JSON file
|
|
9
|
+
* - file:/path/to/secrets.json - Entire JSON file as secret
|
|
10
|
+
* - raw value - Direct value (backward compatibility)
|
|
11
|
+
*/
|
|
12
|
+
export function parseSecretRef(value) {
|
|
13
|
+
// Check if it's a reference
|
|
14
|
+
const envMatch = value.match(/^env:([^:]+):(.+)$/);
|
|
15
|
+
if (envMatch) {
|
|
16
|
+
return {
|
|
17
|
+
isRef: true,
|
|
18
|
+
ref: {
|
|
19
|
+
provider: "env",
|
|
20
|
+
source: envMatch[1],
|
|
21
|
+
key: envMatch[2],
|
|
22
|
+
},
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
const fileMatch = value.match(/^file:([^:]+):(.+)$/);
|
|
26
|
+
if (fileMatch) {
|
|
27
|
+
return {
|
|
28
|
+
isRef: true,
|
|
29
|
+
ref: {
|
|
30
|
+
provider: "file",
|
|
31
|
+
source: fileMatch[1],
|
|
32
|
+
key: fileMatch[2],
|
|
33
|
+
},
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
const fileOnlyMatch = value.match(/^file:(.+)$/);
|
|
37
|
+
if (fileOnlyMatch) {
|
|
38
|
+
return {
|
|
39
|
+
isRef: true,
|
|
40
|
+
ref: {
|
|
41
|
+
provider: "file",
|
|
42
|
+
source: fileOnlyMatch[1],
|
|
43
|
+
key: "", // Entire file
|
|
44
|
+
},
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
// Not a reference - treat as direct value
|
|
48
|
+
return {
|
|
49
|
+
isRef: false,
|
|
50
|
+
rawValue: value,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Resolve a secret reference to its value
|
|
55
|
+
*/
|
|
56
|
+
export function resolveSecret(ref, options = {}) {
|
|
57
|
+
const warnings = [];
|
|
58
|
+
switch (ref.provider) {
|
|
59
|
+
case "env":
|
|
60
|
+
return resolveEnvSecret(ref, options, warnings);
|
|
61
|
+
case "file":
|
|
62
|
+
return resolveFileSecret(ref, options, warnings);
|
|
63
|
+
case "exec":
|
|
64
|
+
return resolveExecSecret(ref, options, warnings);
|
|
65
|
+
default:
|
|
66
|
+
return {
|
|
67
|
+
value: undefined,
|
|
68
|
+
resolved: false,
|
|
69
|
+
warnings: [`Unknown provider: ${String(ref.provider)}`],
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
function resolveEnvSecret(ref, options, warnings) {
|
|
74
|
+
const value = process.env[ref.key];
|
|
75
|
+
if (value === undefined) {
|
|
76
|
+
if (!options.allowMissing) {
|
|
77
|
+
warnings.push(`Environment variable ${ref.key} is not set`);
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
value: undefined,
|
|
81
|
+
resolved: false,
|
|
82
|
+
source: `env:${ref.source}:${ref.key}`,
|
|
83
|
+
warnings,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
value,
|
|
88
|
+
resolved: true,
|
|
89
|
+
source: `env:${ref.source}:${ref.key}`,
|
|
90
|
+
warnings,
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function resolveFileSecret(ref, options, warnings) {
|
|
94
|
+
try {
|
|
95
|
+
const filePath = resolvePath(ref.source);
|
|
96
|
+
// Path traversal protection
|
|
97
|
+
if (options.basePath && !filePath.startsWith(resolvePath(options.basePath))) {
|
|
98
|
+
warnings.push(`Secret file path outside allowed directory: ${ref.source}`);
|
|
99
|
+
return {
|
|
100
|
+
value: undefined,
|
|
101
|
+
resolved: false,
|
|
102
|
+
source: `file:${ref.source}`,
|
|
103
|
+
warnings,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
const content = readFileSync(filePath, "utf-8");
|
|
107
|
+
// If key is empty, return entire file content
|
|
108
|
+
if (!ref.key) {
|
|
109
|
+
return {
|
|
110
|
+
value: content.trim(),
|
|
111
|
+
resolved: true,
|
|
112
|
+
source: `file:${ref.source}`,
|
|
113
|
+
warnings,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
// Try to parse as JSON
|
|
117
|
+
try {
|
|
118
|
+
const json = JSON.parse(content);
|
|
119
|
+
if (typeof json === "object" && json !== null) {
|
|
120
|
+
if (ref.key in json) {
|
|
121
|
+
const value = String(json[ref.key]);
|
|
122
|
+
return {
|
|
123
|
+
value,
|
|
124
|
+
resolved: true,
|
|
125
|
+
source: `file:${ref.source}:${ref.key}`,
|
|
126
|
+
warnings,
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
warnings.push(`Key "${ref.key}" not found in JSON file ${ref.source}`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
// Not valid JSON, treat as plain text
|
|
134
|
+
warnings.push(`File ${ref.source} is not valid JSON`);
|
|
135
|
+
}
|
|
136
|
+
if (!options.allowMissing) {
|
|
137
|
+
warnings.push(`Could not resolve secret from file: ${ref.source}:${ref.key}`);
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
value: undefined,
|
|
141
|
+
resolved: false,
|
|
142
|
+
source: `file:${ref.source}:${ref.key}`,
|
|
143
|
+
warnings,
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
148
|
+
warnings.push(`Failed to read file ${ref.source}: ${message}`);
|
|
149
|
+
return {
|
|
150
|
+
value: undefined,
|
|
151
|
+
resolved: false,
|
|
152
|
+
source: `file:${ref.source}:${ref.key}`,
|
|
153
|
+
warnings,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
function resolveExecSecret(ref, _options, warnings) {
|
|
158
|
+
// Exec provider not yet implemented
|
|
159
|
+
warnings.push("Exec provider is not yet implemented");
|
|
160
|
+
return {
|
|
161
|
+
value: undefined,
|
|
162
|
+
resolved: false,
|
|
163
|
+
source: `exec:${ref.source}:${ref.key}`,
|
|
164
|
+
warnings,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
/**
|
|
168
|
+
* Resolve a value that may be a secret reference or direct value
|
|
169
|
+
*/
|
|
170
|
+
export function resolveValue(value, options = {}) {
|
|
171
|
+
const parsed = parseSecretRef(value);
|
|
172
|
+
if (parsed.isRef && parsed.ref) {
|
|
173
|
+
return resolveSecret(parsed.ref, options);
|
|
174
|
+
}
|
|
175
|
+
// Direct value
|
|
176
|
+
const warnings = [];
|
|
177
|
+
if (options.warnOnDirectValues && parsed.rawValue !== undefined) {
|
|
178
|
+
warnings.push("Using direct secret value. Consider using env:default:KEY or file:/path:key format for better security");
|
|
179
|
+
}
|
|
180
|
+
return {
|
|
181
|
+
value: parsed.rawValue,
|
|
182
|
+
resolved: parsed.rawValue !== undefined,
|
|
183
|
+
warnings,
|
|
184
|
+
};
|
|
185
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { resolveValue } from "./resolver.js";
|
|
2
|
+
/**
|
|
3
|
+
* Runtime snapshot for resolved secrets
|
|
4
|
+
*
|
|
5
|
+
* This class manages a snapshot of resolved secrets that is created
|
|
6
|
+
* at startup and remains immutable during runtime for security.
|
|
7
|
+
*/
|
|
8
|
+
export class SecretsRuntime {
|
|
9
|
+
snapshot;
|
|
10
|
+
config;
|
|
11
|
+
constructor(config = {}) {
|
|
12
|
+
this.config = {
|
|
13
|
+
allowDirectValues: true,
|
|
14
|
+
warnOnDirectValues: true,
|
|
15
|
+
failOnUnresolved: false,
|
|
16
|
+
...config,
|
|
17
|
+
};
|
|
18
|
+
this.snapshot = {
|
|
19
|
+
secrets: new Map(),
|
|
20
|
+
unresolved: [],
|
|
21
|
+
warnings: [],
|
|
22
|
+
timestamp: new Date(),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Resolve a single secret and add to snapshot
|
|
27
|
+
*/
|
|
28
|
+
resolve(key, value) {
|
|
29
|
+
const result = resolveValue(value, {
|
|
30
|
+
allowDirectValues: this.config.allowDirectValues,
|
|
31
|
+
warnOnDirectValues: this.config.warnOnDirectValues,
|
|
32
|
+
allowMissing: !this.config.failOnUnresolved,
|
|
33
|
+
});
|
|
34
|
+
if (result.resolved && result.value !== undefined) {
|
|
35
|
+
this.snapshot.secrets.set(key, {
|
|
36
|
+
value: result.value,
|
|
37
|
+
source: result.source || "direct",
|
|
38
|
+
warnings: result.warnings,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
this.snapshot.unresolved.push(key);
|
|
43
|
+
}
|
|
44
|
+
if (result.warnings.length > 0) {
|
|
45
|
+
this.snapshot.warnings.push(...result.warnings);
|
|
46
|
+
}
|
|
47
|
+
return result;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Resolve multiple secrets from an object
|
|
51
|
+
*/
|
|
52
|
+
resolveObject(obj) {
|
|
53
|
+
const resolved = {};
|
|
54
|
+
for (const [key, value] of Object.entries(obj)) {
|
|
55
|
+
const result = this.resolve(key, value);
|
|
56
|
+
if (result.value !== undefined) {
|
|
57
|
+
resolved[key] = result.value;
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return resolved;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Get a resolved secret value
|
|
64
|
+
*/
|
|
65
|
+
get(key) {
|
|
66
|
+
return this.snapshot.secrets.get(key)?.value;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a secret is resolved
|
|
70
|
+
*/
|
|
71
|
+
has(key) {
|
|
72
|
+
return this.snapshot.secrets.has(key);
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Get full snapshot info for a secret
|
|
76
|
+
*/
|
|
77
|
+
getInfo(key) {
|
|
78
|
+
return this.snapshot.secrets.get(key);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Get all unresolved keys
|
|
82
|
+
*/
|
|
83
|
+
getUnresolved() {
|
|
84
|
+
return [...this.snapshot.unresolved];
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get all warnings
|
|
88
|
+
*/
|
|
89
|
+
getWarnings() {
|
|
90
|
+
return [...this.snapshot.warnings];
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get snapshot timestamp
|
|
94
|
+
*/
|
|
95
|
+
getTimestamp() {
|
|
96
|
+
return this.snapshot.timestamp;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Create an immutable snapshot copy
|
|
100
|
+
*/
|
|
101
|
+
createSnapshot() {
|
|
102
|
+
return {
|
|
103
|
+
secrets: new Map(this.snapshot.secrets),
|
|
104
|
+
unresolved: [...this.snapshot.unresolved],
|
|
105
|
+
warnings: [...this.snapshot.warnings],
|
|
106
|
+
timestamp: new Date(this.snapshot.timestamp),
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* Check if there are any unresolved required secrets
|
|
111
|
+
*/
|
|
112
|
+
hasUnresolved() {
|
|
113
|
+
return this.snapshot.unresolved.length > 0;
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Get summary of the runtime state
|
|
117
|
+
*/
|
|
118
|
+
getSummary() {
|
|
119
|
+
return {
|
|
120
|
+
resolved: this.snapshot.secrets.size,
|
|
121
|
+
unresolved: this.snapshot.unresolved.length,
|
|
122
|
+
warnings: this.snapshot.warnings.length,
|
|
123
|
+
timestamp: this.snapshot.timestamp,
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
/**
|
|
128
|
+
* Global runtime instance (lazy initialized)
|
|
129
|
+
*/
|
|
130
|
+
let globalRuntime = null;
|
|
131
|
+
export function getGlobalRuntime() {
|
|
132
|
+
if (!globalRuntime) {
|
|
133
|
+
globalRuntime = new SecretsRuntime();
|
|
134
|
+
}
|
|
135
|
+
return globalRuntime;
|
|
136
|
+
}
|
|
137
|
+
export function setGlobalRuntime(runtime) {
|
|
138
|
+
globalRuntime = runtime;
|
|
139
|
+
}
|
|
140
|
+
export function resetGlobalRuntime() {
|
|
141
|
+
globalRuntime = null;
|
|
142
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Secrets Management System for PoolBot
|
|
3
|
+
*
|
|
4
|
+
* Provides secure secret resolution with support for:
|
|
5
|
+
* - Environment variables (env:default:KEY)
|
|
6
|
+
* - JSON files (file:/path/to/secrets.json)
|
|
7
|
+
* - Direct values (for backward compatibility)
|
|
8
|
+
*
|
|
9
|
+
* Based on OpenClaw patterns but adapted for PoolBot architecture.
|
|
10
|
+
*/
|
|
11
|
+
export {};
|
|
@@ -14,6 +14,20 @@ export const DEFAULT_GATEWAY_HTTP_TOOL_DENY = [
|
|
|
14
14
|
"gateway",
|
|
15
15
|
// Interactive setup — requires terminal QR scan, hangs on HTTP
|
|
16
16
|
"whatsapp_login",
|
|
17
|
+
// Cron automation — scheduling can be used for persistence
|
|
18
|
+
"cron",
|
|
19
|
+
// Exec tools — remote code execution risk
|
|
20
|
+
"exec",
|
|
21
|
+
"shell",
|
|
22
|
+
"spawn",
|
|
23
|
+
// File system mutations
|
|
24
|
+
"fs_write",
|
|
25
|
+
"fs_delete",
|
|
26
|
+
"fs_move",
|
|
27
|
+
"apply_patch",
|
|
28
|
+
// Device pairing
|
|
29
|
+
"device_pair",
|
|
30
|
+
"pair_device",
|
|
17
31
|
];
|
|
18
32
|
/**
|
|
19
33
|
* ACP tools that should always require explicit user approval.
|
|
@@ -30,5 +44,71 @@ export const DANGEROUS_ACP_TOOL_NAMES = [
|
|
|
30
44
|
"fs_delete",
|
|
31
45
|
"fs_move",
|
|
32
46
|
"apply_patch",
|
|
47
|
+
"cron",
|
|
48
|
+
"exec_approved",
|
|
49
|
+
"elevated_exec",
|
|
33
50
|
];
|
|
34
51
|
export const DANGEROUS_ACP_TOOLS = new Set(DANGEROUS_ACP_TOOL_NAMES);
|
|
52
|
+
/**
|
|
53
|
+
* Tool categories by risk level
|
|
54
|
+
*/
|
|
55
|
+
export const TOOL_CATEGORIES = {
|
|
56
|
+
/** Execution tools - can run arbitrary code */
|
|
57
|
+
EXECUTION: ["exec", "spawn", "shell", "exec_approved", "elevated_exec"],
|
|
58
|
+
/** File system mutators - can modify files */
|
|
59
|
+
FILE_MUTATORS: ["fs_write", "fs_delete", "fs_move", "apply_patch", "fs_mkdir", "fs_rename"],
|
|
60
|
+
/** Session orchestrators - can spawn/control sessions */
|
|
61
|
+
SESSION_ORCHESTRATORS: ["sessions_spawn", "sessions_send", "sessions_kill", "sessions_reset"],
|
|
62
|
+
/** Gateway control - can reconfigure gateway */
|
|
63
|
+
GATEWAY_CONTROL: ["gateway", "config_set", "config_reload"],
|
|
64
|
+
/** Automation - can schedule/automate */
|
|
65
|
+
AUTOMATION: ["cron", "hook_register", "automation_create"],
|
|
66
|
+
/** External integrations - can interact with external services */
|
|
67
|
+
EXTERNAL: ["whatsapp_login", "device_pair", "pair_device", "oauth_flow"],
|
|
68
|
+
};
|
|
69
|
+
/**
|
|
70
|
+
* Check if a tool is in a specific category
|
|
71
|
+
*/
|
|
72
|
+
export function isToolInCategory(toolName, category) {
|
|
73
|
+
return TOOL_CATEGORIES[category].includes(toolName);
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Check if a tool is considered dangerous
|
|
77
|
+
*/
|
|
78
|
+
export function isDangerousTool(toolName) {
|
|
79
|
+
return DANGEROUS_ACP_TOOLS.has(toolName.toLowerCase().trim());
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Check if a tool is denied over HTTP gateway
|
|
83
|
+
*/
|
|
84
|
+
export function isGatewayHttpDenied(toolName) {
|
|
85
|
+
return DEFAULT_GATEWAY_HTTP_TOOL_DENY.includes(toolName.toLowerCase().trim());
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Get risk level for a tool
|
|
89
|
+
*/
|
|
90
|
+
export function getToolRiskLevel(toolName) {
|
|
91
|
+
if (["exec", "spawn", "shell", "sessions_spawn"].includes(toolName)) {
|
|
92
|
+
return "critical";
|
|
93
|
+
}
|
|
94
|
+
if (["fs_write", "fs_delete", "apply_patch", "gateway"].includes(toolName)) {
|
|
95
|
+
return "high";
|
|
96
|
+
}
|
|
97
|
+
if (["fs_move", "cron", "sessions_send"].includes(toolName)) {
|
|
98
|
+
return "medium";
|
|
99
|
+
}
|
|
100
|
+
return "low";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Get tools that require explicit approval
|
|
104
|
+
*/
|
|
105
|
+
export function getToolsRequiringApproval(riskLevel = "high") {
|
|
106
|
+
const allDangerous = Array.from(DANGEROUS_ACP_TOOLS);
|
|
107
|
+
if (riskLevel === "critical") {
|
|
108
|
+
return allDangerous.filter((t) => getToolRiskLevel(t) === "critical");
|
|
109
|
+
}
|
|
110
|
+
if (riskLevel === "high") {
|
|
111
|
+
return allDangerous.filter((t) => ["critical", "high"].includes(getToolRiskLevel(t)));
|
|
112
|
+
}
|
|
113
|
+
return allDangerous;
|
|
114
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Security Module for PoolBot
|
|
3
|
+
*
|
|
4
|
+
* Provides enterprise-grade security controls:
|
|
5
|
+
* - Audit logging for security events
|
|
6
|
+
* - Dangerous tool detection
|
|
7
|
+
* - Safe regex validation
|
|
8
|
+
* - External content validation
|
|
9
|
+
*
|
|
10
|
+
* Based on OpenClaw patterns but adapted for PoolBot architecture.
|
|
11
|
+
*/
|
|
12
|
+
export {};
|