@gcunharodrigues/wrxn 0.17.0 → 0.18.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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@gcunharodrigues/wrxn",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.18.1",
|
|
4
4
|
"description": "WRXN Kernel — installable AI operating system. Two profiles (project | workspace), pull-based updates, managed/seeded/state file classes.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"wrxn": "bin/wrxn.cjs"
|
|
@@ -15,21 +15,38 @@ const path = require('path');
|
|
|
15
15
|
// secretScan — replicated here because each self-contained install-only module imports no shared
|
|
16
16
|
// kernel-lib (node stdlib only), exactly as it is duplicated across dream.cjs / sync.cjs. CASE-SENSITIVE:
|
|
17
17
|
// the token shapes are case-specific. A coalesced sidecar must never harden a credential onto disk.
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
/
|
|
18
|
+
// The CANONICAL secret-shape set (#39) — kept BYTE-IDENTICAL across every copy (dream / sync / harvest /
|
|
19
|
+
// memory-synth + this one), drift-pinned by adapter-drift-guard.test.cjs. Each self-contained module
|
|
20
|
+
// replicates the set (the install-only adapters import no shared module — node stdlib only), so the pin is
|
|
21
|
+
// what keeps the copies honest. CASE-SENSITIVE except where a shape carries its own /i flag.
|
|
22
|
+
const SECRET_PATTERNS_CANON = [
|
|
23
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key id
|
|
24
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/, // GitHub token (ghp_/gho_/ghu_/ghs_/ghr_); {20,} covers the 36-char + CI forms
|
|
25
|
+
/github_pat_[A-Za-z0-9_]{22,}/, // GitHub fine-grained PAT
|
|
26
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/, // Slack token
|
|
27
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style secret key
|
|
28
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/, // OpenAI project-scoped key (underscore form sk-… misses)
|
|
29
|
+
/AIza[0-9A-Za-z._-]{10,}/, // Google / Gemini API key
|
|
30
|
+
/sk_(?:live|test)_[A-Za-z0-9]{20,}/, // Stripe live/test secret key
|
|
31
|
+
/npm_[A-Za-z0-9]{20,}/, // npm publish / automation token
|
|
32
|
+
/\bey[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{3,}\.[A-Za-z0-9_-]{3,}\b/, // JWT (incl. Bearer payloads); the eyJ… header gates it
|
|
33
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/, // PEM block (FULL — must precede the header fallback so redaction eats the body)
|
|
34
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM header (fallback: a lone/truncated header with no END)
|
|
35
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/, // opaque Bearer token (non-JWT)
|
|
36
|
+
/\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD)\b\s*[:=]\s*\S+/i, // KEY/TOKEN/SECRET/PASSWORD = value
|
|
37
|
+
];
|
|
38
|
+
// sidecar-LOCAL extras (#38) the canonical set does not subsume — broader / hooks-layer-specific, so they
|
|
39
|
+
// are NOT part of the drift-pinned core. Kept so sidecar never weakens (no pre-existing match is lost).
|
|
40
|
+
const SIDECAR_EXTRA = [
|
|
41
|
+
/Bearer\s+[A-Za-z0-9\-._~+/]{16,}=*/i, // case-insensitive bearer scheme, 16-char floor (broader than the canonical Bearer)
|
|
26
42
|
// password=/pwd= assignment (=, :, quoted JSON/YAML). The keyword is EITHER fully quoted ("password":)
|
|
27
43
|
// OR bare (password=) — never a lone trailing quote, so a path ending in "passwd" used as a JSON key
|
|
28
44
|
// (e.g. "../../etc/passwd": …) is NOT misread as an assignment (it would falsely refuse a sidecar write).
|
|
29
45
|
/(?:["'](?:password|passwd|pwd)["']|(?:password|passwd|pwd))\s*[:=]\s*["']?[^\s"',;)&}{]+/i,
|
|
30
46
|
/[a-z][a-z0-9+.\-]+:\/\/[^\s:/@]+:[^\s/@]+@\S+/i, // URI connection string with inline creds (scheme://user:pass@host)
|
|
31
|
-
/eyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}/, // JWT
|
|
47
|
+
/eyJ[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}\.[A-Za-z0-9_-]{5,}/, // JWT (eyJ header; shorter min than the canonical JWT)
|
|
32
48
|
];
|
|
49
|
+
const SECRET_PATTERNS = [...SECRET_PATTERNS_CANON, ...SIDECAR_EXTRA];
|
|
33
50
|
|
|
34
51
|
function secretScan(text) {
|
|
35
52
|
const s = String(text || ''); // NOT lowercased — the token shapes are case-sensitive.
|
package/payload/.wrxn/dream.cjs
CHANGED
|
@@ -421,11 +421,20 @@ function negativeFilter(text) {
|
|
|
421
421
|
// token shapes are case-specific). Evidence quotes are audit-only (never written to a page) so they
|
|
422
422
|
// stay out of scope here, like the negative filters.
|
|
423
423
|
const SECRET_PATTERNS = [
|
|
424
|
-
/AKIA[0-9A-Z]{16}/,
|
|
425
|
-
/gh[pousr]_[A-Za-z0-9]{
|
|
426
|
-
/
|
|
427
|
-
/
|
|
428
|
-
|
|
424
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key id
|
|
425
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/, // GitHub token (ghp_/gho_/ghu_/ghs_/ghr_); {20,} covers the 36-char + CI forms
|
|
426
|
+
/github_pat_[A-Za-z0-9_]{22,}/, // GitHub fine-grained PAT
|
|
427
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/, // Slack token
|
|
428
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style secret key
|
|
429
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/, // OpenAI project-scoped key (underscore form sk-… misses)
|
|
430
|
+
/AIza[0-9A-Za-z._-]{10,}/, // Google / Gemini API key
|
|
431
|
+
/sk_(?:live|test)_[A-Za-z0-9]{20,}/, // Stripe live/test secret key
|
|
432
|
+
/npm_[A-Za-z0-9]{20,}/, // npm publish / automation token
|
|
433
|
+
/\bey[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{3,}\.[A-Za-z0-9_-]{3,}\b/, // JWT (incl. Bearer payloads); the eyJ… header gates it
|
|
434
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/, // PEM block (FULL — must precede the header fallback so redaction eats the body)
|
|
435
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM header (fallback: a lone/truncated header with no END)
|
|
436
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/, // opaque Bearer token (non-JWT)
|
|
437
|
+
/\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD)\b\s*[:=]\s*\S+/i, // KEY/TOKEN/SECRET/PASSWORD = value
|
|
429
438
|
];
|
|
430
439
|
|
|
431
440
|
function secretScan(text) {
|
|
@@ -653,11 +653,20 @@ function isKebab(s) {
|
|
|
653
653
|
// A merged survivor must never harden a session secret into recalled prose. Same patterns + case-sensitive
|
|
654
654
|
// scope as dream/sync — replicated because each install-only adapter is self-contained (node stdlib only).
|
|
655
655
|
const SECRET_PATTERNS = [
|
|
656
|
-
/AKIA[0-9A-Z]{16}/,
|
|
657
|
-
/gh[pousr]_[A-Za-z0-9]{
|
|
658
|
-
/
|
|
659
|
-
/
|
|
660
|
-
|
|
656
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key id
|
|
657
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/, // GitHub token (ghp_/gho_/ghu_/ghs_/ghr_); {20,} covers the 36-char + CI forms
|
|
658
|
+
/github_pat_[A-Za-z0-9_]{22,}/, // GitHub fine-grained PAT
|
|
659
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/, // Slack token
|
|
660
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style secret key
|
|
661
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/, // OpenAI project-scoped key (underscore form sk-… misses)
|
|
662
|
+
/AIza[0-9A-Za-z._-]{10,}/, // Google / Gemini API key
|
|
663
|
+
/sk_(?:live|test)_[A-Za-z0-9]{20,}/, // Stripe live/test secret key
|
|
664
|
+
/npm_[A-Za-z0-9]{20,}/, // npm publish / automation token
|
|
665
|
+
/\bey[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{3,}\.[A-Za-z0-9_-]{3,}\b/, // JWT (incl. Bearer payloads); the eyJ… header gates it
|
|
666
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/, // PEM block (FULL — must precede the header fallback so redaction eats the body)
|
|
667
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM header (fallback: a lone/truncated header with no END)
|
|
668
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/, // opaque Bearer token (non-JWT)
|
|
669
|
+
/\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD)\b\s*[:=]\s*\S+/i, // KEY/TOKEN/SECRET/PASSWORD = value
|
|
661
670
|
];
|
|
662
671
|
|
|
663
672
|
function secretScan(text) {
|
|
@@ -24,19 +24,28 @@ const path = require('path');
|
|
|
24
24
|
const https = require('https');
|
|
25
25
|
const { spawnSync } = require('child_process');
|
|
26
26
|
|
|
27
|
-
// ── default tiering (PRD)
|
|
28
|
-
// handoff
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
27
|
+
// ── default tiering (PRD + #59) ────────────────────────────────────────────────
|
|
28
|
+
// handoff stays FAST — gemini-3.1-flash-lite with thinkingBudget:0 (reasoning OFF), because handoff is on the
|
|
29
|
+
// 180s session-start hold path. dream REASONS — gemini-3.5-flash with thinkingBudget:-1 (dynamic, the model
|
|
30
|
+
// decides) + maxOutputTokens:8192 (sized for reasoning + the ~4.3k-char dream JSON so it can't truncate),
|
|
31
|
+
// because dream runs off the critical path where quality matters. Both fall back to claude/claude-sonnet-4-6.
|
|
32
|
+
// The seeded memory.config.json is DEFAULTS serialized; an operator edits it and `wrxn update` preserves it
|
|
33
|
+
// (seeded class), so EXISTING installs keep their config and only FRESH installs get these defaults (no
|
|
34
|
+
// migration). Claude stays the fallback so a keyless fresh install still writes a baton via the operator's CLI
|
|
35
|
+
// auth (no GEMINI_API_KEY → gemini gated → claude carries it). thinkingBudget: 0 off / -1 dynamic / N>0 bounded.
|
|
36
|
+
// DEFAULT_TASK is the conservative per-task default for any task NOT named in DEFAULTS.tasks (reasoning off,
|
|
37
|
+
// preserving #30's safe default); handoff + dream pin their own tiering below.
|
|
32
38
|
const DEFAULT_TASK = {
|
|
33
|
-
primary: { engine: 'gemini', model: 'gemini-3.1-flash-lite' },
|
|
39
|
+
primary: { engine: 'gemini', model: 'gemini-3.1-flash-lite', thinkingBudget: 0 },
|
|
34
40
|
fallback: { engine: 'claude', model: 'claude-sonnet-4-6' },
|
|
35
41
|
};
|
|
36
42
|
const DEFAULTS = {
|
|
37
43
|
tasks: {
|
|
38
44
|
handoff: clone(DEFAULT_TASK),
|
|
39
|
-
dream:
|
|
45
|
+
dream: {
|
|
46
|
+
primary: { engine: 'gemini', model: 'gemini-3.5-flash', thinkingBudget: -1, maxOutputTokens: 8192 },
|
|
47
|
+
fallback: { engine: 'claude', model: 'claude-sonnet-4-6' },
|
|
48
|
+
},
|
|
40
49
|
},
|
|
41
50
|
};
|
|
42
51
|
|
|
@@ -178,6 +187,51 @@ const THINK_MAX = 600;
|
|
|
178
187
|
const TOOL_USE_MAX = 300;
|
|
179
188
|
const TOOL_RESULT_MAX = 200;
|
|
180
189
|
|
|
190
|
+
// Hook-injected framework-context sentinels. SessionStart emits <wrxn-orientation> (which embeds the
|
|
191
|
+
// ENTIRE prior baton as the resume surface); UserPromptSubmit emits <synapse-rules>/<recall-surface>/
|
|
192
|
+
// <reference-candidate> each turn; <system-reminder> wraps harness notes. All land verbatim in the
|
|
193
|
+
// transcript. Left in, they out-mass a light session's real turns → the synth re-emits the embedded
|
|
194
|
+
// prior baton (fresh `wrote`, frozen content) — the content-channel rot of #62. Strip them BEFORE
|
|
195
|
+
// chunking. The blob builder is the ONE shared surface, so the claude and gemini paths both benefit.
|
|
196
|
+
const INJECTED_SENTINELS = ['wrxn-orientation', 'synapse-rules', 'recall-surface', 'reference-candidate', 'system-reminder'];
|
|
197
|
+
// ReDoS bound (#62): the phase-1 closed-block strip is ~quadratic on a pathological part (many opens, no
|
|
198
|
+
// closes). A real injected block (orientation embeds a ≤400-word baton) is a few KB; this cap sits far
|
|
199
|
+
// above that yet below the multi-second regex range, so the strip stays effectively linear.
|
|
200
|
+
const INJECTED_STRIP_MAX = 65536;
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Strip hook-injected framework-context blocks from one transcript text part BEFORE chunking. Two phases:
|
|
204
|
+
* 1. every well-delimited `<tag>…</tag>` block, anywhere (multi-line via [\s\S]; nested/sequential safe);
|
|
205
|
+
* 2. a part-LEADING unclosed `<tag>…` (transcript truncated mid-block) → stripped to end-of-part.
|
|
206
|
+
* The phase-2 strip is ANCHORED to part-leading (`^\s*` — after optional whitespace, incl. the newline a
|
|
207
|
+
* phase-1 removal leaves) because real framework injections are always part-leading. A sentinel merely
|
|
208
|
+
* MENTIONED mid-prose is therefore NOT a leading match, so its trailing text survives (Finding A). PURE
|
|
209
|
+
* and FAIL-OPEN: any fault returns the input unchanged — a filter fault must NEVER break the blob. Node
|
|
210
|
+
* stdlib only. Mirrors the redactSecrets scrub discipline. (#62)
|
|
211
|
+
* @param {string} text
|
|
212
|
+
* @returns {string}
|
|
213
|
+
*/
|
|
214
|
+
function stripInjectedContext(text) {
|
|
215
|
+
try {
|
|
216
|
+
let out = String(text || '');
|
|
217
|
+
// ReDoS guard (Finding B): skip a part far larger than any real injected block — it is ordinary content
|
|
218
|
+
// (a pasted file/log) with nothing part-leading to strip, so returning it as-is can never hang.
|
|
219
|
+
if (out.length > INJECTED_STRIP_MAX) return out;
|
|
220
|
+
// Phase 1 — remove every well-delimited block anywhere (low risk; sequential + nested blocks collapse).
|
|
221
|
+
for (const tag of INJECTED_SENTINELS) {
|
|
222
|
+
out = out.replace(new RegExp(`<${tag}>[\\s\\S]*?</${tag}>`, 'g'), '');
|
|
223
|
+
}
|
|
224
|
+
// Phase 2 — remove a part-LEADING unclosed remainder to end-of-part (after phase 1, any leading injected
|
|
225
|
+
// block left dangling by truncation is part-leading). Anchored so a mid-prose mention keeps its tail.
|
|
226
|
+
for (const tag of INJECTED_SENTINELS) {
|
|
227
|
+
out = out.replace(new RegExp(`^\\s*<${tag}>[\\s\\S]*$`), '');
|
|
228
|
+
}
|
|
229
|
+
return out;
|
|
230
|
+
} catch {
|
|
231
|
+
return String(text || ''); // a filter fault never breaks the blob
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
181
235
|
/**
|
|
182
236
|
* Build a bounded plain-text blob from a Claude Code transcript (JSONL string).
|
|
183
237
|
* @param {string} jsonlText the raw transcript file contents
|
|
@@ -199,12 +253,12 @@ function buildTranscriptBlob(jsonlText) {
|
|
|
199
253
|
const c = m.content;
|
|
200
254
|
const parts = [];
|
|
201
255
|
if (typeof c === 'string') {
|
|
202
|
-
parts.push(c);
|
|
256
|
+
parts.push(stripInjectedContext(c)); // a SessionStart-injected orientation often arrives as string content
|
|
203
257
|
} else if (Array.isArray(c)) {
|
|
204
258
|
for (const p of c) {
|
|
205
259
|
if (!p || typeof p !== 'object') continue;
|
|
206
260
|
if (p.type === 'text') {
|
|
207
|
-
parts.push(p.text || '');
|
|
261
|
+
parts.push(stripInjectedContext(p.text || '')); // UserPromptSubmit sentinels prepend the user's text part
|
|
208
262
|
} else if (p.type === 'thinking') {
|
|
209
263
|
parts.push('[thinking] ' + String(p.thinking || '').slice(0, THINK_MAX));
|
|
210
264
|
} else if (p.type === 'tool_use') {
|
|
@@ -272,15 +326,17 @@ function buildClaudeSpec({ model, prompt, blob }) {
|
|
|
272
326
|
* `x-goog-api-key` header, the task prompt as `system_instruction` and the blob as user content
|
|
273
327
|
* (mirrors the proven aimem-handoff-synth call). PURE.
|
|
274
328
|
*
|
|
275
|
-
* `
|
|
276
|
-
*
|
|
277
|
-
* HTTP-200 no-op on non-thinking models)
|
|
278
|
-
*
|
|
279
|
-
* forced-thinking models (gemma-class) that reject
|
|
329
|
+
* `thinkingBudget` is the PER-ENGINE reasoning directive (#59, generalizing #30): a NUMBER sets
|
|
330
|
+
* `generationConfig.thinkingConfig = { thinkingBudget }` — `0` disables model-side thinking (a verified
|
|
331
|
+
* HTTP-200 no-op on non-thinking models), `-1` lets the model decide (dynamic), `N>0` bounds it to N thinking
|
|
332
|
+
* tokens. An ABSENT budget defaults to `0` (preserves #30's safe default). An explicit `null` OMITS
|
|
333
|
+
* thinkingConfig entirely — the AC3 retry path for forced-thinking models (gemma-class) that reject the
|
|
334
|
+
* directive with an HTTP 400. `maxOutputTokens` is the per-engine output cap (default GEMINI_MAX_OUTPUT_TOKENS).
|
|
280
335
|
*/
|
|
281
|
-
function buildGeminiSpec({ model, prompt, blob, apiKey,
|
|
282
|
-
const generationConfig = { temperature: 0.2, maxOutputTokens
|
|
283
|
-
|
|
336
|
+
function buildGeminiSpec({ model, prompt, blob, apiKey, thinkingBudget = 0, maxOutputTokens = GEMINI_MAX_OUTPUT_TOKENS }) {
|
|
337
|
+
const generationConfig = { temperature: 0.2, maxOutputTokens };
|
|
338
|
+
// a NUMBER (incl. 0 and -1) sets the directive; an explicit `null` OMITS it (the AC3 retry-without path).
|
|
339
|
+
if (thinkingBudget !== null) generationConfig.thinkingConfig = { thinkingBudget };
|
|
284
340
|
return {
|
|
285
341
|
engine: 'gemini',
|
|
286
342
|
method: 'POST',
|
|
@@ -450,11 +506,12 @@ async function runEngine(engine, { prompt, blob, apiKey, invoke, sleep = default
|
|
|
450
506
|
callOnce = () => invoke(spec);
|
|
451
507
|
} else if (engine.engine === 'gemini') {
|
|
452
508
|
if (!apiKey) return { text: null, attempts: 0 }; // missing key fails this engine (→ fallback / null), no request, no retry.
|
|
509
|
+
const { thinkingBudget, maxOutputTokens } = engine; // per-engine reasoning config (#59); absent → buildGeminiSpec defaults (0 / 4096).
|
|
453
510
|
callOnce = async () => {
|
|
454
|
-
const r = await invoke(buildGeminiSpec({ model: engine.model, prompt, blob, apiKey,
|
|
511
|
+
const r = await invoke(buildGeminiSpec({ model: engine.model, prompt, blob, apiKey, thinkingBudget, maxOutputTokens }));
|
|
455
512
|
if (r && r.thinkingUnsupported) {
|
|
456
|
-
// this model rejects the thinkingConfig directive (forced-thinking / gemma-class) — retry once without it.
|
|
457
|
-
return invoke(buildGeminiSpec({ model: engine.model, prompt, blob, apiKey,
|
|
513
|
+
// this model rejects the thinkingConfig directive (forced-thinking / gemma-class) — retry once without it (same cap).
|
|
514
|
+
return invoke(buildGeminiSpec({ model: engine.model, prompt, blob, apiKey, thinkingBudget: null, maxOutputTokens }));
|
|
458
515
|
}
|
|
459
516
|
return r;
|
|
460
517
|
};
|
|
@@ -615,22 +672,31 @@ function stripPreamble(text) {
|
|
|
615
672
|
// becomes the durable baton. Pattern-based (high-signal vendor token shapes, JWTs incl. Bearer
|
|
616
673
|
// payloads, and `KEY/TOKEN/SECRET/PASSWORD=value` assignments); each match → `[REDACTED]`. Conservative
|
|
617
674
|
// by design: it never rewrites ordinary prose, only well-known credential shapes.
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
/\
|
|
675
|
+
// The ONE canonical secret-shape set — drift-pinned byte-identical to the dream / sync / harvest copies
|
|
676
|
+
// by adapter-drift-guard.test.cjs. Non-global base: redaction derives a g-flagged clone below. (Dropping
|
|
677
|
+
// the older inline \b boundaries is deliberate and fail-safe — a redactor erring broader never under-scrubs.)
|
|
678
|
+
const SECRET_PATTERNS = [
|
|
679
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key id
|
|
680
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/, // GitHub token (ghp_/gho_/ghu_/ghs_/ghr_); {20,} covers the 36-char + CI forms
|
|
681
|
+
/github_pat_[A-Za-z0-9_]{22,}/, // GitHub fine-grained PAT
|
|
682
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/, // Slack token
|
|
683
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style secret key
|
|
684
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/, // OpenAI project-scoped key (underscore form sk-… misses)
|
|
685
|
+
/AIza[0-9A-Za-z._-]{10,}/, // Google / Gemini API key
|
|
686
|
+
/sk_(?:live|test)_[A-Za-z0-9]{20,}/, // Stripe live/test secret key
|
|
687
|
+
/npm_[A-Za-z0-9]{20,}/, // npm publish / automation token
|
|
688
|
+
/\bey[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{3,}\.[A-Za-z0-9_-]{3,}\b/, // JWT (incl. Bearer payloads); the eyJ… header gates it
|
|
689
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/, // PEM block (FULL — must precede the header fallback so redaction eats the body)
|
|
690
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM header (fallback: a lone/truncated header with no END)
|
|
691
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/, // opaque Bearer token (non-JWT)
|
|
692
|
+
/\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD)\b\s*[:=]\s*\S+/i, // KEY/TOKEN/SECRET/PASSWORD = value
|
|
632
693
|
];
|
|
633
694
|
|
|
695
|
+
// Redaction form: every shape made global so EVERY occurrence on a line is scrubbed (not just the first).
|
|
696
|
+
// Each clone PRESERVES its own flags (e.g. the /i assignment shape) and only ADDS g — so detection and
|
|
697
|
+
// redaction never diverge. String#replace resets a global regex's lastIndex per call → reuse is safe.
|
|
698
|
+
const REDACTIONS = SECRET_PATTERNS.map((re) => new RegExp(re.source, re.flags.includes('g') ? re.flags : re.flags + 'g'));
|
|
699
|
+
|
|
634
700
|
/**
|
|
635
701
|
* Redact common secret shapes from `text`, replacing each match with `[REDACTED]`. PURE. Ordinary prose
|
|
636
702
|
* is preserved verbatim — only credential-looking substrings are scrubbed.
|
|
@@ -957,7 +1023,7 @@ async function run(args, { invoke = defaultInvoke, out = process.stdout, err = p
|
|
|
957
1023
|
return 2;
|
|
958
1024
|
}
|
|
959
1025
|
const root = rootArg || findInstallRoot(path.dirname(path.resolve(file))) || process.cwd();
|
|
960
|
-
const blob = readTranscriptBlob(file);
|
|
1026
|
+
const blob = redactSecrets(readTranscriptBlob(file)); // scrub BEFORE the blob egresses to the external model — parity with runHandoff:729 / runDream:866 (#63).
|
|
961
1027
|
const config = loadConfig(root);
|
|
962
1028
|
const apiKey = loadEnv(root).GEMINI_API_KEY;
|
|
963
1029
|
const text = await synthesize({ task, prompt, blob, config, apiKey, invoke });
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"tasks": {
|
|
3
3
|
"handoff": {
|
|
4
|
-
"primary": { "engine": "gemini", "model": "gemini-3.1-flash-lite" },
|
|
4
|
+
"primary": { "engine": "gemini", "model": "gemini-3.1-flash-lite", "thinkingBudget": 0 },
|
|
5
5
|
"fallback": { "engine": "claude", "model": "claude-sonnet-4-6" }
|
|
6
6
|
},
|
|
7
7
|
"dream": {
|
|
8
|
-
"primary": { "engine": "gemini", "model": "gemini-3.
|
|
8
|
+
"primary": { "engine": "gemini", "model": "gemini-3.5-flash", "thinkingBudget": -1, "maxOutputTokens": 8192 },
|
|
9
9
|
"fallback": { "engine": "claude", "model": "claude-sonnet-4-6" }
|
|
10
10
|
}
|
|
11
11
|
}
|
package/payload/.wrxn/sync.cjs
CHANGED
|
@@ -264,11 +264,20 @@ async function driftFromDoor(root, { transport, timeoutMs } = {}) {
|
|
|
264
264
|
// no shared kernel-lib import), exactly as findInstallRoot/flag/fail are duplicated across these files.
|
|
265
265
|
// CASE-SENSITIVE: the token shapes are case-specific.
|
|
266
266
|
const SECRET_PATTERNS = [
|
|
267
|
-
/AKIA[0-9A-Z]{16}/,
|
|
268
|
-
/gh[pousr]_[A-Za-z0-9]{
|
|
269
|
-
/
|
|
270
|
-
/
|
|
271
|
-
|
|
267
|
+
/AKIA[0-9A-Z]{16}/, // AWS access key id
|
|
268
|
+
/gh[pousr]_[A-Za-z0-9]{20,}/, // GitHub token (ghp_/gho_/ghu_/ghs_/ghr_); {20,} covers the 36-char + CI forms
|
|
269
|
+
/github_pat_[A-Za-z0-9_]{22,}/, // GitHub fine-grained PAT
|
|
270
|
+
/xox[baprs]-[A-Za-z0-9-]{10,}/, // Slack token
|
|
271
|
+
/sk-[A-Za-z0-9]{20,}/, // OpenAI-style secret key
|
|
272
|
+
/sk-proj-[A-Za-z0-9_-]{20,}/, // OpenAI project-scoped key (underscore form sk-… misses)
|
|
273
|
+
/AIza[0-9A-Za-z._-]{10,}/, // Google / Gemini API key
|
|
274
|
+
/sk_(?:live|test)_[A-Za-z0-9]{20,}/, // Stripe live/test secret key
|
|
275
|
+
/npm_[A-Za-z0-9]{20,}/, // npm publish / automation token
|
|
276
|
+
/\bey[A-Za-z0-9_-]{8,}\.[A-Za-z0-9_-]{3,}\.[A-Za-z0-9_-]{3,}\b/, // JWT (incl. Bearer payloads); the eyJ… header gates it
|
|
277
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----[\s\S]*?-----END [A-Z ]*PRIVATE KEY-----/, // PEM block (FULL — must precede the header fallback so redaction eats the body)
|
|
278
|
+
/-----BEGIN [A-Z ]*PRIVATE KEY-----/, // PEM header (fallback: a lone/truncated header with no END)
|
|
279
|
+
/Bearer\s+[A-Za-z0-9._~+/=-]{20,}/, // opaque Bearer token (non-JWT)
|
|
280
|
+
/\b[A-Za-z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD|PASSWD|PWD)\b\s*[:=]\s*\S+/i, // KEY/TOKEN/SECRET/PASSWORD = value
|
|
272
281
|
];
|
|
273
282
|
|
|
274
283
|
function secretScan(text) {
|