@inetafrica/open-claudia 2.6.29 → 2.6.30
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 +30 -0
- package/README.md +16 -0
- package/bin/cli.js +8 -0
- package/bin/cluster.js +75 -0
- package/core/cluster-client.js +79 -0
- package/core/handlers.js +46 -0
- package/core/pack-guard.js +9 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## v2.6.30
|
|
4
|
+
- **Cluster self-management for AgentSpace pods.** When running as an AgentSpace pod, the bot can inspect and manage its own deployment through the backend broker — `/cluster status|logs [n]|restart|start|stop|scale <0|1>|sync` (owner-gated) for humans, and `open-claudia cluster …` for the agent. Both are thin clients over `core/cluster-client.js`, which posts to the broker's capability-gated `/pods/self/cluster` endpoint with the pod's bearer token; the bot holds no Kubernetes credentials. `brokerConfigured()` requires both `AGENTSPACE_API_URL` and `AGENTSPACE_POD_TOKEN`, so an unconfigured local install safely replies "not available" while a provisioned pod reaches the broker. Also tightens the `core/pack-guard.js` exfil heuristic: the broad "URL co-occurring with a sensitive word" rule (which false-positived on legitimate infra notes) is replaced by two precise carriers — a send/store/upload verb pointed at a URL/address, and a credential attached directly to a URL as a query/fragment param.
|
|
5
|
+
|
|
6
|
+
## v2.6.29
|
|
7
|
+
- **Kazee channel hardening.** `send()` now retries up to 3× with 1s/2s backoff (skipping 4xx), `edit()` normalizes text the same way as `send()`, inbound handling is async and detects ogg/opus voice notes (promoting them from "audio" to "voice" so the router dispatches correctly), reply-to context best-effort fetches the replied-to message before emitting the envelope, and `format.js` converts stray Telegram HTML tags (`<b>`, `<code>`, `<pre>`, `<i>`, `<s>`, `<a>`) to Markdown as a safety net.
|
|
8
|
+
|
|
9
|
+
## v2.6.28
|
|
10
|
+
- **Usage: report true peak context, not the inflated per-turn sum.** The result event re-counted the cached prefix once per tool round-trip, so `contextTokens` ballooned into the millions and the "Context this turn" alert fired every turn. It now tracks the peak single-call prefix (input + cache_read + cache_creation) and reports that as `contextTokens`; the summed value is kept as `billedContextTokens` for cost attribution.
|
|
11
|
+
|
|
12
|
+
## v2.6.27
|
|
13
|
+
- **Tasks: recency-ranked injection, staleness footer, and dream hygiene.** Open-task injection now ranks by activity (max of root and child `updatedAt`) rather than status — the most-recently-worked render in full, colder ones render title-only with a `task get <id>` pointer (new accessor + loopback endpoint + CLI sub), and anything untouched >14d (`OPEN_CLAUDIA_TASK_STALE_DAYS`) collapses into a capped one-line stale footer. The dream pass gains a task-hygiene step that surfaces the stalest tasks in the morning report — flag-and-ask only; it never edits or deletes tasks.
|
|
14
|
+
|
|
15
|
+
## v2.6.26
|
|
16
|
+
- **Memory: threat-scan guard + FTS-miss instrumentation.** `core/pack-guard.js` scans reviewer/dream content before write (override/exfil/base64 heuristics; strict on the auto-injected Stance/Procedure/State/description, lenient on Journal) and is wired into the pack reviewer and dream merge/umbrella/retag — a hit skips the write and announces one line. `logRecall` now records FTS-miss rescues (packs the judge kept that FTS scored ~0) into `recall-stats.json`, so a vector backend becomes a data-driven decision.
|
|
17
|
+
|
|
18
|
+
## v2.6.25
|
|
19
|
+
- **Memory: provenance tracking for pack writes.** A per-pack `.provenance.json` sidecar records which origin (user / reviewer / dream) last authored each section; `updatePack`/`createPack` take an origin, and an automated writer never silently overwrites a user-authored Stance. An absent sidecar defaults to "user" (protect-by-default).
|
|
20
|
+
|
|
21
|
+
## v2.6.24
|
|
22
|
+
- **Memory: pack usage telemetry + lifecycle retirement.** Packs gain `usage_count` telemetry (bumped on each use alongside `last_used`), a dream archive action retires cold packs (idle ≥30d, used ≤3×) into `.archived` with safety guards, and `pack archive`/`restore`/`archived` CLI commands are added; `/skills` surfaces the archived count. Also adds a post-dream chat summary (`DREAM_SUMMARY`), toggled with the new `/dreamsummary on|off` command (default on); when off, the nightly consolidation runs silently.
|
|
23
|
+
|
|
24
|
+
## v2.6.23
|
|
25
|
+
- **Progressive pack disclosure on by default.** Injects Stance + State teasers with on-demand Procedure/Journal and widens the pack match limit to 6. Override with `PACK_PROGRESSIVE=off`.
|
|
26
|
+
|
|
27
|
+
## v2.6.22
|
|
28
|
+
- **Wire `/skills` and `/learn` into the context-pack store.** Both previously read from / wrote to the empty legacy `~/.claude/skills` dir; now `/skills` lists packs with show/remove, and `/learn` folds work into a matching pack's Procedure plus a dated Journal line.
|
|
29
|
+
|
|
30
|
+
## v2.6.21
|
|
31
|
+
- **CI: allow tests to run without bot env.** The test suite no longer requires bot environment variables to be present.
|
|
32
|
+
|
|
3
33
|
## v2.6.20
|
|
4
34
|
- **AgentSpace pod durability.** `/upgrade` and password-change callbacks now preserve the `/api` base path expected by AgentSpace, CLI paths are resolved to concrete executables at startup, and runs repair a stale/missing session workspace before spawning Claude, Cursor, or Codex. This prevents misleading `spawn ... ENOENT` failures when the saved session cwd no longer exists.
|
|
5
35
|
|
package/README.md
CHANGED
|
@@ -47,6 +47,7 @@ Send text, voice notes, screenshots, and files from your phone. Your chosen AI a
|
|
|
47
47
|
- **Token economy** — byte-stable system prompt for maximum Anthropic prompt-cache hits; dynamic state rides each message instead
|
|
48
48
|
- **Web UI** — optional browser UI for setup and config (`open-claudia start --web`)
|
|
49
49
|
- **Auto-updates** — checks npm every 5 minutes, upgrade with `/upgrade`
|
|
50
|
+
- **Cluster self-management** — when running as an AgentSpace pod, the bot can inspect and manage its own deployment (`/cluster`, or `open-claudia cluster …` for the agent) through the broker, which authenticates, capability-checks, and audits every operation
|
|
50
51
|
- **Requirements doctor** — `/doctor` checks CLI installs, auth, voice tools, and writable paths
|
|
51
52
|
- **Cross-platform** — macOS, Linux, and Windows
|
|
52
53
|
|
|
@@ -187,6 +188,7 @@ When you select a project, the last conversation is automatically resumed. Tap "
|
|
|
187
188
|
| `/learn [<hint>]` | Capture the last piece of work into the matching context pack |
|
|
188
189
|
| `/skills [show\|remove <name>]` | List, show, or remove legacy learned skills |
|
|
189
190
|
| `/soul` | View/edit assistant identity and personality |
|
|
191
|
+
| `/dreamsummary [on\|off]` | Toggle the post-dream memory summary in chat |
|
|
190
192
|
|
|
191
193
|
### Automation
|
|
192
194
|
|
|
@@ -235,9 +237,23 @@ Tokens are redacted from chat output and logs.
|
|
|
235
237
|
| `/version` | Show current running version |
|
|
236
238
|
| `/upgrade` | Upgrade to latest version and restart (includes a post-upgrade doctor summary) |
|
|
237
239
|
| `/restart` | Restart the bot |
|
|
240
|
+
| `/dashboard` | Get a one-time login link to the web dashboard |
|
|
238
241
|
| `/stop` | Cancel a running task |
|
|
239
242
|
| `/help` | Show all commands |
|
|
240
243
|
|
|
244
|
+
### Cluster control
|
|
245
|
+
|
|
246
|
+
Available only when the bot runs as an AgentSpace-provisioned pod (the broker credentials `AGENTSPACE_API_URL` and `AGENTSPACE_POD_TOKEN` are present). Off this network the command replies that it isn't available and does nothing. Owner-gated; every operation is authenticated, capability-checked, and audited by the broker, and confined to the bot's own deployment.
|
|
247
|
+
|
|
248
|
+
| Command | Description |
|
|
249
|
+
|---------|-------------|
|
|
250
|
+
| `/cluster status` | Deployment readiness/phase |
|
|
251
|
+
| `/cluster logs [n]` | Tail the last n container log lines |
|
|
252
|
+
| `/cluster restart` | Rollout restart (pulls latest image) |
|
|
253
|
+
| `/cluster start` / `/cluster stop` | Scale to 1 / 0 replicas |
|
|
254
|
+
| `/cluster scale <0\|1>` | Stop (0) or start (1) |
|
|
255
|
+
| `/cluster sync` | Re-apply the deployment manifest |
|
|
256
|
+
|
|
241
257
|
## Memory & Long-Term Context
|
|
242
258
|
|
|
243
259
|
Open Claudia layers three memory systems on top of the backend's native sessions:
|
package/bin/cli.js
CHANGED
|
@@ -274,6 +274,11 @@ switch (command) {
|
|
|
274
274
|
break;
|
|
275
275
|
}
|
|
276
276
|
|
|
277
|
+
case "cluster": {
|
|
278
|
+
require("./cluster").run(args.slice(1));
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
281
|
+
|
|
277
282
|
case "pack": {
|
|
278
283
|
require("./pack").run(args.slice(1));
|
|
279
284
|
break;
|
|
@@ -347,6 +352,9 @@ Background work (only inside an active bot-spawned task):
|
|
|
347
352
|
open-claudia task add|list|start|done|remove Per-channel todo (survives restarts)
|
|
348
353
|
open-claudia agent "<prompt>" [--role X] Spawn a throwaway sub-agent for research
|
|
349
354
|
|
|
355
|
+
Cluster control (self-management of this bot's own deployment via the broker):
|
|
356
|
+
open-claudia cluster status|logs|restart|start|stop|scale <0|1>|sync
|
|
357
|
+
|
|
350
358
|
Multi-user / cross-channel:
|
|
351
359
|
open-claudia people list|show|add|note|link Roster of known team members
|
|
352
360
|
open-claudia intros list|approve|reject Pending introductions from unknown chats
|
package/bin/cluster.js
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// open-claudia cluster <operation>
|
|
2
|
+
//
|
|
3
|
+
// Self-management of this bot's own Kubernetes deployment, mediated by the
|
|
4
|
+
// AgentSpace broker. The bot has no direct cluster credentials — every command
|
|
5
|
+
// here is a request the broker authenticates, capability-checks, audits, and
|
|
6
|
+
// executes against the pod's own namespace.
|
|
7
|
+
|
|
8
|
+
const { clusterRequest } = require("../core/cluster-client");
|
|
9
|
+
|
|
10
|
+
const HELP = `
|
|
11
|
+
Cluster control for this bot's own deployment (via the AgentSpace broker).
|
|
12
|
+
|
|
13
|
+
open-claudia cluster status Show deployment readiness/phase
|
|
14
|
+
open-claudia cluster logs [n] Tail the last n container log lines (default 100, max 1000)
|
|
15
|
+
open-claudia cluster restart Rollout restart (pulls latest image)
|
|
16
|
+
open-claudia cluster start Scale to 1 replica
|
|
17
|
+
open-claudia cluster stop Scale to 0 replicas
|
|
18
|
+
open-claudia cluster scale <0|1> Stop (0) or start (1)
|
|
19
|
+
open-claudia cluster sync Re-apply the deployment manifest
|
|
20
|
+
|
|
21
|
+
All operations are confined to this pod's own namespace and gated by the
|
|
22
|
+
capabilities the broker has granted this bot.
|
|
23
|
+
`;
|
|
24
|
+
|
|
25
|
+
function ok(line) { if (line) console.log(line); process.exit(0); }
|
|
26
|
+
function fail(line) { if (line) console.error(line); process.exit(1); }
|
|
27
|
+
|
|
28
|
+
// Render a broker response into a human line + correct exit code.
|
|
29
|
+
function report(res, successLine) {
|
|
30
|
+
if (res.status >= 200 && res.status < 300) {
|
|
31
|
+
ok(typeof successLine === "function" ? successLine(res.json || {}) : successLine);
|
|
32
|
+
}
|
|
33
|
+
const detail = res.json && res.json.error ? res.json.error : (res.body || "").slice(0, 400);
|
|
34
|
+
if (res.status === 0) fail(`Broker request failed: ${detail}`);
|
|
35
|
+
fail(`Broker ${res.status}: ${detail}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
async function run(args) {
|
|
39
|
+
const op = (args[0] || "").toLowerCase();
|
|
40
|
+
const rest = args.slice(1);
|
|
41
|
+
if (!op || op === "help" || op === "--help") { console.log(HELP); process.exit(op ? 0 : 2); }
|
|
42
|
+
|
|
43
|
+
switch (op) {
|
|
44
|
+
case "status": {
|
|
45
|
+
const res = await clusterRequest("status");
|
|
46
|
+
return report(res, (j) =>
|
|
47
|
+
`Deployment: ${j.phase || "?"}${j.ready ? " (ready)" : ""}${j.message ? ` — ${j.message}` : ""}`);
|
|
48
|
+
}
|
|
49
|
+
case "logs": {
|
|
50
|
+
const tailLines = rest[0] ? parseInt(rest[0], 10) : undefined;
|
|
51
|
+
if (rest[0] && Number.isNaN(tailLines)) fail("Usage: cluster logs [n] (n = number of lines)");
|
|
52
|
+
const res = await clusterRequest("logs", tailLines ? { tailLines } : {});
|
|
53
|
+
return report(res, (j) => (j.logs || "(no logs)"));
|
|
54
|
+
}
|
|
55
|
+
case "restart":
|
|
56
|
+
return report(await clusterRequest("restart"), (j) => j.message || "Restart requested.");
|
|
57
|
+
case "start":
|
|
58
|
+
return report(await clusterRequest("start"), (j) => j.message || "Started.");
|
|
59
|
+
case "stop":
|
|
60
|
+
return report(await clusterRequest("stop"), (j) => j.message || "Stopped.");
|
|
61
|
+
case "scale": {
|
|
62
|
+
const n = rest[0];
|
|
63
|
+
if (n !== "0" && n !== "1") fail("Usage: cluster scale <0|1>");
|
|
64
|
+
return report(await clusterRequest("scale", { replicas: parseInt(n, 10) }), (j) => j.message || `Scaled to ${n}.`);
|
|
65
|
+
}
|
|
66
|
+
case "sync":
|
|
67
|
+
return report(await clusterRequest("sync"), (j) => j.message || "Reconciled.");
|
|
68
|
+
default:
|
|
69
|
+
console.error(`Unknown cluster operation: ${op}`);
|
|
70
|
+
console.log(HELP);
|
|
71
|
+
process.exit(2);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
module.exports = { run, HELP };
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Thin client for the AgentSpace cluster-control broker.
|
|
2
|
+
//
|
|
3
|
+
// Both entry points use this single implementation:
|
|
4
|
+
// - bin/cluster.js (the `open-claudia cluster ...` CLI, driven by the agent)
|
|
5
|
+
// - core/handlers.js (the `/cluster` Telegram slash command, driven by a human)
|
|
6
|
+
//
|
|
7
|
+
// The bot holds no Kubernetes credentials. It authenticates to the broker as
|
|
8
|
+
// itself with the pod-self token; the broker enforces capabilities, policy and
|
|
9
|
+
// audit and performs the actual cluster operation on the pod's own namespace.
|
|
10
|
+
|
|
11
|
+
const CLUSTER_OPERATIONS = ["status", "logs", "restart", "start", "stop", "scale", "sync"];
|
|
12
|
+
|
|
13
|
+
function brokerConfigured() {
|
|
14
|
+
return !!(process.env.AGENTSPACE_API_URL && process.env.AGENTSPACE_POD_TOKEN);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
// Build the absolute URL to a pods/self/* endpoint, mirroring the path-base
|
|
18
|
+
// logic used by the upgrade flow in core/handlers.js.
|
|
19
|
+
function endpoint(pathSuffix) {
|
|
20
|
+
const apiUrl = process.env.AGENTSPACE_API_URL;
|
|
21
|
+
const base = new URL(apiUrl);
|
|
22
|
+
if (base.pathname === "/") base.pathname = "/api/";
|
|
23
|
+
if (!base.pathname.endsWith("/")) base.pathname += "/";
|
|
24
|
+
return new URL(pathSuffix, base);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// POST a cluster operation to the broker. Resolves to { status, body, json }.
|
|
28
|
+
// Never rejects on HTTP/transport errors — surfaces them via status 0 so callers
|
|
29
|
+
// can format a single error path.
|
|
30
|
+
function clusterRequest(operation, params = {}) {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
if (!brokerConfigured()) {
|
|
33
|
+
resolve({
|
|
34
|
+
status: 0,
|
|
35
|
+
body: "AgentSpace broker not configured (AGENTSPACE_API_URL / AGENTSPACE_POD_TOKEN missing). This only works inside a pod provisioned by AgentSpace.",
|
|
36
|
+
json: null,
|
|
37
|
+
});
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
let u;
|
|
42
|
+
try {
|
|
43
|
+
u = endpoint("pods/self/cluster");
|
|
44
|
+
} catch (e) {
|
|
45
|
+
resolve({ status: 0, body: `Bad AGENTSPACE_API_URL: ${e.message || e}`, json: null });
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const payload = JSON.stringify({ operation, ...params });
|
|
50
|
+
const lib = u.protocol === "https:" ? require("https") : require("http");
|
|
51
|
+
const req = lib.request(
|
|
52
|
+
{
|
|
53
|
+
method: "POST",
|
|
54
|
+
hostname: u.hostname,
|
|
55
|
+
port: u.port || (u.protocol === "https:" ? 443 : 80),
|
|
56
|
+
path: u.pathname + u.search,
|
|
57
|
+
headers: {
|
|
58
|
+
Authorization: `Bearer ${process.env.AGENTSPACE_POD_TOKEN}`,
|
|
59
|
+
"Content-Type": "application/json",
|
|
60
|
+
"Content-Length": Buffer.byteLength(payload),
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
(res) => {
|
|
64
|
+
let data = "";
|
|
65
|
+
res.on("data", (c) => { data += c; });
|
|
66
|
+
res.on("end", () => {
|
|
67
|
+
let json = null;
|
|
68
|
+
try { json = JSON.parse(data); } catch (e) {}
|
|
69
|
+
resolve({ status: res.statusCode, body: data, json });
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
);
|
|
73
|
+
req.on("error", (e) => resolve({ status: 0, body: String(e.message || e), json: null }));
|
|
74
|
+
req.setTimeout(20000, () => { req.destroy(new Error("timeout")); });
|
|
75
|
+
req.end(payload);
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
module.exports = { clusterRequest, brokerConfigured, CLUSTER_OPERATIONS };
|
package/core/handlers.js
CHANGED
|
@@ -525,6 +525,52 @@ register({
|
|
|
525
525
|
},
|
|
526
526
|
});
|
|
527
527
|
|
|
528
|
+
register({
|
|
529
|
+
name: "cluster", description: "Control this bot's own deployment (status/logs/restart/start/stop/scale/sync)",
|
|
530
|
+
args: "<status|logs|restart|start|stop|scale 0|1|sync>", ownerOnly: true,
|
|
531
|
+
handler: async (env, { tail }) => {
|
|
532
|
+
if (!ownerEnv(env)) return;
|
|
533
|
+
const { clusterRequest, brokerConfigured } = require("./cluster-client");
|
|
534
|
+
if (!brokerConfigured()) {
|
|
535
|
+
return send("Cluster control isn't available — this bot isn't running under AgentSpace (no broker credentials).");
|
|
536
|
+
}
|
|
537
|
+
const parts = (tail || "").trim().split(/\s+/).filter(Boolean);
|
|
538
|
+
const op = (parts[0] || "").toLowerCase();
|
|
539
|
+
const allowed = ["status", "logs", "restart", "start", "stop", "scale", "sync"];
|
|
540
|
+
if (!op || !allowed.includes(op)) {
|
|
541
|
+
return send("Usage: /cluster <status | logs [n] | restart | start | stop | scale 0|1 | sync>");
|
|
542
|
+
}
|
|
543
|
+
const params = {};
|
|
544
|
+
if (op === "logs" && parts[1]) {
|
|
545
|
+
const n = parseInt(parts[1], 10);
|
|
546
|
+
if (!Number.isNaN(n)) params.tailLines = n;
|
|
547
|
+
}
|
|
548
|
+
if (op === "scale") {
|
|
549
|
+
const n = parts[1];
|
|
550
|
+
if (n !== "0" && n !== "1") return send("Usage: /cluster scale <0|1>");
|
|
551
|
+
params.replicas = parseInt(n, 10);
|
|
552
|
+
}
|
|
553
|
+
try {
|
|
554
|
+
const res = await clusterRequest(op, params);
|
|
555
|
+
if (res.status >= 200 && res.status < 300) {
|
|
556
|
+
const j = res.json || {};
|
|
557
|
+
if (op === "status") {
|
|
558
|
+
return send(`Deployment: ${j.phase || "?"}${j.ready ? " (ready)" : ""}${j.message ? ` — ${j.message}` : ""}`);
|
|
559
|
+
}
|
|
560
|
+
if (op === "logs") {
|
|
561
|
+
const logs = j.logs || "(no logs)";
|
|
562
|
+
return send(logs.length > 3500 ? logs.slice(-3500) : logs);
|
|
563
|
+
}
|
|
564
|
+
return send(j.message || "Done.");
|
|
565
|
+
}
|
|
566
|
+
const detail = (res.json && res.json.error) ? res.json.error : (res.body || "").slice(0, 300);
|
|
567
|
+
return send(res.status === 0 ? `Cluster request failed: ${detail}` : `Cluster ${res.status}: ${detail}`);
|
|
568
|
+
} catch (e) {
|
|
569
|
+
return send(`Cluster error: ${e.message || e}`);
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
});
|
|
573
|
+
|
|
528
574
|
register({
|
|
529
575
|
name: "projects", description: "Browse all workspace projects",
|
|
530
576
|
handler: async (env) => { if (authorized(env)) send("Pick:", { keyboard: projectKeyboard() }); },
|
package/core/pack-guard.js
CHANGED
|
@@ -29,8 +29,15 @@ const OVERRIDE_PATTERNS = [
|
|
|
29
29
|
// Exfiltration: a send/post/upload verb pointed at a URL or address. Scanned in
|
|
30
30
|
// every section, including Journal.
|
|
31
31
|
const EXFIL_PATTERNS = [
|
|
32
|
-
|
|
33
|
-
/\b(
|
|
32
|
+
// A send/store/upload verb pointed at a URL or address.
|
|
33
|
+
/\b(send|post|upload|exfiltrate|forward|transmit|leak|email|curl|wget|fetch|store|save|drop|put|copy)\b[^.\n]{0,60}\b(to|at|into)\b[^.\n]{0,40}(https?:\/\/|ftp:\/\/|[\w.-]+@[\w.-]+)/i,
|
|
34
|
+
// A credential embedded directly in a URL as a query/fragment param
|
|
35
|
+
// (e.g. https://evil/?token=…): the classic verbless exfil carrier. Prose that
|
|
36
|
+
// merely mentions a URL near the word "token" no longer trips — the credential
|
|
37
|
+
// must actually be attached to the URL with =/:, which legitimate infra/ops
|
|
38
|
+
// notes don't do. This replaces a broad URL+keyword co-occurrence rule that
|
|
39
|
+
// false-positived on benign sentences like "the API at <url> uses a token".
|
|
40
|
+
/(https?:\/\/)[^\s)]*?\b(api_?key|access_?token|token|password|secret|credential)\b\s*[=:]/i,
|
|
34
41
|
/\bcurl\b[^\n]*\b(https?:\/\/)/i,
|
|
35
42
|
];
|
|
36
43
|
|
package/package.json
CHANGED