opencode-swarm 7.138.3 → 7.139.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/.opencode/skills/execute/SKILL.md +11 -2
- package/.opencode/skills/swarm-implement/SKILL.md +1 -1
- package/dist/cli/{config-doctor-2mwgdbsn.js → config-doctor-aszp7z1h.js} +2 -2
- package/dist/cli/{core-v6bte964.js → core-3s832mxx.js} +1 -1
- package/dist/cli/{curation-policy-5dqexw5j.js → curation-policy-a3hs73f7.js} +6 -6
- package/dist/cli/{curator-llm-factory-wkjme59b.js → curator-llm-factory-e8ngwyjm.js} +26 -25
- package/dist/cli/{curator-jjtp9hn2.js → curator-zxjqcc8r.js} +26 -25
- package/dist/cli/{evidence-summary-service-6cttrkzw.js → evidence-summary-service-qfhjfxdz.js} +7 -7
- package/dist/cli/{gate-evidence-ecx4djaw.js → gate-evidence-m2grv70v.js} +4 -4
- package/dist/cli/{guardrail-explain-xppwfnxr.js → guardrail-explain-rqpmzxyy.js} +27 -26
- package/dist/cli/{guardrail-log-swet7rch.js → guardrail-log-vd4a0ng5.js} +4 -3
- package/dist/cli/{hive-promoter-xvq8jzbs.js → hive-promoter-jc5vym27.js} +26 -25
- package/dist/cli/{index-tc61fybd.js → index-0tsetsxs.js} +4 -4
- package/dist/cli/{index-jhb8kjc5.js → index-3nr2sn32.js} +3 -3
- package/dist/cli/{index-y06a43sk.js → index-4ft523nb.js} +323 -80
- package/dist/cli/index-556ja8an.js +62 -0
- package/dist/cli/{index-31vfewmn.js → index-5bkpyvdf.js} +271 -94
- package/dist/cli/{index-wqg74mwj.js → index-67yqywan.js} +2 -2
- package/dist/cli/{index-gda7cwqz.js → index-6q240xgm.js} +30 -68
- package/dist/cli/{index-adde7nm2.js → index-84s6x604.js} +5 -5
- package/dist/cli/{index-j0xx9wpj.js → index-85hstja2.js} +1 -1
- package/dist/cli/{index-xybtaph7.js → index-a614s16f.js} +1 -1
- package/dist/cli/{index-10mz5js6.js → index-chvqee1j.js} +2 -2
- package/dist/cli/{index-rrxcfxrd.js → index-ddmnhv4h.js} +1 -1
- package/dist/cli/{index-1v89yst3.js → index-gytyqv1d.js} +2 -2
- package/dist/cli/{index-93ky909v.js → index-kv36s0x0.js} +3 -3
- package/dist/cli/{index-z7jgvm9b.js → index-nbgw5dxa.js} +1 -1
- package/dist/cli/{index-tqshdzaw.js → index-nc544sfw.js} +1 -1
- package/dist/cli/{index-js7th5my.js → index-nsnfvckc.js} +1 -1
- package/dist/cli/{index-4fmztnrq.js → index-p2dz85tv.js} +2 -2
- package/dist/cli/{index-y6dvs5tt.js → index-q3qtgtd0.js} +1 -1
- package/dist/cli/{index-99af5gvd.js → index-r3pth9a7.js} +230 -118
- package/dist/cli/{index-ab57h0yq.js → index-rtetrxxq.js} +1 -1
- package/dist/cli/{index-rjratees.js → index-s428dmy6.js} +2 -2
- package/dist/cli/{index-q344kj84.js → index-t26nfmcs.js} +1 -1
- package/dist/cli/{index-1djv5684.js → index-t31cc7kv.js} +6 -6
- package/dist/cli/{index-39k6y018.js → index-tcdcj1g2.js} +2 -2
- package/dist/cli/{index-en4fb04r.js → index-tyh0p5e8.js} +1 -1
- package/dist/cli/{index-xgmx9had.js → index-y46anh5e.js} +28 -27
- package/dist/cli/index-ymszzvnr.js +1725 -0
- package/dist/cli/{index-ngqxzkqm.js → index-z1tkxzq4.js} +2 -2
- package/dist/cli/{index-btx8j39p.js → index-zd15ya1w.js} +1 -1
- package/dist/cli/index.js +26 -25
- package/dist/cli/{knowledge-escalator-7n384ys1.js → knowledge-escalator-6473ey4c.js} +7 -7
- package/dist/cli/{knowledge-events-yaj0yjd6.js → knowledge-events-wwzpz8m4.js} +5 -5
- package/dist/cli/{knowledge-link-xc6yhxmz.js → knowledge-link-trfydkzm.js} +4 -4
- package/dist/cli/{knowledge-store-d99k0z2d.js → knowledge-store-99gd42jy.js} +5 -5
- package/dist/cli/{knowledge-validator-e5fqy9h4.js → knowledge-validator-v2eevvdg.js} +8 -8
- package/dist/cli/{pending-delegations-7dbc0j2a.js → pending-delegations-p6n83x67.js} +3 -3
- package/dist/cli/{pr-subscriptions-m943zn6g.js → pr-subscriptions-xbrfr51c.js} +3 -3
- package/dist/cli/{runner-x1hy047y.js → runner-9an52tmz.js} +5 -5
- package/dist/cli/{scan-cursor-9y9msabt.js → scan-cursor-39zxpw34.js} +6 -6
- package/dist/cli/{schema-xg5f11md.js → schema-xhfwr6b2.js} +1 -1
- package/dist/cli/scope-persistence-nsa6ra9s.js +61 -0
- package/dist/cli/{skill-generator-arvwfdfh.js → skill-generator-5px81476.js} +9 -9
- package/dist/cli/{telemetry-0yqed3jv.js → telemetry-7nt7zhyp.js} +1 -1
- package/dist/cli/{worktree-collision-ownership-hjrt5hx2.js → worktree-collision-ownership-9x4s4890.js} +3 -3
- package/dist/hooks/guardrails/destructive-command.d.ts +35 -0
- package/dist/hooks/guardrails/file-authority.d.ts +71 -26
- package/dist/hooks/guardrails/index.d.ts +2 -0
- package/dist/hooks/guardrails/tool-before.d.ts +5 -0
- package/dist/hooks/scope-guard.d.ts +3 -1
- package/dist/index.js +533 -522
- package/dist/plan/manager.d.ts +2 -0
- package/dist/scope/path-identity.d.ts +27 -0
- package/dist/scope/scope-binding.d.ts +90 -2
- package/dist/scope/scope-lease-renewal.d.ts +28 -0
- package/dist/scope/scope-persistence.d.ts +102 -19
- package/dist/scope/scope-resolution-diagnostic.d.ts +7 -0
- package/dist/services/run-memory.d.ts +53 -5
- package/dist/tools/declare-scope.d.ts +3 -1
- package/dist/tools/save-plan.d.ts +2 -0
- package/dist/tools/update-task-status.d.ts +2 -0
- package/dist/turbo/lean/lane-scope.d.ts +1 -1
- package/dist/utils/path-security.d.ts +25 -1
- package/package.json +1 -1
- package/dist/cli/index-2y4jy0de.js +0 -642
- package/dist/cli/scope-persistence-n1bryhzf.js +0 -38
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
// @bun
|
|
2
|
+
// src/scope/path-identity.ts
|
|
3
|
+
import * as path from "path";
|
|
4
|
+
var BIDI_CONTROL_CHARACTERS = /[\u061c\u200e\u200f\u202a-\u202e\u2066-\u2069]/u;
|
|
5
|
+
function isControlCharacter(character) {
|
|
6
|
+
const codePoint = character.codePointAt(0) ?? 0;
|
|
7
|
+
return codePoint <= 31 || codePoint >= 127 && codePoint <= 159;
|
|
8
|
+
}
|
|
9
|
+
function isBidiControlCharacter(character) {
|
|
10
|
+
return BIDI_CONTROL_CHARACTERS.test(character);
|
|
11
|
+
}
|
|
12
|
+
function getPathFlavor(pathLib = path) {
|
|
13
|
+
return pathLib.sep === "\\" ? "win32" : "posix";
|
|
14
|
+
}
|
|
15
|
+
function pathImplementation(flavor) {
|
|
16
|
+
return flavor === "win32" ? path.win32 : path.posix;
|
|
17
|
+
}
|
|
18
|
+
function normalizePathIdentity(value, flavor = getPathFlavor()) {
|
|
19
|
+
const pathImpl = pathImplementation(flavor);
|
|
20
|
+
const normalized = pathImpl.normalize(flavor === "win32" ? value.replace(/\//g, "\\") : value);
|
|
21
|
+
return flavor === "win32" ? normalized.toLowerCase() : normalized;
|
|
22
|
+
}
|
|
23
|
+
function pathIdentitiesEqual(a, b, flavor = getPathFlavor()) {
|
|
24
|
+
return normalizePathIdentity(a, flavor) === normalizePathIdentity(b, flavor);
|
|
25
|
+
}
|
|
26
|
+
function isPathIdentityWithin(target, container, flavor = getPathFlavor()) {
|
|
27
|
+
const pathImpl = pathImplementation(flavor);
|
|
28
|
+
const targetIdentity = normalizePathIdentity(target, flavor);
|
|
29
|
+
const containerIdentity = normalizePathIdentity(container, flavor);
|
|
30
|
+
if (targetIdentity === containerIdentity)
|
|
31
|
+
return true;
|
|
32
|
+
const relative = pathImpl.relative(containerIdentity, targetIdentity);
|
|
33
|
+
return relative.length > 0 && relative !== ".." && !relative.startsWith(`..${pathImpl.sep}`) && !pathImpl.isAbsolute(relative);
|
|
34
|
+
}
|
|
35
|
+
function isPathWithinDeclaredScope(filePath, scopeEntries, cwd = process.cwd(), flavor = getPathFlavor()) {
|
|
36
|
+
const pathImpl = pathImplementation(flavor);
|
|
37
|
+
const resolvedFile = pathImpl.resolve(cwd, filePath);
|
|
38
|
+
return scopeEntries.some((scope) => isPathIdentityWithin(resolvedFile, pathImpl.resolve(cwd, scope), flavor));
|
|
39
|
+
}
|
|
40
|
+
function isOnDifferentPathRoot(target, cwd, flavor = getPathFlavor()) {
|
|
41
|
+
const pathImpl = pathImplementation(flavor);
|
|
42
|
+
const targetRoot = pathImpl.parse(pathImpl.normalize(target)).root;
|
|
43
|
+
const cwdRoot = pathImpl.parse(pathImpl.normalize(cwd)).root;
|
|
44
|
+
return !pathIdentitiesEqual(targetRoot, cwdRoot, flavor);
|
|
45
|
+
}
|
|
46
|
+
function unsafePathTextReason(value) {
|
|
47
|
+
if ([...value].some(isControlCharacter)) {
|
|
48
|
+
return "control characters are not allowed in paths";
|
|
49
|
+
}
|
|
50
|
+
if ([...value].some(isBidiControlCharacter)) {
|
|
51
|
+
return "bidirectional control characters are not allowed in paths";
|
|
52
|
+
}
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
function sanitizeDiagnosticText(value, maxLength = 256) {
|
|
56
|
+
const rendered = String(value).split("").map((character) => isControlCharacter(character) || isBidiControlCharacter(character) ? "?" : character).join("").replace(/\s+/gu, " ").trim();
|
|
57
|
+
if (rendered.length <= maxLength)
|
|
58
|
+
return rendered;
|
|
59
|
+
return `${rendered.slice(0, Math.max(0, maxLength - 1))}\u2026`;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export { getPathFlavor, pathIdentitiesEqual, isPathIdentityWithin, isPathWithinDeclaredScope, isOnDifferentPathRoot, unsafePathTextReason, sanitizeDiagnosticText };
|
|
@@ -2,7 +2,16 @@
|
|
|
2
2
|
import {
|
|
3
3
|
init_schema,
|
|
4
4
|
stripKnownSwarmPrefix
|
|
5
|
-
} from "./index-
|
|
5
|
+
} from "./index-rtetrxxq.js";
|
|
6
|
+
import {
|
|
7
|
+
getPathFlavor,
|
|
8
|
+
isOnDifferentPathRoot,
|
|
9
|
+
isPathIdentityWithin,
|
|
10
|
+
isPathWithinDeclaredScope,
|
|
11
|
+
pathIdentitiesEqual,
|
|
12
|
+
sanitizeDiagnosticText,
|
|
13
|
+
unsafePathTextReason
|
|
14
|
+
} from "./index-556ja8an.js";
|
|
6
15
|
import {
|
|
7
16
|
init_constants
|
|
8
17
|
} from "./index-3jcyn8g6.js";
|
|
@@ -2095,11 +2104,41 @@ function getGlobMatcher(pattern, caseInsensitive = true) {
|
|
|
2095
2104
|
return () => false;
|
|
2096
2105
|
}
|
|
2097
2106
|
}
|
|
2107
|
+
var AUTHORITY_ROLE_CAPABILITIES = {
|
|
2108
|
+
architect: "direct-write",
|
|
2109
|
+
coder: "direct-write",
|
|
2110
|
+
reviewer: "direct-write",
|
|
2111
|
+
critic: "direct-write",
|
|
2112
|
+
critic_oversight: "dedicated-tool-only",
|
|
2113
|
+
critic_finding_validator: "dedicated-tool-only",
|
|
2114
|
+
explorer: "read-only",
|
|
2115
|
+
sme: "read-only",
|
|
2116
|
+
researcher: "read-only",
|
|
2117
|
+
test_engineer: "direct-write",
|
|
2118
|
+
docs: "direct-write",
|
|
2119
|
+
docs_design: "direct-write",
|
|
2120
|
+
designer: "direct-write",
|
|
2121
|
+
critic_sounding_board: "read-only",
|
|
2122
|
+
critic_drift_verifier: "read-only",
|
|
2123
|
+
critic_hallucination_verifier: "read-only",
|
|
2124
|
+
critic_architecture_supervisor: "read-only",
|
|
2125
|
+
curator_init: "dedicated-tool-only",
|
|
2126
|
+
curator_phase: "dedicated-tool-only",
|
|
2127
|
+
curator_postmortem: "dedicated-tool-only",
|
|
2128
|
+
curator_consolidation: "dedicated-tool-only",
|
|
2129
|
+
council_generalist: "read-only",
|
|
2130
|
+
council_skeptic: "read-only",
|
|
2131
|
+
council_domain_expert: "read-only",
|
|
2132
|
+
skill_improver: "dedicated-tool-only",
|
|
2133
|
+
spec_writer: "dedicated-tool-only"
|
|
2134
|
+
};
|
|
2135
|
+
var NON_WRITING_SWARM_RULES = Object.fromEntries(Object.entries(AUTHORITY_ROLE_CAPABILITIES).filter(([, capability]) => capability === "read-only" || capability === "dedicated-tool-only").map(([agent]) => [agent, { readOnly: true }]));
|
|
2098
2136
|
var DEFAULT_AGENT_AUTHORITY_RULES = {
|
|
2099
2137
|
build: {},
|
|
2100
2138
|
plan: {},
|
|
2101
2139
|
general: {},
|
|
2102
2140
|
explore: {},
|
|
2141
|
+
...NON_WRITING_SWARM_RULES,
|
|
2103
2142
|
architect: {
|
|
2104
2143
|
blockedExact: [".swarm/plan.md", ".swarm/plan.json"],
|
|
2105
2144
|
blockedZones: ["generated", "config"],
|
|
@@ -2189,6 +2228,22 @@ var DEFAULT_AGENT_AUTHORITY_RULES = {
|
|
|
2189
2228
|
blockedZones: ["generated"]
|
|
2190
2229
|
}
|
|
2191
2230
|
};
|
|
2231
|
+
function matchesAuthorityDenyPrefix(candidate, prefix, cwd, flavor = getPathFlavor()) {
|
|
2232
|
+
const pathImpl = flavor === "win32" ? path.win32 : path.posix;
|
|
2233
|
+
const normalizeInput = (value) => flavor === "win32" ? value.replace(/\//g, "\\") : value;
|
|
2234
|
+
const normalizedPrefixInput = normalizeInput(prefix);
|
|
2235
|
+
const requiresComponentBoundary = normalizedPrefixInput.endsWith(pathImpl.sep);
|
|
2236
|
+
const root = pathImpl.resolve(normalizeInput(cwd));
|
|
2237
|
+
const target = pathImpl.resolve(root, normalizeInput(candidate));
|
|
2238
|
+
const denied = pathImpl.resolve(root, normalizedPrefixInput);
|
|
2239
|
+
if (!isPathIdentityWithin(target, root, flavor) || !isPathIdentityWithin(denied, root, flavor)) {
|
|
2240
|
+
return false;
|
|
2241
|
+
}
|
|
2242
|
+
const identity = (value) => flavor === "win32" ? value.toLowerCase() : value;
|
|
2243
|
+
const targetIdentity = identity(target);
|
|
2244
|
+
const deniedIdentity = identity(denied);
|
|
2245
|
+
return requiresComponentBoundary ? targetIdentity === deniedIdentity || targetIdentity.startsWith(`${deniedIdentity}${pathImpl.sep}`) : targetIdentity.startsWith(deniedIdentity);
|
|
2246
|
+
}
|
|
2192
2247
|
function buildEffectiveRules(authorityConfig) {
|
|
2193
2248
|
if (authorityConfig?.enabled === false || !authorityConfig?.rules) {
|
|
2194
2249
|
return { ...DEFAULT_AGENT_AUTHORITY_RULES };
|
|
@@ -2220,21 +2275,37 @@ function buildEffectiveRules(authorityConfig) {
|
|
|
2220
2275
|
return merged;
|
|
2221
2276
|
}
|
|
2222
2277
|
function isOnDifferentFilesystemRoot(targetAbsolute, cwdAbsolute, pathLib = path) {
|
|
2223
|
-
|
|
2278
|
+
const flavor = pathLib === path.win32 || pathLib.parse("C:\\probe").root !== "" ? "win32" : "posix";
|
|
2279
|
+
return isOnDifferentPathRoot(targetAbsolute, cwdAbsolute, flavor);
|
|
2224
2280
|
}
|
|
2225
|
-
|
|
2226
|
-
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
|
|
2231
|
-
|
|
2232
|
-
|
|
2233
|
-
|
|
2234
|
-
|
|
2235
|
-
|
|
2236
|
-
|
|
2237
|
-
|
|
2281
|
+
var BUILT_IN_VERIFIER_CONFIG_GLOBS = [
|
|
2282
|
+
"**/oxlintrc*",
|
|
2283
|
+
"**/.oxlintrc*",
|
|
2284
|
+
"**/.eslintrc*",
|
|
2285
|
+
"**/eslint.config.*",
|
|
2286
|
+
"**/.prettierrc*",
|
|
2287
|
+
"**/prettier.config.*",
|
|
2288
|
+
"**/biome.json",
|
|
2289
|
+
"**/biome.jsonc",
|
|
2290
|
+
"**/tsconfig*.json",
|
|
2291
|
+
"**/.secretscanignore",
|
|
2292
|
+
"**/.golangci*"
|
|
2293
|
+
];
|
|
2294
|
+
function denyAuthority(agent, normalizedPath, input) {
|
|
2295
|
+
const safeAgent = sanitizeDiagnosticText(agent, 96);
|
|
2296
|
+
const safePath = sanitizeDiagnosticText(normalizedPath, 320);
|
|
2297
|
+
const safeDetail = sanitizeDiagnosticText(input.detail, 640);
|
|
2298
|
+
return {
|
|
2299
|
+
allowed: false,
|
|
2300
|
+
code: input.code,
|
|
2301
|
+
layer: input.layer,
|
|
2302
|
+
rule: sanitizeDiagnosticText(input.rule, 128),
|
|
2303
|
+
path: safePath,
|
|
2304
|
+
agent: safeAgent,
|
|
2305
|
+
reason: sanitizeDiagnosticText(`${input.code}: Path blocked: ${safeDetail} [agent=${safeAgent}; path=${safePath}]`, 1024),
|
|
2306
|
+
recovery: input.recovery,
|
|
2307
|
+
zone: input.zone
|
|
2308
|
+
};
|
|
2238
2309
|
}
|
|
2239
2310
|
var MAX_ALLOWED_HINT_ENTRIES = 20;
|
|
2240
2311
|
function formatAllowedHints(rules) {
|
|
@@ -2258,10 +2329,19 @@ function formatAllowedHints(rules) {
|
|
|
2258
2329
|
parts.push("Block rules (blocked zones/prefixes/globs/exact) and universal deny paths still apply.");
|
|
2259
2330
|
return parts.join(" ");
|
|
2260
2331
|
}
|
|
2261
|
-
function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules, options) {
|
|
2332
|
+
function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules, options = {}) {
|
|
2262
2333
|
const normalizedAgent = agentName.toLowerCase();
|
|
2263
2334
|
const strippedAgent = stripKnownSwarmPrefix(agentName).toLowerCase();
|
|
2264
2335
|
const dir = cwd || process.cwd();
|
|
2336
|
+
const unsafePathReason = unsafePathTextReason(filePath);
|
|
2337
|
+
if (unsafePathReason) {
|
|
2338
|
+
return denyAuthority(agentName, filePath, {
|
|
2339
|
+
code: "AUTHORITY_INVALID_PATH",
|
|
2340
|
+
layer: "path-validation",
|
|
2341
|
+
rule: "safe-path-text",
|
|
2342
|
+
detail: unsafePathReason
|
|
2343
|
+
});
|
|
2344
|
+
}
|
|
2265
2345
|
let normalizedPath;
|
|
2266
2346
|
let resolvedTarget;
|
|
2267
2347
|
try {
|
|
@@ -2273,71 +2353,160 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules, o
|
|
|
2273
2353
|
normalizedPath = path.relative(dir, resolvedTarget).replace(/\\/g, "/");
|
|
2274
2354
|
}
|
|
2275
2355
|
if (isOnDifferentFilesystemRoot(resolvedTarget, dir)) {
|
|
2276
|
-
return {
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2356
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2357
|
+
code: "AUTHORITY_ROOT_ESCAPE",
|
|
2358
|
+
layer: "containment",
|
|
2359
|
+
rule: "same-filesystem-root",
|
|
2360
|
+
detail: "target is on a different drive or filesystem root"
|
|
2361
|
+
});
|
|
2280
2362
|
}
|
|
2281
2363
|
if (normalizedPath === ".." || normalizedPath.startsWith("../")) {
|
|
2282
|
-
return {
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2364
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2365
|
+
code: "AUTHORITY_ROOT_ESCAPE",
|
|
2366
|
+
layer: "containment",
|
|
2367
|
+
rule: "workspace-containment",
|
|
2368
|
+
detail: "target resolves outside the working directory"
|
|
2369
|
+
});
|
|
2286
2370
|
}
|
|
2287
2371
|
const rules = effectiveRules[normalizedAgent] ?? effectiveRules[strippedAgent];
|
|
2288
2372
|
if (!rules) {
|
|
2289
|
-
return
|
|
2373
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2374
|
+
code: "AUTHORITY_UNKNOWN_AGENT",
|
|
2375
|
+
layer: "role-capability",
|
|
2376
|
+
rule: "known-agent-role",
|
|
2377
|
+
detail: "Unknown agent: no authority classification exists"
|
|
2378
|
+
});
|
|
2379
|
+
}
|
|
2380
|
+
const flavor = getPathFlavor();
|
|
2381
|
+
const matchesPathPrefix = (candidate, prefix) => isPathIdentityWithin(path.resolve(dir, candidate), path.resolve(dir, prefix), flavor);
|
|
2382
|
+
const matchesGlob = (glob) => getGlobMatcher(glob)(normalizedPath);
|
|
2383
|
+
for (const prefix of options.universalDenyPrefixes ?? []) {
|
|
2384
|
+
if (matchesAuthorityDenyPrefix(normalizedPath, prefix, dir, flavor)) {
|
|
2385
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2386
|
+
code: "AUTHORITY_UNIVERSAL_DENY",
|
|
2387
|
+
layer: "universal-deny",
|
|
2388
|
+
rule: `universal:${prefix}`,
|
|
2389
|
+
detail: `target is under universal deny prefix ${prefix}`
|
|
2390
|
+
});
|
|
2391
|
+
}
|
|
2392
|
+
}
|
|
2393
|
+
const isCoderAgent = normalizedAgent === "coder" || strippedAgent === "coder";
|
|
2394
|
+
if (isCoderAgent && matchesPathPrefix(normalizedPath, ".swarm")) {
|
|
2395
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2396
|
+
code: "AUTHORITY_PROTECTED_PATH",
|
|
2397
|
+
layer: "protected-path",
|
|
2398
|
+
rule: "coder-swarm-state-protection",
|
|
2399
|
+
detail: "coder writes to .swarm are always protected"
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
const isSwarmRole = Object.hasOwn(AUTHORITY_ROLE_CAPABILITIES, strippedAgent);
|
|
2403
|
+
if (isSwarmRole) {
|
|
2404
|
+
const matchedVerifier = [
|
|
2405
|
+
...BUILT_IN_VERIFIER_CONFIG_GLOBS,
|
|
2406
|
+
...options.verifierConfigPaths ?? []
|
|
2407
|
+
].find(matchesGlob);
|
|
2408
|
+
if (matchedVerifier) {
|
|
2409
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2410
|
+
code: "AUTHORITY_VERIFIER_CONFIG",
|
|
2411
|
+
layer: "protected-path",
|
|
2412
|
+
rule: `verifier-config:${matchedVerifier}`,
|
|
2413
|
+
detail: `Path blocked (glob ${matchedVerifier}): swarm roles cannot write verifier-owned configuration in the config zone`
|
|
2414
|
+
});
|
|
2415
|
+
}
|
|
2416
|
+
}
|
|
2417
|
+
const canonicalCapability = AUTHORITY_ROLE_CAPABILITIES[strippedAgent];
|
|
2418
|
+
if (canonicalCapability === "read-only" || canonicalCapability === "dedicated-tool-only") {
|
|
2419
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2420
|
+
code: "AUTHORITY_ROLE_READ_ONLY",
|
|
2421
|
+
layer: "role-capability",
|
|
2422
|
+
rule: `role-capability:${canonicalCapability}`,
|
|
2423
|
+
detail: canonicalCapability === "dedicated-tool-only" ? "role may mutate state only through dedicated tools" : "agent role is read-only"
|
|
2424
|
+
});
|
|
2290
2425
|
}
|
|
2291
2426
|
if (rules.readOnly) {
|
|
2427
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2428
|
+
code: "AUTHORITY_ROLE_READ_ONLY",
|
|
2429
|
+
layer: "role-capability",
|
|
2430
|
+
rule: "role-policy:read-only",
|
|
2431
|
+
detail: "agent role is read-only"
|
|
2432
|
+
});
|
|
2433
|
+
}
|
|
2434
|
+
const pathIsInDeclaredScope = isCoderAgent && options.declaredScope != null && options.declaredScope.length > 0 && isPathWithinDeclaredScope(normalizedPath, options.declaredScope, dir);
|
|
2435
|
+
if (pathIsInDeclaredScope) {
|
|
2292
2436
|
return {
|
|
2293
|
-
allowed:
|
|
2294
|
-
|
|
2437
|
+
allowed: true,
|
|
2438
|
+
layer: "declared-scope",
|
|
2439
|
+
path: sanitizeDiagnosticText(normalizedPath, 320),
|
|
2440
|
+
agent: sanitizeDiagnosticText(agentName, 96)
|
|
2441
|
+
};
|
|
2442
|
+
}
|
|
2443
|
+
if (options.authorityEnabled === false) {
|
|
2444
|
+
return {
|
|
2445
|
+
allowed: true,
|
|
2446
|
+
layer: "role-policy",
|
|
2447
|
+
path: sanitizeDiagnosticText(normalizedPath, 320),
|
|
2448
|
+
agent: sanitizeDiagnosticText(agentName, 96)
|
|
2295
2449
|
};
|
|
2296
2450
|
}
|
|
2297
2451
|
if (rules.blockedExact) {
|
|
2298
2452
|
for (const blocked of rules.blockedExact) {
|
|
2299
|
-
if (normalizedPath
|
|
2300
|
-
return {
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2453
|
+
if (pathIdentitiesEqual(normalizedPath, blocked, flavor)) {
|
|
2454
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2455
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2456
|
+
layer: "role-policy",
|
|
2457
|
+
rule: `blockedExact:${blocked}`,
|
|
2458
|
+
detail: `path is blocked by exact role policy ${blocked}`,
|
|
2459
|
+
recovery: isCoderAgent ? "declare_scope" : undefined
|
|
2460
|
+
});
|
|
2304
2461
|
}
|
|
2305
2462
|
}
|
|
2306
2463
|
}
|
|
2307
2464
|
if (rules.blockedGlobs && rules.blockedGlobs.length > 0) {
|
|
2308
2465
|
for (const glob of rules.blockedGlobs) {
|
|
2309
|
-
|
|
2310
|
-
|
|
2311
|
-
|
|
2312
|
-
|
|
2313
|
-
|
|
2314
|
-
|
|
2466
|
+
if (matchesGlob(glob)) {
|
|
2467
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2468
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2469
|
+
layer: "role-policy",
|
|
2470
|
+
rule: `blockedGlob:${glob}`,
|
|
2471
|
+
detail: `Path blocked (glob ${glob}) by role policy`,
|
|
2472
|
+
recovery: isCoderAgent ? "declare_scope" : undefined
|
|
2473
|
+
});
|
|
2315
2474
|
}
|
|
2316
2475
|
}
|
|
2317
2476
|
}
|
|
2318
2477
|
if (rules.allowedExact && rules.allowedExact.length > 0) {
|
|
2319
|
-
const isExplicitlyAllowed = rules.allowedExact.some((allowed) => normalizedPath
|
|
2478
|
+
const isExplicitlyAllowed = rules.allowedExact.some((allowed) => pathIdentitiesEqual(normalizedPath, allowed, flavor));
|
|
2320
2479
|
if (isExplicitlyAllowed) {
|
|
2321
|
-
return {
|
|
2480
|
+
return {
|
|
2481
|
+
allowed: true,
|
|
2482
|
+
layer: "role-policy",
|
|
2483
|
+
path: normalizedPath,
|
|
2484
|
+
agent: agentName
|
|
2485
|
+
};
|
|
2322
2486
|
}
|
|
2323
2487
|
}
|
|
2324
2488
|
if (rules.blockedZones && rules.blockedZones.length > 0) {
|
|
2325
2489
|
const { zone } = classifyFile(normalizedPath);
|
|
2326
2490
|
if (rules.blockedZones.includes(zone)) {
|
|
2327
|
-
return {
|
|
2328
|
-
|
|
2329
|
-
|
|
2491
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2492
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2493
|
+
layer: "role-policy",
|
|
2494
|
+
rule: `blockedZone:${zone}`,
|
|
2495
|
+
detail: `path is in ${zone} zone`,
|
|
2496
|
+
recovery: isCoderAgent ? "declare_scope" : undefined,
|
|
2330
2497
|
zone
|
|
2331
|
-
};
|
|
2498
|
+
});
|
|
2332
2499
|
}
|
|
2333
2500
|
}
|
|
2334
2501
|
if (rules.allowedGlobs && rules.allowedGlobs.length > 0) {
|
|
2335
|
-
const isGlobAllowed = rules.allowedGlobs.some(
|
|
2336
|
-
const matcher = getGlobMatcher(glob);
|
|
2337
|
-
return matcher(normalizedPath);
|
|
2338
|
-
});
|
|
2502
|
+
const isGlobAllowed = rules.allowedGlobs.some(matchesGlob);
|
|
2339
2503
|
if (isGlobAllowed) {
|
|
2340
|
-
return {
|
|
2504
|
+
return {
|
|
2505
|
+
allowed: true,
|
|
2506
|
+
layer: "role-policy",
|
|
2507
|
+
path: normalizedPath,
|
|
2508
|
+
agent: agentName
|
|
2509
|
+
};
|
|
2341
2510
|
}
|
|
2342
2511
|
}
|
|
2343
2512
|
if (rules.allowedCaseSensitiveGlobs && rules.allowedCaseSensitiveGlobs.length > 0) {
|
|
@@ -2346,59 +2515,67 @@ function checkFileAuthorityWithRules(agentName, filePath, cwd, effectiveRules, o
|
|
|
2346
2515
|
return matcher(normalizedPath);
|
|
2347
2516
|
});
|
|
2348
2517
|
if (isCaseSensitiveGlobAllowed) {
|
|
2349
|
-
return {
|
|
2518
|
+
return {
|
|
2519
|
+
allowed: true,
|
|
2520
|
+
layer: "role-policy",
|
|
2521
|
+
path: normalizedPath,
|
|
2522
|
+
agent: agentName
|
|
2523
|
+
};
|
|
2350
2524
|
}
|
|
2351
2525
|
}
|
|
2352
2526
|
if (rules.blockedPrefix && rules.blockedPrefix.length > 0) {
|
|
2353
2527
|
for (const prefix of rules.blockedPrefix) {
|
|
2354
|
-
if (normalizedPath
|
|
2355
|
-
return {
|
|
2356
|
-
|
|
2357
|
-
|
|
2358
|
-
|
|
2359
|
-
|
|
2360
|
-
|
|
2361
|
-
|
|
2362
|
-
|
|
2363
|
-
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
2372
|
-
|
|
2373
|
-
|
|
2374
|
-
|
|
2375
|
-
allowed: false,
|
|
2376
|
-
reason: `Path ${normalizedPath} not in allowed list for ${normalizedAgent}. ${formatAllowedHints(rules)}`
|
|
2377
|
-
};
|
|
2528
|
+
if (matchesPathPrefix(normalizedPath, prefix)) {
|
|
2529
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2530
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2531
|
+
layer: "role-policy",
|
|
2532
|
+
rule: `blockedPrefix:${prefix}`,
|
|
2533
|
+
detail: `path is under blocked role prefix ${prefix}`,
|
|
2534
|
+
recovery: isCoderAgent ? "declare_scope" : undefined
|
|
2535
|
+
});
|
|
2536
|
+
}
|
|
2537
|
+
}
|
|
2538
|
+
}
|
|
2539
|
+
if (rules.allowedPrefix != null && rules.allowedPrefix.length > 0) {
|
|
2540
|
+
const isAllowed = rules.allowedPrefix.some((prefix) => matchesPathPrefix(normalizedPath, prefix));
|
|
2541
|
+
if (!isAllowed) {
|
|
2542
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2543
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2544
|
+
layer: "role-policy",
|
|
2545
|
+
rule: "allowedPrefix",
|
|
2546
|
+
detail: `path is not in allowed list for this role. ${formatAllowedHints(rules)}`,
|
|
2547
|
+
recovery: isCoderAgent ? "declare_scope" : undefined
|
|
2548
|
+
});
|
|
2378
2549
|
}
|
|
2550
|
+
} else if (rules.allowedPrefix != null && rules.allowedPrefix.length === 0) {
|
|
2551
|
+
return denyAuthority(agentName, normalizedPath, {
|
|
2552
|
+
code: "AUTHORITY_POLICY_DENY",
|
|
2553
|
+
layer: "role-policy",
|
|
2554
|
+
rule: "allowedPrefix:empty",
|
|
2555
|
+
detail: `path is not in allowed list because the role allowlist is empty. ${formatAllowedHints(rules)}`,
|
|
2556
|
+
recovery: isCoderAgent ? "declare_scope" : undefined
|
|
2557
|
+
});
|
|
2379
2558
|
}
|
|
2380
|
-
return {
|
|
2559
|
+
return {
|
|
2560
|
+
allowed: true,
|
|
2561
|
+
layer: "role-policy",
|
|
2562
|
+
path: normalizedPath,
|
|
2563
|
+
agent: agentName
|
|
2564
|
+
};
|
|
2381
2565
|
}
|
|
2382
|
-
function checkFileAuthority(agentName, filePath, cwd, authorityConfig, options) {
|
|
2383
|
-
return checkFileAuthorityWithRules(agentName, filePath, cwd, buildEffectiveRules(authorityConfig),
|
|
2566
|
+
function checkFileAuthority(agentName, filePath, cwd, authorityConfig, options = {}) {
|
|
2567
|
+
return checkFileAuthorityWithRules(agentName, filePath, cwd, buildEffectiveRules(authorityConfig), {
|
|
2568
|
+
...options,
|
|
2569
|
+
authorityEnabled: authorityConfig?.enabled ?? options.authorityEnabled,
|
|
2570
|
+
universalDenyPrefixes: authorityConfig?.universal_deny_prefixes ?? options.universalDenyPrefixes,
|
|
2571
|
+
verifierConfigPaths: authorityConfig?.verifier_config_paths ?? options.verifierConfigPaths
|
|
2572
|
+
});
|
|
2384
2573
|
}
|
|
2385
2574
|
|
|
2386
2575
|
// src/hooks/guardrails/helpers.ts
|
|
2387
2576
|
init_constants();
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
const dir = cwd ?? process.cwd();
|
|
2391
|
-
const caseInsensitive = process.platform === "win32";
|
|
2392
|
-
const resolvedFileRaw = path2.resolve(dir, filePath);
|
|
2393
|
-
const resolvedFile = caseInsensitive ? resolvedFileRaw.toLowerCase() : resolvedFileRaw;
|
|
2394
|
-
return scopeEntries.some((scope) => {
|
|
2395
|
-
const resolvedScopeRaw = path2.resolve(dir, scope);
|
|
2396
|
-
const resolvedScope = caseInsensitive ? resolvedScopeRaw.toLowerCase() : resolvedScopeRaw;
|
|
2397
|
-
if (resolvedFile === resolvedScope)
|
|
2398
|
-
return true;
|
|
2399
|
-
const rel = path2.relative(resolvedScope, resolvedFile);
|
|
2400
|
-
return rel.length > 0 && !rel.startsWith("..") && !path2.isAbsolute(rel);
|
|
2401
|
-
});
|
|
2577
|
+
function isInDeclaredScope(filePath, scopeEntries, cwd) {
|
|
2578
|
+
return isPathWithinDeclaredScope(filePath, scopeEntries, cwd);
|
|
2402
2579
|
}
|
|
2403
2580
|
function redactShellCommand(cmd) {
|
|
2404
2581
|
if (typeof cmd !== "string")
|
|
@@ -2414,7 +2591,7 @@ function redactShellCommand(cmd) {
|
|
|
2414
2591
|
|
|
2415
2592
|
// src/hooks/guardrails/audit-log.ts
|
|
2416
2593
|
init_logger();
|
|
2417
|
-
import * as
|
|
2594
|
+
import * as path2 from "path";
|
|
2418
2595
|
function redactPath(filePath) {
|
|
2419
2596
|
if (typeof filePath !== "string" || filePath.length === 0)
|
|
2420
2597
|
return filePath;
|
|
@@ -2426,7 +2603,7 @@ function redactPath(filePath) {
|
|
|
2426
2603
|
if (rawMacHomeMatch) {
|
|
2427
2604
|
return `~${rawMacHomeMatch[2]}`;
|
|
2428
2605
|
}
|
|
2429
|
-
const normalized =
|
|
2606
|
+
const normalized = path2.normalize(filePath);
|
|
2430
2607
|
const windowsHomeMatch = normalized.match(/^([A-Za-z]:\\Users\\[^\\]+)(\\.*)$/);
|
|
2431
2608
|
if (windowsHomeMatch) {
|
|
2432
2609
|
return `~\\${windowsHomeMatch[2]}`;
|
|
@@ -2438,4 +2615,4 @@ function redactPath(filePath) {
|
|
|
2438
2615
|
return normalized;
|
|
2439
2616
|
}
|
|
2440
2617
|
|
|
2441
|
-
export { stableCanonicalStringify, classifyFile, checkFileAuthority,
|
|
2618
|
+
export { stableCanonicalStringify, classifyFile, checkFileAuthority, isInDeclaredScope, redactShellCommand, redactPath };
|
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
} from "./index-rtry5xyf.js";
|
|
5
5
|
import {
|
|
6
6
|
readKnowledgeEvents
|
|
7
|
-
} from "./index-
|
|
7
|
+
} from "./index-kv36s0x0.js";
|
|
8
8
|
import {
|
|
9
9
|
isLinked,
|
|
10
10
|
readLinkPointer,
|
|
11
11
|
resolveKnowledgeStoreDir,
|
|
12
12
|
resolveLinkDir
|
|
13
|
-
} from "./index-
|
|
13
|
+
} from "./index-tyh0p5e8.js";
|
|
14
14
|
|
|
15
15
|
// src/knowledge/curation-policy.ts
|
|
16
16
|
import { appendFile, mkdir as mkdir2, readFile as readFile2 } from "fs/promises";
|