@poolzin/pool-bot 2026.2.8 → 2026.2.10
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 +19 -0
- package/dist/agents/bash-tools.exec.js +7 -2
- package/dist/build-info.json +3 -3
- package/dist/memory/embedding-chunk-limits.js +22 -0
- package/dist/memory/embedding-input-limits.js +56 -0
- package/dist/memory/embedding-model-limits.js +24 -0
- package/dist/memory/embeddings.js +1 -1
- package/dist/security/audit.js +32 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,22 @@
|
|
|
1
|
+
## v2026.2.10 (2026-02-16)
|
|
2
|
+
|
|
3
|
+
### Fixes
|
|
4
|
+
- Reduce default exec tool timeout from 1800s (30 min) to 120s (2 min) — prevents bot from silently hanging on stuck bash commands; per-call timeout param and config `tools.exec.timeoutSec` still override; new env vars `POOLBOT_EXEC_TIMEOUT_SEC` / `CLAWDBOT_EXEC_TIMEOUT_SEC` for further control
|
|
5
|
+
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
## v2026.2.9 (2026-02-15)
|
|
9
|
+
|
|
10
|
+
### Improvements
|
|
11
|
+
- Security audit hardening: warn on missing auth rate limiting for non-loopback gateways, flag dangerous HTTP tool allow-lists
|
|
12
|
+
- Embedding input limits: new helpers to split oversized text chunks before sending to embedding providers
|
|
13
|
+
- Updated default local embedding model to `embeddinggemma-300m-qat-Q8_0` (quantization-aware trained variant)
|
|
14
|
+
|
|
15
|
+
### Cleanup
|
|
16
|
+
- Removed 6 dead code files (836 lines) from upstream port f494b78 — ollama-stream, tool-policy-pipeline, tool-mutation, compaction-safety-timeout, compaction-timeout, wait-for-idle-before-flush
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
1
20
|
## v2026.2.8 (2026-02-15)
|
|
2
21
|
|
|
3
22
|
### Features
|
|
@@ -58,6 +58,11 @@ function validateHostEnv(env) {
|
|
|
58
58
|
const DEFAULT_MAX_OUTPUT = clampNumber(readEnvInt("PI_BASH_MAX_OUTPUT_CHARS"), 200_000, 1_000, 200_000);
|
|
59
59
|
const DEFAULT_PENDING_MAX_OUTPUT = clampNumber(readEnvInt("POOLBOT_BASH_PENDING_MAX_OUTPUT_CHARS") ??
|
|
60
60
|
readEnvInt("CLAWDBOT_BASH_PENDING_MAX_OUTPUT_CHARS"), 200_000, 1_000, 200_000);
|
|
61
|
+
// Default exec timeout: 120s is generous for typical commands (grep, cat, ls, build).
|
|
62
|
+
// The AI can still pass a per-call `timeout` for intentionally long operations,
|
|
63
|
+
// and users can override via config (`tools.exec.timeoutSec`) or env var.
|
|
64
|
+
// Previous default (1800s / 30 min) caused the bot to hang silently on stuck commands.
|
|
65
|
+
const DEFAULT_EXEC_TIMEOUT_SEC = clampNumber(readEnvInt("POOLBOT_EXEC_TIMEOUT_SEC") ?? readEnvInt("CLAWDBOT_EXEC_TIMEOUT_SEC"), 120, 1, 86_400);
|
|
61
66
|
const DEFAULT_PATH = process.env.PATH ?? "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin";
|
|
62
67
|
const DEFAULT_NOTIFY_TAIL_CHARS = 400;
|
|
63
68
|
const DEFAULT_APPROVAL_TIMEOUT_MS = 120_000;
|
|
@@ -73,7 +78,7 @@ const execSchema = Type.Object({
|
|
|
73
78
|
})),
|
|
74
79
|
background: Type.Optional(Type.Boolean({ description: "Run in background immediately" })),
|
|
75
80
|
timeout: Type.Optional(Type.Number({
|
|
76
|
-
description: "Timeout in seconds (
|
|
81
|
+
description: "Timeout in seconds (default 120, kills process on expiry). Set higher for long builds/downloads.",
|
|
77
82
|
})),
|
|
78
83
|
pty: Type.Optional(Type.Boolean({
|
|
79
84
|
description: "Run in a pseudo-terminal (PTY) when available (TTY-required CLIs, coding agents)",
|
|
@@ -559,7 +564,7 @@ export function createExecTool(defaults) {
|
|
|
559
564
|
const allowBackground = defaults?.allowBackground ?? true;
|
|
560
565
|
const defaultTimeoutSec = typeof defaults?.timeoutSec === "number" && defaults.timeoutSec > 0
|
|
561
566
|
? defaults.timeoutSec
|
|
562
|
-
:
|
|
567
|
+
: DEFAULT_EXEC_TIMEOUT_SEC;
|
|
563
568
|
const defaultPathPrepend = normalizePathPrepend(defaults?.pathPrepend);
|
|
564
569
|
const safeBins = resolveSafeBins(defaults?.safeBins);
|
|
565
570
|
const notifyOnExit = defaults?.notifyOnExit !== false;
|
package/dist/build-info.json
CHANGED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { estimateUtf8Bytes, splitTextToUtf8ByteLimit } from "./embedding-input-limits.js";
|
|
2
|
+
import { resolveEmbeddingMaxInputTokens } from "./embedding-model-limits.js";
|
|
3
|
+
import { hashText } from "./internal.js";
|
|
4
|
+
export function enforceEmbeddingMaxInputTokens(provider, chunks) {
|
|
5
|
+
const maxInputTokens = resolveEmbeddingMaxInputTokens(provider);
|
|
6
|
+
const out = [];
|
|
7
|
+
for (const chunk of chunks) {
|
|
8
|
+
if (estimateUtf8Bytes(chunk.text) <= maxInputTokens) {
|
|
9
|
+
out.push(chunk);
|
|
10
|
+
continue;
|
|
11
|
+
}
|
|
12
|
+
for (const text of splitTextToUtf8ByteLimit(chunk.text, maxInputTokens)) {
|
|
13
|
+
out.push({
|
|
14
|
+
startLine: chunk.startLine,
|
|
15
|
+
endLine: chunk.endLine,
|
|
16
|
+
text,
|
|
17
|
+
hash: hashText(text),
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return out;
|
|
22
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Helpers for enforcing embedding model input size limits.
|
|
2
|
+
//
|
|
3
|
+
// We use UTF-8 byte length as a conservative upper bound for tokenizer output.
|
|
4
|
+
// Tokenizers operate over bytes; a token must contain at least one byte, so
|
|
5
|
+
// token_count <= utf8_byte_length.
|
|
6
|
+
export function estimateUtf8Bytes(text) {
|
|
7
|
+
if (!text) {
|
|
8
|
+
return 0;
|
|
9
|
+
}
|
|
10
|
+
return Buffer.byteLength(text, "utf8");
|
|
11
|
+
}
|
|
12
|
+
export function splitTextToUtf8ByteLimit(text, maxUtf8Bytes) {
|
|
13
|
+
if (maxUtf8Bytes <= 0) {
|
|
14
|
+
return [text];
|
|
15
|
+
}
|
|
16
|
+
if (estimateUtf8Bytes(text) <= maxUtf8Bytes) {
|
|
17
|
+
return [text];
|
|
18
|
+
}
|
|
19
|
+
const parts = [];
|
|
20
|
+
let cursor = 0;
|
|
21
|
+
while (cursor < text.length) {
|
|
22
|
+
let low = cursor + 1;
|
|
23
|
+
let high = Math.min(text.length, cursor + maxUtf8Bytes);
|
|
24
|
+
let best = cursor;
|
|
25
|
+
while (low <= high) {
|
|
26
|
+
const mid = Math.floor((low + high) / 2);
|
|
27
|
+
const bytes = estimateUtf8Bytes(text.slice(cursor, mid));
|
|
28
|
+
if (bytes <= maxUtf8Bytes) {
|
|
29
|
+
best = mid;
|
|
30
|
+
low = mid + 1;
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
high = mid - 1;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
if (best <= cursor) {
|
|
37
|
+
best = Math.min(text.length, cursor + 1);
|
|
38
|
+
}
|
|
39
|
+
// Avoid splitting inside a surrogate pair.
|
|
40
|
+
if (best < text.length &&
|
|
41
|
+
best > cursor &&
|
|
42
|
+
text.charCodeAt(best - 1) >= 0xd800 &&
|
|
43
|
+
text.charCodeAt(best - 1) <= 0xdbff &&
|
|
44
|
+
text.charCodeAt(best) >= 0xdc00 &&
|
|
45
|
+
text.charCodeAt(best) <= 0xdfff) {
|
|
46
|
+
best -= 1;
|
|
47
|
+
}
|
|
48
|
+
const part = text.slice(cursor, best);
|
|
49
|
+
if (!part) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
parts.push(part);
|
|
53
|
+
cursor = best;
|
|
54
|
+
}
|
|
55
|
+
return parts;
|
|
56
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const DEFAULT_EMBEDDING_MAX_INPUT_TOKENS = 8192;
|
|
2
|
+
const KNOWN_EMBEDDING_MAX_INPUT_TOKENS = {
|
|
3
|
+
"openai:text-embedding-3-small": 8192,
|
|
4
|
+
"openai:text-embedding-3-large": 8192,
|
|
5
|
+
"openai:text-embedding-ada-002": 8191,
|
|
6
|
+
"gemini:text-embedding-004": 2048,
|
|
7
|
+
"voyage:voyage-3": 32000,
|
|
8
|
+
"voyage:voyage-3-lite": 16000,
|
|
9
|
+
"voyage:voyage-code-3": 32000,
|
|
10
|
+
};
|
|
11
|
+
export function resolveEmbeddingMaxInputTokens(provider) {
|
|
12
|
+
if (typeof provider.maxInputTokens === "number") {
|
|
13
|
+
return provider.maxInputTokens;
|
|
14
|
+
}
|
|
15
|
+
const key = `${provider.id}:${provider.model}`.toLowerCase();
|
|
16
|
+
const known = KNOWN_EMBEDDING_MAX_INPUT_TOKENS[key];
|
|
17
|
+
if (typeof known === "number") {
|
|
18
|
+
return known;
|
|
19
|
+
}
|
|
20
|
+
if (provider.id.toLowerCase() === "gemini") {
|
|
21
|
+
return 2048;
|
|
22
|
+
}
|
|
23
|
+
return DEFAULT_EMBEDDING_MAX_INPUT_TOKENS;
|
|
24
|
+
}
|
|
@@ -13,7 +13,7 @@ function sanitizeAndNormalizeEmbedding(vec) {
|
|
|
13
13
|
}
|
|
14
14
|
return sanitized.map((value) => value / magnitude);
|
|
15
15
|
}
|
|
16
|
-
const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-
|
|
16
|
+
export const DEFAULT_LOCAL_MODEL = "hf:ggml-org/embeddinggemma-300m-qat-q8_0-GGUF/embeddinggemma-300m-qat-Q8_0.gguf";
|
|
17
17
|
function canAutoSelectLocal(options) {
|
|
18
18
|
const modelPath = options.local?.modelPath?.trim();
|
|
19
19
|
if (!modelPath) {
|
package/dist/security/audit.js
CHANGED
|
@@ -6,6 +6,7 @@ import { resolveGatewayAuth } from "../gateway/auth.js";
|
|
|
6
6
|
import { formatCliCommand } from "../cli/command-format.js";
|
|
7
7
|
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
|
8
8
|
import { probeGateway } from "../gateway/probe.js";
|
|
9
|
+
import { DEFAULT_GATEWAY_HTTP_TOOL_DENY } from "./dangerous-tools.js";
|
|
9
10
|
import { collectAttackSurfaceSummaryFindings, collectExposureMatrixFindings, collectHooksHardeningFindings, collectIncludeFilePermFindings, collectModelHygieneFindings, collectSmallModelRiskFindings, collectPluginsTrustFindings, collectSecretsInConfigFindings, collectStateDeepFilesystemFindings, collectSyncedFolderFindings, readConfigSnapshotForAudit, } from "./audit-extra.js";
|
|
10
11
|
import { readChannelAllowFromStore } from "../pairing/pairing-store.js";
|
|
11
12
|
import { resolveNativeCommandsEnabled, resolveNativeSkillsEnabled } from "../config/commands.js";
|
|
@@ -182,6 +183,26 @@ function collectGatewayConfigFindings(cfg, env) {
|
|
|
182
183
|
const hasSharedSecret = (auth.mode === "token" && hasToken) || (auth.mode === "password" && hasPassword);
|
|
183
184
|
const hasTailscaleAuth = auth.allowTailscale === true && tailscaleMode === "serve";
|
|
184
185
|
const hasGatewayAuth = hasSharedSecret || hasTailscaleAuth;
|
|
186
|
+
// HTTP /tools/invoke: warn if operators re-enable dangerous tools over HTTP
|
|
187
|
+
const gatewayCfgAny = cfg.gateway;
|
|
188
|
+
const toolsCfg = gatewayCfgAny?.tools;
|
|
189
|
+
const gatewayToolsAllowRaw = Array.isArray(toolsCfg?.allow) ? toolsCfg.allow : [];
|
|
190
|
+
const gatewayToolsAllow = new Set(gatewayToolsAllowRaw
|
|
191
|
+
.map((v) => (typeof v === "string" ? v.trim().toLowerCase() : ""))
|
|
192
|
+
.filter(Boolean));
|
|
193
|
+
const reenabledOverHttp = DEFAULT_GATEWAY_HTTP_TOOL_DENY.filter((name) => gatewayToolsAllow.has(name));
|
|
194
|
+
if (reenabledOverHttp.length > 0) {
|
|
195
|
+
const extraRisk = bind !== "loopback" || tailscaleMode === "funnel";
|
|
196
|
+
findings.push({
|
|
197
|
+
checkId: "gateway.tools_invoke_http.dangerous_allow",
|
|
198
|
+
severity: extraRisk ? "critical" : "warn",
|
|
199
|
+
title: "Gateway HTTP /tools/invoke re-enables dangerous tools",
|
|
200
|
+
detail: `gateway.tools.allow includes ${reenabledOverHttp.join(", ")} which removes them from the default HTTP deny list. ` +
|
|
201
|
+
"This can allow remote session spawning / control-plane actions via HTTP and increases RCE blast radius if the gateway is reachable.",
|
|
202
|
+
remediation: "Remove these entries from gateway.tools.allow (recommended). " +
|
|
203
|
+
"If you keep them enabled, keep gateway.bind loopback-only (or tailnet-only), restrict network exposure, and treat the gateway token/password as full-admin.",
|
|
204
|
+
});
|
|
205
|
+
}
|
|
185
206
|
if (bind !== "loopback" && !hasSharedSecret) {
|
|
186
207
|
findings.push({
|
|
187
208
|
checkId: "gateway.bind_no_auth",
|
|
@@ -256,6 +277,17 @@ function collectGatewayConfigFindings(cfg, env) {
|
|
|
256
277
|
detail: `gateway auth token is ${token.length} chars; prefer a long random token.`,
|
|
257
278
|
});
|
|
258
279
|
}
|
|
280
|
+
const authCfgAny = cfg.gateway?.auth;
|
|
281
|
+
if (bind !== "loopback" && !authCfgAny?.rateLimit) {
|
|
282
|
+
findings.push({
|
|
283
|
+
checkId: "gateway.auth_no_rate_limit",
|
|
284
|
+
severity: "warn",
|
|
285
|
+
title: "No auth rate limiting configured",
|
|
286
|
+
detail: "gateway.bind is not loopback but no gateway.auth.rateLimit is configured. " +
|
|
287
|
+
"Without rate limiting, brute-force auth attacks are not mitigated.",
|
|
288
|
+
remediation: "Set gateway.auth.rateLimit (e.g. { maxAttempts: 10, windowMs: 60000, lockoutMs: 300000 }).",
|
|
289
|
+
});
|
|
290
|
+
}
|
|
259
291
|
return findings;
|
|
260
292
|
}
|
|
261
293
|
function collectBrowserControlFindings(cfg) {
|