@wrongstack/core 0.299.0 → 0.300.0
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/dist/coordination/director.d.ts +8 -0
- package/dist/coordination/fleet-manager.d.ts +48 -3
- package/dist/coordination/ifleet-manager.d.ts +2 -0
- package/dist/coordination/index.js +120 -20
- package/dist/coordination/multi-agent-coordinator.d.ts +1 -0
- package/dist/core/fallback-model.d.ts +48 -0
- package/dist/core/index.d.ts +3 -2
- package/dist/core/index.js +226 -26
- package/dist/core/instruction-template.d.ts +80 -0
- package/dist/core/system-prompt-blocks.d.ts +10 -1
- package/dist/core/system-prompt-builder.d.ts +35 -1
- package/dist/defaults/index.js +238 -99
- package/dist/execution/autonomy-brain.d.ts +7 -0
- package/dist/execution/council-brain.d.ts +11 -0
- package/dist/execution/council-orchestrator.d.ts +23 -4
- package/dist/execution/council-prompts.d.ts +12 -1
- package/dist/execution/index.js +355 -138
- package/dist/fleet-notifier.d.ts +9 -2
- package/dist/hooks/index.js +8 -4
- package/dist/hq/index.js +18 -4
- package/dist/hq/protocol/fleet.d.ts +20 -0
- package/dist/hq/protocol.js +10 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1512 -707
- package/dist/kernel/events/brain-events.d.ts +9 -0
- package/dist/kernel/events/provider-events.d.ts +42 -1
- package/dist/models/index.js +1 -1
- package/dist/plugin/api.d.ts +6 -0
- package/dist/plugin/config.d.ts +55 -0
- package/dist/plugin/index.d.ts +1 -1
- package/dist/plugin/index.js +134 -21
- package/dist/security/index.d.ts +1 -1
- package/dist/security/index.js +157 -42
- package/dist/security/permission-helpers.d.ts +23 -6
- package/dist/security/permission-policy.d.ts +16 -0
- package/dist/security/totp.d.ts +14 -0
- package/dist/storage/director-state.d.ts +7 -0
- package/dist/storage/index.js +33 -8
- package/dist/tools/fallback-system-config-view-tool.d.ts +1 -1
- package/dist/tools/index.js +388 -102
- package/dist/types/council.d.ts +11 -0
- package/dist/types/index.d.ts +1 -1
- package/dist/types/multi-agent.d.ts +10 -0
- package/dist/types/one-shot-llm.d.ts +9 -0
- package/dist/types/plugin.d.ts +28 -0
- package/dist/worktree/index.js +4 -4
- package/instructions/system-lite.md +81 -3
- package/instructions/system-pro.md +275 -90
- package/instructions/system.md +228 -81
- package/package.json +3 -3
package/dist/security/index.js
CHANGED
|
@@ -712,22 +712,22 @@ var UNSAFE_PROPERTY_NAMES = /* @__PURE__ */ new Set(["__proto__", "prototype", "
|
|
|
712
712
|
function isRecord(value) {
|
|
713
713
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
714
714
|
}
|
|
715
|
-
function error(diagnostics, code,
|
|
716
|
-
diagnostics.push({ severity: "error", code, path:
|
|
715
|
+
function error(diagnostics, code, path9, message) {
|
|
716
|
+
diagnostics.push({ severity: "error", code, path: path9, message });
|
|
717
717
|
}
|
|
718
|
-
function validateStringList(value,
|
|
718
|
+
function validateStringList(value, path9, diagnostics, limits) {
|
|
719
719
|
if (!Array.isArray(value)) {
|
|
720
|
-
error(diagnostics, limits.listErrorCode,
|
|
720
|
+
error(diagnostics, limits.listErrorCode, path9, `must be an array of strings`);
|
|
721
721
|
return void 0;
|
|
722
722
|
}
|
|
723
723
|
if (value.length > limits.maxItems) {
|
|
724
|
-
error(diagnostics, limits.tooManyCode,
|
|
724
|
+
error(diagnostics, limits.tooManyCode, path9, `must contain at most ${limits.maxItems} ${limits.itemLabel}s`);
|
|
725
725
|
return void 0;
|
|
726
726
|
}
|
|
727
727
|
const seen = /* @__PURE__ */ new Set();
|
|
728
728
|
const out = [];
|
|
729
729
|
for (const [index, entry] of value.entries()) {
|
|
730
|
-
const itemPath = `${
|
|
730
|
+
const itemPath = `${path9}[${index}]`;
|
|
731
731
|
if (typeof entry !== "string" || entry.length === 0 || entry.length > limits.maxChars) {
|
|
732
732
|
error(
|
|
733
733
|
diagnostics,
|
|
@@ -1467,6 +1467,7 @@ function numericValue(value) {
|
|
|
1467
1467
|
|
|
1468
1468
|
// src/security/permission-policy.ts
|
|
1469
1469
|
import * as fs from "node:fs/promises";
|
|
1470
|
+
import * as path6 from "node:path";
|
|
1470
1471
|
|
|
1471
1472
|
// src/utils/atomic-write.ts
|
|
1472
1473
|
import {
|
|
@@ -1767,19 +1768,19 @@ var UNSAFE_PROPERTY_NAMES2 = /* @__PURE__ */ new Set(["__proto__", "prototype",
|
|
|
1767
1768
|
function isRecord2(value) {
|
|
1768
1769
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1769
1770
|
}
|
|
1770
|
-
function error2(diagnostics, code,
|
|
1771
|
-
diagnostics.push({ severity: "error", code, path:
|
|
1771
|
+
function error2(diagnostics, code, path9, message) {
|
|
1772
|
+
diagnostics.push({ severity: "error", code, path: path9, message });
|
|
1772
1773
|
}
|
|
1773
|
-
function validatePatterns(value,
|
|
1774
|
+
function validatePatterns(value, path9, diagnostics) {
|
|
1774
1775
|
if (!Array.isArray(value)) {
|
|
1775
|
-
error2(diagnostics, "invalid_pattern_list",
|
|
1776
|
+
error2(diagnostics, "invalid_pattern_list", path9, "must be an array of strings");
|
|
1776
1777
|
return void 0;
|
|
1777
1778
|
}
|
|
1778
1779
|
if (value.length > TRUST_POLICY_LIMITS.maxPatternsPerRule) {
|
|
1779
1780
|
error2(
|
|
1780
1781
|
diagnostics,
|
|
1781
1782
|
"too_many_patterns",
|
|
1782
|
-
|
|
1783
|
+
path9,
|
|
1783
1784
|
`must contain at most ${TRUST_POLICY_LIMITS.maxPatternsPerRule} patterns`
|
|
1784
1785
|
);
|
|
1785
1786
|
return void 0;
|
|
@@ -1787,7 +1788,7 @@ function validatePatterns(value, path6, diagnostics) {
|
|
|
1787
1788
|
const patterns = [];
|
|
1788
1789
|
const seen = /* @__PURE__ */ new Set();
|
|
1789
1790
|
for (const [index, pattern] of value.entries()) {
|
|
1790
|
-
const itemPath = `${
|
|
1791
|
+
const itemPath = `${path9}[${index}]`;
|
|
1791
1792
|
if (typeof pattern !== "string" || pattern.length === 0 || pattern.length > TRUST_POLICY_LIMITS.maxPatternChars) {
|
|
1792
1793
|
error2(
|
|
1793
1794
|
diagnostics,
|
|
@@ -2201,6 +2202,19 @@ var AutoApprovePermissionPolicy = class _AutoApprovePermissionPolicy {
|
|
|
2201
2202
|
}
|
|
2202
2203
|
};
|
|
2203
2204
|
|
|
2205
|
+
// src/security/permission-helpers.ts
|
|
2206
|
+
import { realpathSync } from "node:fs";
|
|
2207
|
+
import * as path5 from "node:path";
|
|
2208
|
+
|
|
2209
|
+
// src/utils/wstack-paths.ts
|
|
2210
|
+
import * as os from "node:os";
|
|
2211
|
+
import * as path4 from "node:path";
|
|
2212
|
+
function wstackGlobalRoot() {
|
|
2213
|
+
const fromEnv = process.env["WRONGSTACK_HOME"];
|
|
2214
|
+
if (fromEnv && fromEnv.trim().length > 0) return path4.resolve(fromEnv);
|
|
2215
|
+
return path4.join(os.homedir(), ".wrongstack");
|
|
2216
|
+
}
|
|
2217
|
+
|
|
2204
2218
|
// src/security/permission-helpers.ts
|
|
2205
2219
|
function matchesTrust(patterns, subject) {
|
|
2206
2220
|
return patterns.includes(subject) || matchAny(patterns, subject);
|
|
@@ -2272,9 +2286,51 @@ var SHELL_READ_VERBS = /* @__PURE__ */ new Set([
|
|
|
2272
2286
|
function stripShellQuotes(value) {
|
|
2273
2287
|
return value.replace(/^['"]|['"]$/g, "");
|
|
2274
2288
|
}
|
|
2289
|
+
var AGENT_STATE_SENSITIVE_BASENAMES = /^(?:config\.json|config\.local\.json|trust\.json|auth\.json|\.key)$/i;
|
|
2290
|
+
function unescapeGlobSubject(value) {
|
|
2291
|
+
return value.replace(/\\([*?[\]])/g, "$1");
|
|
2292
|
+
}
|
|
2293
|
+
function normalizeForCompare(value) {
|
|
2294
|
+
const forward = unescapeGlobSubject(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
2295
|
+
return process.platform === "win32" ? forward.toLowerCase() : forward;
|
|
2296
|
+
}
|
|
2297
|
+
function realpathOfNearestExisting(p) {
|
|
2298
|
+
let probe = p;
|
|
2299
|
+
const tail = [];
|
|
2300
|
+
for (; ; ) {
|
|
2301
|
+
try {
|
|
2302
|
+
return tail.length === 0 ? realpathSync(probe) : path5.join(realpathSync(probe), ...tail);
|
|
2303
|
+
} catch {
|
|
2304
|
+
const parent = path5.dirname(probe);
|
|
2305
|
+
if (parent === probe) return p;
|
|
2306
|
+
tail.unshift(path5.basename(probe));
|
|
2307
|
+
probe = parent;
|
|
2308
|
+
}
|
|
2309
|
+
}
|
|
2310
|
+
}
|
|
2311
|
+
var agentStateRootRealCache = /* @__PURE__ */ new Map();
|
|
2312
|
+
function isInsideAgentStateRoot(absPath) {
|
|
2313
|
+
const lexicalRoot = normalizeForCompare(path5.resolve(wstackGlobalRoot()));
|
|
2314
|
+
if (!lexicalRoot) return false;
|
|
2315
|
+
const target = normalizeForCompare(absPath);
|
|
2316
|
+
if (target === lexicalRoot || target.startsWith(`${lexicalRoot}/`)) return true;
|
|
2317
|
+
let realRoot = agentStateRootRealCache.get(lexicalRoot);
|
|
2318
|
+
if (realRoot === void 0) {
|
|
2319
|
+
realRoot = normalizeForCompare(realpathOfNearestExisting(path5.resolve(wstackGlobalRoot())));
|
|
2320
|
+
agentStateRootRealCache.set(lexicalRoot, realRoot);
|
|
2321
|
+
}
|
|
2322
|
+
if (!realRoot || realRoot === lexicalRoot) return false;
|
|
2323
|
+
const realTarget = normalizeForCompare(realpathOfNearestExisting(absPath));
|
|
2324
|
+
return realTarget === realRoot || realTarget.startsWith(`${realRoot}/`);
|
|
2325
|
+
}
|
|
2326
|
+
function isProtectedAgentStatePath(absPath) {
|
|
2327
|
+
if (!isInsideAgentStateRoot(absPath)) return false;
|
|
2328
|
+
return AGENT_STATE_SENSITIVE_BASENAMES.test(path5.basename(normalizeForCompare(absPath)));
|
|
2329
|
+
}
|
|
2275
2330
|
function pathLooksSensitive(rawPath) {
|
|
2276
2331
|
const normalized = stripShellQuotes(rawPath).replace(/\\/g, "/");
|
|
2277
|
-
|
|
2332
|
+
if (SENSITIVE_READ_PATHS.some((pattern) => pattern.test(normalized))) return true;
|
|
2333
|
+
return isProtectedAgentStatePath(normalized);
|
|
2278
2334
|
}
|
|
2279
2335
|
function inputPathLooksSensitive(input) {
|
|
2280
2336
|
if (!input || typeof input !== "object") return false;
|
|
@@ -2298,6 +2354,34 @@ function shellCommandReadsSensitivePath(command) {
|
|
|
2298
2354
|
}
|
|
2299
2355
|
|
|
2300
2356
|
// src/security/permission-policy.ts
|
|
2357
|
+
function fsWriteTargetPaths(input) {
|
|
2358
|
+
const out = [];
|
|
2359
|
+
if (!input || typeof input !== "object") return out;
|
|
2360
|
+
const obj = input;
|
|
2361
|
+
for (const key of [
|
|
2362
|
+
"path",
|
|
2363
|
+
"file_path",
|
|
2364
|
+
"file",
|
|
2365
|
+
"filePath",
|
|
2366
|
+
"files",
|
|
2367
|
+
"target",
|
|
2368
|
+
"targetPath",
|
|
2369
|
+
"out",
|
|
2370
|
+
"directory",
|
|
2371
|
+
"cwd",
|
|
2372
|
+
"template"
|
|
2373
|
+
]) {
|
|
2374
|
+
const value = obj[key];
|
|
2375
|
+
if (typeof value === "string") {
|
|
2376
|
+
if (value.length > 0) out.push(value);
|
|
2377
|
+
} else if (Array.isArray(value)) {
|
|
2378
|
+
for (const item of value) {
|
|
2379
|
+
if (typeof item === "string" && item.length > 0) out.push(item);
|
|
2380
|
+
}
|
|
2381
|
+
}
|
|
2382
|
+
}
|
|
2383
|
+
return out;
|
|
2384
|
+
}
|
|
2301
2385
|
var DefaultPermissionPolicy = class {
|
|
2302
2386
|
policy = {};
|
|
2303
2387
|
loaded = false;
|
|
@@ -2383,6 +2467,30 @@ var DefaultPermissionPolicy = class {
|
|
|
2383
2467
|
getYoloDestructive() {
|
|
2384
2468
|
return this.yoloDestructive;
|
|
2385
2469
|
}
|
|
2470
|
+
/**
|
|
2471
|
+
* True when this call is an FS_WRITE whose target lands inside WrongStack's
|
|
2472
|
+
* own state root.
|
|
2473
|
+
*
|
|
2474
|
+
* FS_WRITE tools disagree on where the target path lives: write/edit use
|
|
2475
|
+
* `path`, replace/format/git `files` (string OR array), patch `directory`,
|
|
2476
|
+
* design `out`, scaffold writes under `cwd`/`template`. Checking only
|
|
2477
|
+
* `path`/`file_path` let a state-root write merely switch tools, so every
|
|
2478
|
+
* path-bearing key is inspected.
|
|
2479
|
+
*
|
|
2480
|
+
* Shared by the YOLO carve-out and the eval-cache guard so the two cannot
|
|
2481
|
+
* drift: the carve-out scans all path keys while the cache key fingerprints
|
|
2482
|
+
* only the subject key, so the cache must consult the same predicate or a
|
|
2483
|
+
* secondary state-root key can be replayed a cached `auto`.
|
|
2484
|
+
*/
|
|
2485
|
+
hasAgentStateWriteTarget(tool, input, ctx) {
|
|
2486
|
+
if (!hasCapability(tool, ToolCapabilities.FS_WRITE)) return false;
|
|
2487
|
+
for (const targetPath of fsWriteTargetPaths(input)) {
|
|
2488
|
+
const base = ctx.workingDir ?? ctx.cwd;
|
|
2489
|
+
const resolved = base ? path6.resolve(base, targetPath) : path6.resolve(targetPath);
|
|
2490
|
+
if (isInsideAgentStateRoot(resolved)) return true;
|
|
2491
|
+
}
|
|
2492
|
+
return false;
|
|
2493
|
+
}
|
|
2386
2494
|
/**
|
|
2387
2495
|
* True when YOLO is on but this specific call is a destructive shell command
|
|
2388
2496
|
* the user has not opted to auto-approve. Shared by `evaluate()` and the
|
|
@@ -2390,6 +2498,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2390
2498
|
*/
|
|
2391
2499
|
yoloBlockedAsDestructive(tool, input, ctx) {
|
|
2392
2500
|
if (!this.yolo || this.yoloDestructive) return false;
|
|
2501
|
+
if (this.hasAgentStateWriteTarget(tool, input, ctx)) return true;
|
|
2393
2502
|
const isShellSurface = tool.name === "bash" || tool.name === "exec" || (tool.capabilities ?? []).includes("shell.arbitrary");
|
|
2394
2503
|
if (!isShellSurface) return false;
|
|
2395
2504
|
const command = getInputString(input, "command") ?? shellCommandLineFromInput(input);
|
|
@@ -2496,7 +2605,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2496
2605
|
const subject = subjectForToolInput(tool.name, input, tool.subjectKey);
|
|
2497
2606
|
const cacheKey = `${tool.name}::${subject ?? tool.name}`;
|
|
2498
2607
|
const evalKey = `${cacheKey}::${permissionFingerprint(tool)}`;
|
|
2499
|
-
if (tool.name !== "write") {
|
|
2608
|
+
if (tool.name !== "write" && !this.hasAgentStateWriteTarget(tool, input, ctx)) {
|
|
2500
2609
|
const cached = this._evalCache.get(evalKey);
|
|
2501
2610
|
if (cached !== void 0) return cached;
|
|
2502
2611
|
}
|
|
@@ -2604,7 +2713,7 @@ var DefaultPermissionPolicy = class {
|
|
|
2604
2713
|
return decision;
|
|
2605
2714
|
}
|
|
2606
2715
|
if (tool.name === "write" && subject) {
|
|
2607
|
-
if (ctx.hasRead(subject)) {
|
|
2716
|
+
if (ctx.hasRead(subject) && !isInsideAgentStateRoot(subject)) {
|
|
2608
2717
|
return {
|
|
2609
2718
|
permission: "auto",
|
|
2610
2719
|
source: "context",
|
|
@@ -2954,13 +3063,14 @@ var DefaultPermissionPolicy = class {
|
|
|
2954
3063
|
}
|
|
2955
3064
|
add("yolo", false, "auto", "yolo", "YOLO mode is not active");
|
|
2956
3065
|
if (tool.name === "write" && subject) {
|
|
2957
|
-
const
|
|
3066
|
+
const isAgentState = isInsideAgentStateRoot(subject);
|
|
3067
|
+
const hasRead = ctx.hasRead(subject) && !isAgentState;
|
|
2958
3068
|
add(
|
|
2959
3069
|
"write smart bypass",
|
|
2960
3070
|
hasRead,
|
|
2961
3071
|
"auto",
|
|
2962
3072
|
"context",
|
|
2963
|
-
hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
3073
|
+
isAgentState ? `file "${subject}" is WrongStack's own state \u2014 bypass never applies, write always confirms` : hasRead ? `file "${subject}" was already read in this session \u2014 auto-approving write` : `file "${subject}" was not read in this session \u2014 bypass does not apply`
|
|
2964
3074
|
);
|
|
2965
3075
|
if (hasRead) {
|
|
2966
3076
|
winnerIndex = steps.length - 1;
|
|
@@ -3042,7 +3152,7 @@ var DefaultPermissionPolicy = class {
|
|
|
3042
3152
|
};
|
|
3043
3153
|
|
|
3044
3154
|
// src/security/readonly-permission-policy.ts
|
|
3045
|
-
import * as
|
|
3155
|
+
import * as path7 from "node:path";
|
|
3046
3156
|
var MUTATION_CAPABILITIES = /* @__PURE__ */ new Set([
|
|
3047
3157
|
ToolCapabilities.FS_WRITE,
|
|
3048
3158
|
ToolCapabilities.FS_WRITE_OUTSIDE_PROJECT,
|
|
@@ -3066,9 +3176,9 @@ function isAllowedReportFile(input, projectRoot) {
|
|
|
3066
3176
|
if (typeof inputPath !== "string") return false;
|
|
3067
3177
|
if (!inputPath.endsWith(".md")) return false;
|
|
3068
3178
|
try {
|
|
3069
|
-
const resolved =
|
|
3070
|
-
const tempDir =
|
|
3071
|
-
if (!resolved.startsWith(tempDir +
|
|
3179
|
+
const resolved = path7.resolve(projectRoot, inputPath);
|
|
3180
|
+
const tempDir = path7.resolve(projectRoot, ".temp_files");
|
|
3181
|
+
if (!resolved.startsWith(tempDir + path7.sep) && resolved !== tempDir) return false;
|
|
3072
3182
|
return true;
|
|
3073
3183
|
} catch {
|
|
3074
3184
|
return false;
|
|
@@ -3217,16 +3327,20 @@ function generateTotp(secretBase32, atMs = Date.now(), stepSeconds = DEFAULT_STE
|
|
|
3217
3327
|
return hotp(secret, counter, digits);
|
|
3218
3328
|
}
|
|
3219
3329
|
function verifyTotp(code, secretBase32, atMs = Date.now(), window = DEFAULT_WINDOW, stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3220
|
-
|
|
3330
|
+
return verifyTotpCounter(code, secretBase32, atMs, window, stepSeconds, digits) !== void 0;
|
|
3331
|
+
}
|
|
3332
|
+
function verifyTotpCounter(code, secretBase32, atMs = Date.now(), window = DEFAULT_WINDOW, stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3333
|
+
if (typeof code !== "string" || code.length !== digits) return void 0;
|
|
3221
3334
|
const secret = base32Decode(secretBase32);
|
|
3222
3335
|
const currentCounter = totpCounter(Math.floor(atMs / 1e3), stepSeconds);
|
|
3223
3336
|
for (let offset = -window; offset <= window; offset++) {
|
|
3224
|
-
const
|
|
3337
|
+
const counter = currentCounter + offset;
|
|
3338
|
+
const expected = hotp(secret, counter, digits);
|
|
3225
3339
|
if (timingSafeEqual(Buffer.from(expected), Buffer.from(code))) {
|
|
3226
|
-
return
|
|
3340
|
+
return counter;
|
|
3227
3341
|
}
|
|
3228
3342
|
}
|
|
3229
|
-
return
|
|
3343
|
+
return void 0;
|
|
3230
3344
|
}
|
|
3231
3345
|
function buildOtpAuthUri(secretBase32, account, issuer = "WrongStack HQ", stepSeconds = DEFAULT_STEP_SECONDS, digits = DEFAULT_DIGITS) {
|
|
3232
3346
|
const label = `${encodeURIComponent(issuer)}:${encodeURIComponent(account)}`;
|
|
@@ -3275,7 +3389,7 @@ function verifyRecoveryCode(code, storedHashes) {
|
|
|
3275
3389
|
import { createCipheriv, createDecipheriv, randomBytes as randomBytes2, scryptSync } from "node:crypto";
|
|
3276
3390
|
import * as fs2 from "node:fs";
|
|
3277
3391
|
import * as fsp from "node:fs/promises";
|
|
3278
|
-
import * as
|
|
3392
|
+
import * as path8 from "node:path";
|
|
3279
3393
|
|
|
3280
3394
|
// src/utils/deep-merge.ts
|
|
3281
3395
|
var FORBIDDEN_PROTO_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -3578,7 +3692,7 @@ var DefaultSecretVault = class {
|
|
|
3578
3692
|
const oldVersion = this._keyVersion;
|
|
3579
3693
|
const newKey = randomBytes2(KEY_BYTES);
|
|
3580
3694
|
const newVersion = oldVersion + 1;
|
|
3581
|
-
fs2.mkdirSync(
|
|
3695
|
+
fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
|
|
3582
3696
|
const passphrase = getVaultPassphrase();
|
|
3583
3697
|
if (passphrase) {
|
|
3584
3698
|
writeKeyFileAtomicSync(this.keyFile, wrapDataKey(newKey, newVersion, passphrase));
|
|
@@ -3665,7 +3779,7 @@ var DefaultSecretVault = class {
|
|
|
3665
3779
|
} catch (err) {
|
|
3666
3780
|
if (err.code !== "ENOENT") throw err;
|
|
3667
3781
|
}
|
|
3668
|
-
fs2.mkdirSync(
|
|
3782
|
+
fs2.mkdirSync(path8.dirname(this.keyFile), { recursive: true });
|
|
3669
3783
|
const key = randomBytes2(KEY_BYTES);
|
|
3670
3784
|
const passphrase = getVaultPassphrase();
|
|
3671
3785
|
const initialBytes = passphrase ? wrapDataKey(key, 1, passphrase) : key;
|
|
@@ -3725,7 +3839,7 @@ async function rewriteConfigEncrypted(configPath, vault, patch) {
|
|
|
3725
3839
|
}
|
|
3726
3840
|
const merged = deepMerge(current, patch ?? {});
|
|
3727
3841
|
const encrypted = encryptConfigSecrets(merged, vault);
|
|
3728
|
-
await fsp.mkdir(
|
|
3842
|
+
await fsp.mkdir(path8.dirname(configPath), { recursive: true });
|
|
3729
3843
|
await atomicWrite(configPath, JSON.stringify(encrypted, null, 2), { mode: 384 });
|
|
3730
3844
|
await restrictFilePermissions2(configPath);
|
|
3731
3845
|
await vault.flushHardening?.();
|
|
@@ -3947,33 +4061,33 @@ var RESERVED_BYPASS_FIELDS = /* @__PURE__ */ new Set([
|
|
|
3947
4061
|
function isRecord3(value) {
|
|
3948
4062
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3949
4063
|
}
|
|
3950
|
-
function addString(issues, value,
|
|
4064
|
+
function addString(issues, value, path9, required = true) {
|
|
3951
4065
|
if (typeof value === "string" && value.trim().length > 0) return true;
|
|
3952
4066
|
if (!required && value === void 0) return false;
|
|
3953
|
-
issues.push({ path:
|
|
4067
|
+
issues.push({ path: path9, message: "must be a non-empty string" });
|
|
3954
4068
|
return false;
|
|
3955
4069
|
}
|
|
3956
|
-
function addEnum(issues, value,
|
|
4070
|
+
function addEnum(issues, value, path9, values) {
|
|
3957
4071
|
if (typeof value === "string" && values.has(value)) return true;
|
|
3958
|
-
issues.push({ path:
|
|
4072
|
+
issues.push({ path: path9, message: `must be one of: ${[...values].join(", ")}` });
|
|
3959
4073
|
return false;
|
|
3960
4074
|
}
|
|
3961
|
-
function validateAttributes(issues, value,
|
|
4075
|
+
function validateAttributes(issues, value, path9) {
|
|
3962
4076
|
if (!isRecord3(value)) {
|
|
3963
|
-
issues.push({ path:
|
|
4077
|
+
issues.push({ path: path9, message: "must be an object" });
|
|
3964
4078
|
return false;
|
|
3965
4079
|
}
|
|
3966
4080
|
let valid = true;
|
|
3967
4081
|
for (const [key, item] of Object.entries(value)) {
|
|
3968
4082
|
if (RESERVED_BYPASS_FIELDS.has(key)) {
|
|
3969
4083
|
issues.push({
|
|
3970
|
-
path: `${
|
|
4084
|
+
path: `${path9}.${key}`,
|
|
3971
4085
|
message: "generic authorization bypass fields are forbidden"
|
|
3972
4086
|
});
|
|
3973
4087
|
valid = false;
|
|
3974
4088
|
} else if (item !== null && !["string", "number", "boolean"].includes(typeof item)) {
|
|
3975
4089
|
issues.push({
|
|
3976
|
-
path: `${
|
|
4090
|
+
path: `${path9}.${key}`,
|
|
3977
4091
|
message: "must be a string, number, boolean, or null"
|
|
3978
4092
|
});
|
|
3979
4093
|
valid = false;
|
|
@@ -3981,16 +4095,16 @@ function validateAttributes(issues, value, path6) {
|
|
|
3981
4095
|
}
|
|
3982
4096
|
return valid;
|
|
3983
4097
|
}
|
|
3984
|
-
function validateScope(issues, value,
|
|
4098
|
+
function validateScope(issues, value, path9) {
|
|
3985
4099
|
if (!isRecord3(value)) {
|
|
3986
|
-
issues.push({ path:
|
|
4100
|
+
issues.push({ path: path9, message: "must be an object" });
|
|
3987
4101
|
return false;
|
|
3988
4102
|
}
|
|
3989
4103
|
for (const field of ["projectId", "sessionId", "cwd"]) {
|
|
3990
|
-
if (value[field] !== void 0) addString(issues, value[field], `${
|
|
4104
|
+
if (value[field] !== void 0) addString(issues, value[field], `${path9}.${field}`);
|
|
3991
4105
|
}
|
|
3992
4106
|
if (value.constraints !== void 0)
|
|
3993
|
-
validateAttributes(issues, value.constraints, `${
|
|
4107
|
+
validateAttributes(issues, value.constraints, `${path9}.constraints`);
|
|
3994
4108
|
return true;
|
|
3995
4109
|
}
|
|
3996
4110
|
function decodeTrustBoundaryRequest(input) {
|
|
@@ -4116,6 +4230,7 @@ export {
|
|
|
4116
4230
|
validateDirectoryPolicy,
|
|
4117
4231
|
validateTrustPolicy,
|
|
4118
4232
|
verifyRecoveryCode,
|
|
4119
|
-
verifyTotp
|
|
4233
|
+
verifyTotp,
|
|
4234
|
+
verifyTotpCounter
|
|
4120
4235
|
};
|
|
4121
4236
|
//# sourceMappingURL=index.js.map
|
|
@@ -1,9 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Pure helpers for permission policy evaluation — trust-pattern matching,
|
|
3
|
-
* tool fingerprinting, and sensitive-read detection.
|
|
4
|
-
*
|
|
5
|
-
* Extracted from permission-policy.ts. No class state, no I/O.
|
|
6
|
-
*/
|
|
7
1
|
import type { Tool } from '../types/tool.js';
|
|
8
2
|
/**
|
|
9
3
|
* Match a computed subject against stored trust patterns.
|
|
@@ -88,6 +82,29 @@ export declare function alwaysAllowUnavailableReason(tool: Tool, input: unknown)
|
|
|
88
82
|
*/
|
|
89
83
|
export declare function permissionFingerprint(tool: Tool): string;
|
|
90
84
|
export declare function shellCommandLineFromInput(input: unknown): string | undefined;
|
|
85
|
+
/**
|
|
86
|
+
* True when `absPath` is the wstack global root (`~/.wrongstack`, or
|
|
87
|
+
* `WRONGSTACK_HOME`) or lives inside it.
|
|
88
|
+
*
|
|
89
|
+
* Callers use this to refuse *silent* access to WrongStack's own state. It is
|
|
90
|
+
* not a containment check — the file tools deliberately allow this root so the
|
|
91
|
+
* agent can manage its memory and sessions. What it gates is whether that
|
|
92
|
+
* access may happen without the user seeing it.
|
|
93
|
+
*
|
|
94
|
+
* The comparison is realpath-aware on both sides: `~/.wrongstack` may itself
|
|
95
|
+
* be a symlink, and the file tools canonicalize subjects to realpaths
|
|
96
|
+
* (`safeResolveReal`, WS-048), so a purely lexical check misses every state
|
|
97
|
+
* file under a symlinked root. The lexical pass stays first — it is free and
|
|
98
|
+
* correct for the default layout — and the realpath pass only runs when it
|
|
99
|
+
* fails, comparing the root (cached) against the subject's nearest existing
|
|
100
|
+
* ancestor.
|
|
101
|
+
*/
|
|
102
|
+
export declare function isInsideAgentStateRoot(absPath: string): boolean;
|
|
103
|
+
/**
|
|
104
|
+
* True when writing `absPath` must not be silently auto-approved, because the
|
|
105
|
+
* file is part of WrongStack's own trusted state rather than project content.
|
|
106
|
+
*/
|
|
107
|
+
export declare function isProtectedAgentStatePath(absPath: string): boolean;
|
|
91
108
|
export declare function inputPathLooksSensitive(input: unknown): boolean;
|
|
92
109
|
export declare function shellCommandReadsSensitivePath(command: string): boolean;
|
|
93
110
|
//# sourceMappingURL=permission-helpers.d.ts.map
|
|
@@ -92,6 +92,22 @@ export declare class DefaultPermissionPolicy implements PermissionPolicy {
|
|
|
92
92
|
setYoloDestructive(enabled: boolean): void;
|
|
93
93
|
/** Whether YOLO currently extends to destructive shell commands. */
|
|
94
94
|
getYoloDestructive(): boolean;
|
|
95
|
+
/**
|
|
96
|
+
* True when this call is an FS_WRITE whose target lands inside WrongStack's
|
|
97
|
+
* own state root.
|
|
98
|
+
*
|
|
99
|
+
* FS_WRITE tools disagree on where the target path lives: write/edit use
|
|
100
|
+
* `path`, replace/format/git `files` (string OR array), patch `directory`,
|
|
101
|
+
* design `out`, scaffold writes under `cwd`/`template`. Checking only
|
|
102
|
+
* `path`/`file_path` let a state-root write merely switch tools, so every
|
|
103
|
+
* path-bearing key is inspected.
|
|
104
|
+
*
|
|
105
|
+
* Shared by the YOLO carve-out and the eval-cache guard so the two cannot
|
|
106
|
+
* drift: the carve-out scans all path keys while the cache key fingerprints
|
|
107
|
+
* only the subject key, so the cache must consult the same predicate or a
|
|
108
|
+
* secondary state-root key can be replayed a cached `auto`.
|
|
109
|
+
*/
|
|
110
|
+
private hasAgentStateWriteTarget;
|
|
95
111
|
/**
|
|
96
112
|
* True when YOLO is on but this specific call is a destructive shell command
|
|
97
113
|
* the user has not opted to auto-approve. Shared by `evaluate()` and the
|
package/dist/security/totp.d.ts
CHANGED
|
@@ -55,6 +55,20 @@ export declare function generateTotp(secretBase32: string, atMs?: number, stepSe
|
|
|
55
55
|
* @returns `true` if the code matches any step within the window.
|
|
56
56
|
*/
|
|
57
57
|
export declare function verifyTotp(code: string, secretBase32: string, atMs?: number, window?: number, stepSeconds?: number, digits?: number): boolean;
|
|
58
|
+
/**
|
|
59
|
+
* Like {@link verifyTotp}, but returns the time-step counter the code matched
|
|
60
|
+
* so the caller can enforce single use.
|
|
61
|
+
*
|
|
62
|
+
* RFC 6238 §5.2 requires that a code be accepted only once: the validation
|
|
63
|
+
* window is ±1 step by default, so a code observed in transit stays
|
|
64
|
+
* arithmetically valid for up to ~90 seconds. Recording the matched counter and
|
|
65
|
+
* refusing anything at or below it turns that window into one-shot. HQ's
|
|
66
|
+
* `/api/login/verify` does this against `mutableAuth.totpLastUsedCounter`.
|
|
67
|
+
*
|
|
68
|
+
* @returns the matched counter, or `undefined` when no step in the window
|
|
69
|
+
* matches.
|
|
70
|
+
*/
|
|
71
|
+
export declare function verifyTotpCounter(code: string, secretBase32: string, atMs?: number, window?: number, stepSeconds?: number, digits?: number): number | undefined;
|
|
58
72
|
/**
|
|
59
73
|
* Build an `otpauth://` URI for QR-code provisioning (RFC 6238 / Google Auth).
|
|
60
74
|
*
|
|
@@ -127,6 +127,13 @@ export declare class DirectorStateCheckpoint {
|
|
|
127
127
|
* checkpoint continues from there.
|
|
128
128
|
*/
|
|
129
129
|
resume(snapshot: DirectorStateSnapshot): void;
|
|
130
|
+
/**
|
|
131
|
+
* After resume, pin the live spawn ceiling from the current profile/flag
|
|
132
|
+
* while preserving `spawnCount` (cumulative used budget). Checkpoint
|
|
133
|
+
* metadata previously stored a historical `maxSpawns` that can diverge
|
|
134
|
+
* from the live runtime ceiling — operators need the live value to win.
|
|
135
|
+
*/
|
|
136
|
+
applyLiveMaxSpawns(maxSpawns: number | undefined): void;
|
|
130
137
|
current(): DirectorStateSnapshot;
|
|
131
138
|
recordSpawn(sub: DirectorSubagentState, spawnCount: number): void;
|
|
132
139
|
recordTaskAssigned(task: DirectorTaskState): void;
|
package/dist/storage/index.js
CHANGED
|
@@ -10983,11 +10983,15 @@ var FleetNotifier = class {
|
|
|
10983
10983
|
}
|
|
10984
10984
|
/** Resolve same-project WebUI ping URLs (cached briefly). Exposed for tests. */
|
|
10985
10985
|
async endpoints() {
|
|
10986
|
+
return (await this.targets()).map((t) => t.url);
|
|
10987
|
+
}
|
|
10988
|
+
/** Ping targets with their tokens (cached briefly). */
|
|
10989
|
+
async targets() {
|
|
10986
10990
|
const now = Date.now();
|
|
10987
|
-
if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.
|
|
10988
|
-
const
|
|
10989
|
-
this.cache = { at: now,
|
|
10990
|
-
return
|
|
10991
|
+
if (this.cache && now - this.cache.at < DISCOVERY_TTL_MS) return this.cache.targets;
|
|
10992
|
+
const targets = await this.discover();
|
|
10993
|
+
this.cache = { at: now, targets };
|
|
10994
|
+
return targets;
|
|
10991
10995
|
}
|
|
10992
10996
|
dispose() {
|
|
10993
10997
|
this.disposed = true;
|
|
@@ -11002,8 +11006,8 @@ var FleetNotifier = class {
|
|
|
11002
11006
|
this.cache = null;
|
|
11003
11007
|
}
|
|
11004
11008
|
async flush() {
|
|
11005
|
-
const
|
|
11006
|
-
await Promise.all(
|
|
11009
|
+
const targets = await this.targets();
|
|
11010
|
+
await Promise.all(targets.map((t) => this.doPost(t.url, t.token).catch(() => void 0)));
|
|
11007
11011
|
}
|
|
11008
11012
|
async discover() {
|
|
11009
11013
|
try {
|
|
@@ -11012,16 +11016,22 @@ var FleetNotifier = class {
|
|
|
11012
11016
|
const list = Array.isArray(data?.instances) ? data.instances : [];
|
|
11013
11017
|
return list.filter((i) => i && typeof i.httpPort === "number").filter((i) => i.pid !== this.selfPid).filter((i) => normRoot(i.projectRoot) === this.projectRoot).filter((i) => pidAlive2(i.pid)).map((i) => {
|
|
11014
11018
|
const host = i.host === "0.0.0.0" || i.host === "::" || !i.host ? "127.0.0.1" : i.host;
|
|
11015
|
-
return
|
|
11019
|
+
return {
|
|
11020
|
+
url: `http://${host}:${i.httpPort}/api/fleet/ping`,
|
|
11021
|
+
token: typeof i.authToken === "string" ? i.authToken : void 0
|
|
11022
|
+
};
|
|
11016
11023
|
});
|
|
11017
11024
|
} catch {
|
|
11018
11025
|
return [];
|
|
11019
11026
|
}
|
|
11020
11027
|
}
|
|
11021
11028
|
};
|
|
11022
|
-
async function defaultPost(url) {
|
|
11029
|
+
async function defaultPost(url, token) {
|
|
11023
11030
|
await fetch(url, {
|
|
11024
11031
|
method: "POST",
|
|
11032
|
+
// The API requires a token on every bind (H3). Sent as a header rather
|
|
11033
|
+
// than a query param so it never lands in an access log or the URL.
|
|
11034
|
+
...token ? { headers: { "x-ws-token": token } } : {},
|
|
11025
11035
|
signal: AbortSignal.timeout(POST_TIMEOUT_MS)
|
|
11026
11036
|
});
|
|
11027
11037
|
}
|
|
@@ -11998,6 +12008,21 @@ var DirectorStateCheckpoint = class {
|
|
|
11998
12008
|
resume(snapshot) {
|
|
11999
12009
|
this.snapshot = snapshot;
|
|
12000
12010
|
}
|
|
12011
|
+
/**
|
|
12012
|
+
* After resume, pin the live spawn ceiling from the current profile/flag
|
|
12013
|
+
* while preserving `spawnCount` (cumulative used budget). Checkpoint
|
|
12014
|
+
* metadata previously stored a historical `maxSpawns` that can diverge
|
|
12015
|
+
* from the live runtime ceiling — operators need the live value to win.
|
|
12016
|
+
*/
|
|
12017
|
+
applyLiveMaxSpawns(maxSpawns) {
|
|
12018
|
+
if (this.snapshot.maxSpawns === maxSpawns) return;
|
|
12019
|
+
this.snapshot = {
|
|
12020
|
+
...this.snapshot,
|
|
12021
|
+
maxSpawns
|
|
12022
|
+
};
|
|
12023
|
+
this.bumpUpdatedAt();
|
|
12024
|
+
this.schedule();
|
|
12025
|
+
}
|
|
12001
12026
|
current() {
|
|
12002
12027
|
return this.snapshot;
|
|
12003
12028
|
}
|
|
@@ -2,7 +2,7 @@ import type { Tool } from '../types/tool.js';
|
|
|
2
2
|
import type { FallbackManageToolOptions } from './fallback-manage-tool-options.js';
|
|
3
3
|
export declare const SYSTEM_CONFIG_VIEW_TOOL_NAME = "system_config_view";
|
|
4
4
|
interface SystemConfigViewInput {
|
|
5
|
-
section?: 'all' | 'providers' | 'models' | 'fallbacks' | 'matrix' | 'agents' | 'refiner' | 'doctor' | undefined;
|
|
5
|
+
section?: 'all' | 'providers' | 'models' | 'fallbacks' | 'matrix' | 'agents' | 'fleet' | 'refiner' | 'doctor' | undefined;
|
|
6
6
|
}
|
|
7
7
|
interface SystemConfigViewOutput {
|
|
8
8
|
status: 'ok';
|