@poolzin/pool-bot 2026.2.8 → 2026.2.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 +12 -0
- 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,15 @@
|
|
|
1
|
+
## v2026.2.9 (2026-02-15)
|
|
2
|
+
|
|
3
|
+
### Improvements
|
|
4
|
+
- Security audit hardening: warn on missing auth rate limiting for non-loopback gateways, flag dangerous HTTP tool allow-lists
|
|
5
|
+
- Embedding input limits: new helpers to split oversized text chunks before sending to embedding providers
|
|
6
|
+
- Updated default local embedding model to `embeddinggemma-300m-qat-Q8_0` (quantization-aware trained variant)
|
|
7
|
+
|
|
8
|
+
### Cleanup
|
|
9
|
+
- 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
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
1
13
|
## v2026.2.8 (2026-02-15)
|
|
2
14
|
|
|
3
15
|
### Features
|
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) {
|