@tractorscorch/clank 1.4.0 → 1.4.1
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/index.js +40 -8
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,22 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
6
6
|
|
|
7
7
|
---
|
|
8
8
|
|
|
9
|
+
## [1.4.1] — 2026-03-23
|
|
10
|
+
|
|
11
|
+
### Security
|
|
12
|
+
- **Config get redaction** — `config get` action now redacts sensitive keys (apiKey, token, botToken) before returning to LLM context
|
|
13
|
+
- **Config set protection** — config tool now blocks prototype pollution (`__proto__`, `constructor`, `prototype`)
|
|
14
|
+
- **Rate limit streaming path** — `handleInboundMessageStreaming` now enforced (was bypassing rate limiter)
|
|
15
|
+
- **SSRF private IPs** — web_fetch now blocks RFC 1918 ranges (10.x, 192.168.x, 172.16-31.x) and IPv4-mapped IPv6
|
|
16
|
+
- **STT workspace containment** — speech_to_text tool now uses guardPath() to prevent reading files outside workspace
|
|
17
|
+
|
|
18
|
+
### Audit Result
|
|
19
|
+
- 0 dependency vulnerabilities
|
|
20
|
+
- 14 PASS, 1 WARN (bash blocklist is defense-in-depth), 0 FAIL
|
|
21
|
+
- Grade: A
|
|
22
|
+
|
|
23
|
+
---
|
|
24
|
+
|
|
9
25
|
## [1.4.0] — 2026-03-23
|
|
10
26
|
|
|
11
27
|
### Added
|
package/dist/index.js
CHANGED
|
@@ -1276,6 +1276,10 @@ var init_registry = __esm({
|
|
|
1276
1276
|
});
|
|
1277
1277
|
|
|
1278
1278
|
// src/tools/path-guard.ts
|
|
1279
|
+
var path_guard_exports = {};
|
|
1280
|
+
__export(path_guard_exports, {
|
|
1281
|
+
guardPath: () => guardPath
|
|
1282
|
+
});
|
|
1279
1283
|
import { resolve, isAbsolute, normalize, relative } from "path";
|
|
1280
1284
|
function guardPath(inputPath, projectRoot, opts) {
|
|
1281
1285
|
const resolved = isAbsolute(inputPath) ? normalize(inputPath) : normalize(resolve(projectRoot, inputPath));
|
|
@@ -2255,6 +2259,12 @@ var init_web_fetch = __esm({
|
|
|
2255
2259
|
if (host === "localhost" || host === "127.0.0.1" || host === "[::1]" || host === "0.0.0.0") {
|
|
2256
2260
|
return { ok: false, error: "localhost URLs are blocked (SSRF protection)" };
|
|
2257
2261
|
}
|
|
2262
|
+
if (/^10\./.test(host) || /^192\.168\./.test(host) || /^172\.(1[6-9]|2\d|3[01])\./.test(host)) {
|
|
2263
|
+
return { ok: false, error: "Private network IPs are blocked (SSRF protection)" };
|
|
2264
|
+
}
|
|
2265
|
+
if (host.startsWith("[::ffff:")) {
|
|
2266
|
+
return { ok: false, error: "IPv4-mapped IPv6 addresses are blocked" };
|
|
2267
|
+
}
|
|
2258
2268
|
if (host === "169.254.169.254" || host === "metadata.google.internal") {
|
|
2259
2269
|
return { ok: false, error: "Cloud metadata endpoints are blocked" };
|
|
2260
2270
|
}
|
|
@@ -2391,9 +2401,21 @@ var init_config_tool = __esm({
|
|
|
2391
2401
|
return `Key not found: ${key}`;
|
|
2392
2402
|
}
|
|
2393
2403
|
}
|
|
2394
|
-
|
|
2404
|
+
if (typeof current === "object") {
|
|
2405
|
+
return JSON.stringify(redactConfig(current), null, 2);
|
|
2406
|
+
}
|
|
2407
|
+
const SENSITIVE = /* @__PURE__ */ new Set(["apikey", "api_key", "apiKey", "token", "bottoken", "botToken", "secret", "password", "pin"]);
|
|
2408
|
+
const lastKey = keys[keys.length - 1];
|
|
2409
|
+
if (SENSITIVE.has(lastKey) && typeof current === "string") {
|
|
2410
|
+
return "[REDACTED]";
|
|
2411
|
+
}
|
|
2412
|
+
return String(current);
|
|
2395
2413
|
}
|
|
2396
2414
|
if (action === "set") {
|
|
2415
|
+
const BLOCKED_KEYS = ["__proto__", "constructor", "prototype"];
|
|
2416
|
+
if (keys.some((k) => BLOCKED_KEYS.includes(k))) {
|
|
2417
|
+
return "Error: blocked \u2014 unsafe key";
|
|
2418
|
+
}
|
|
2397
2419
|
let parsed = args.value;
|
|
2398
2420
|
try {
|
|
2399
2421
|
parsed = JSON.parse(args.value);
|
|
@@ -4247,14 +4269,17 @@ var init_voice_tool = __esm({
|
|
|
4247
4269
|
},
|
|
4248
4270
|
safetyLevel: "low",
|
|
4249
4271
|
readOnly: true,
|
|
4250
|
-
validate(args) {
|
|
4272
|
+
validate(args, ctx) {
|
|
4251
4273
|
if (!args.file_path || typeof args.file_path !== "string") return { ok: false, error: "file_path is required" };
|
|
4252
4274
|
return { ok: true };
|
|
4253
4275
|
},
|
|
4254
|
-
async execute(args) {
|
|
4276
|
+
async execute(args, ctx) {
|
|
4255
4277
|
const { readFile: readFile13 } = await import("fs/promises");
|
|
4256
4278
|
const { existsSync: existsSync12 } = await import("fs");
|
|
4257
|
-
const
|
|
4279
|
+
const { guardPath: guardPath2 } = await Promise.resolve().then(() => (init_path_guard(), path_guard_exports));
|
|
4280
|
+
const guard = guardPath2(args.file_path, ctx.projectRoot, { allowExternal: ctx.allowExternal });
|
|
4281
|
+
if (!guard.ok) return guard.error;
|
|
4282
|
+
const filePath = guard.path;
|
|
4258
4283
|
if (!existsSync12(filePath)) return `Error: File not found: ${filePath}`;
|
|
4259
4284
|
const config = await loadConfig();
|
|
4260
4285
|
const engine = new STTEngine(config);
|
|
@@ -5948,6 +5973,13 @@ var init_server = __esm({
|
|
|
5948
5973
|
* Used by channel adapters for real-time streaming (e.g., Telegram message editing).
|
|
5949
5974
|
*/
|
|
5950
5975
|
async handleInboundMessageStreaming(context, text, callbacks) {
|
|
5976
|
+
const rlKey = deriveSessionKey(context);
|
|
5977
|
+
if (this.isRateLimited(rlKey)) {
|
|
5978
|
+
throw new Error("Rate limited \u2014 too many messages. Wait a moment.");
|
|
5979
|
+
}
|
|
5980
|
+
return this._handleInboundMessageStreamingInner(context, text, callbacks);
|
|
5981
|
+
}
|
|
5982
|
+
async _handleInboundMessageStreamingInner(context, text, callbacks) {
|
|
5951
5983
|
const agentId = resolveRoute(
|
|
5952
5984
|
context,
|
|
5953
5985
|
[],
|
|
@@ -6021,7 +6053,7 @@ var init_server = __esm({
|
|
|
6021
6053
|
res.writeHead(200, { "Content-Type": "application/json" });
|
|
6022
6054
|
res.end(JSON.stringify({
|
|
6023
6055
|
status: "ok",
|
|
6024
|
-
version: "1.4.
|
|
6056
|
+
version: "1.4.1",
|
|
6025
6057
|
uptime: process.uptime(),
|
|
6026
6058
|
clients: this.clients.size,
|
|
6027
6059
|
agents: this.engines.size
|
|
@@ -6129,7 +6161,7 @@ var init_server = __esm({
|
|
|
6129
6161
|
const hello = {
|
|
6130
6162
|
type: "hello",
|
|
6131
6163
|
protocol: PROTOCOL_VERSION,
|
|
6132
|
-
version: "1.4.
|
|
6164
|
+
version: "1.4.1",
|
|
6133
6165
|
agents: this.config.agents.list.map((a) => ({
|
|
6134
6166
|
id: a.id,
|
|
6135
6167
|
name: a.name || a.id,
|
|
@@ -7511,7 +7543,7 @@ async function runTui(opts) {
|
|
|
7511
7543
|
ws.on("open", () => {
|
|
7512
7544
|
ws.send(JSON.stringify({
|
|
7513
7545
|
type: "connect",
|
|
7514
|
-
params: { auth: { token }, mode: "tui", version: "1.4.
|
|
7546
|
+
params: { auth: { token }, mode: "tui", version: "1.4.1" }
|
|
7515
7547
|
}));
|
|
7516
7548
|
});
|
|
7517
7549
|
ws.on("message", (data) => {
|
|
@@ -7940,7 +7972,7 @@ import { fileURLToPath as fileURLToPath5 } from "url";
|
|
|
7940
7972
|
import { dirname as dirname5, join as join19 } from "path";
|
|
7941
7973
|
var __filename3 = fileURLToPath5(import.meta.url);
|
|
7942
7974
|
var __dirname3 = dirname5(__filename3);
|
|
7943
|
-
var version = "1.4.
|
|
7975
|
+
var version = "1.4.1";
|
|
7944
7976
|
try {
|
|
7945
7977
|
const pkg = JSON.parse(readFileSync(join19(__dirname3, "..", "package.json"), "utf-8"));
|
|
7946
7978
|
version = pkg.version;
|