@h-rig/guard-plugin 0.0.6-alpha.156 → 0.0.6-alpha.158
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/src/agent-binary-build.d.ts +9 -0
- package/dist/src/agent-binary-build.js +88 -0
- package/dist/src/agent-shell.d.ts +24 -0
- package/dist/src/agent-shell.js +939 -0
- package/dist/src/controlled-bash.d.ts +5 -0
- package/dist/src/controlled-bash.js +784 -0
- package/dist/src/embedded-native-assets.d.ts +7 -0
- package/dist/src/embedded-native-assets.js +6 -0
- package/dist/src/guard.d.ts +17 -0
- package/dist/src/guard.js +684 -0
- package/dist/src/hook-ids.d.ts +6 -0
- package/dist/src/hook-ids.js +16 -0
- package/dist/src/hooks/audit-trail.d.ts +1 -1
- package/dist/src/hooks/audit-trail.js +8 -4
- package/dist/src/hooks/import-guard.d.ts +1 -1
- package/dist/src/hooks/import-guard.js +651 -1
- package/dist/src/hooks/post-edit-lint.d.ts +1 -1
- package/dist/src/hooks/post-edit-lint.js +4 -0
- package/dist/src/hooks/safety-guard.d.ts +1 -1
- package/dist/src/hooks/safety-guard.js +651 -1
- package/dist/src/hooks/scope-guard.d.ts +1 -1
- package/dist/src/hooks/scope-guard.js +651 -1
- package/dist/src/hooks/test-integrity-guard.d.ts +1 -1
- package/dist/src/hooks/test-integrity-guard.js +651 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1175 -51
- package/dist/src/plugin.js +1131 -51
- package/dist/src/scope.d.ts +3 -0
- package/dist/src/scope.js +58 -0
- package/package.json +20 -5
package/dist/src/plugin.js
CHANGED
|
@@ -1,11 +1,679 @@
|
|
|
1
1
|
// @bun
|
|
2
|
-
|
|
3
|
-
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __returnValue = (v) => v;
|
|
4
|
+
function __exportSetter(name, newValue) {
|
|
5
|
+
this[name] = __returnValue.bind(null, newValue);
|
|
6
|
+
}
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, {
|
|
10
|
+
get: all[name],
|
|
11
|
+
enumerable: true,
|
|
12
|
+
configurable: true,
|
|
13
|
+
set: __exportSetter.bind(all, name)
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
var __esm = (fn, res) => () => (fn && (res = fn(fn = 0)), res);
|
|
17
|
+
var __require = import.meta.require;
|
|
18
|
+
|
|
19
|
+
// packages/guard-plugin/src/hook-ids.ts
|
|
20
|
+
var SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard", SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard", IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard", TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard", AUDIT_TRAIL_HOOK_ID = "@rig/guard-plugin:audit-trail", POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
|
|
21
|
+
|
|
22
|
+
// packages/guard-plugin/src/scope.ts
|
|
23
|
+
import { getScopeRules } from "@rig/core/scope-rules";
|
|
24
|
+
function unique(values) {
|
|
25
|
+
return [...new Set(values)];
|
|
26
|
+
}
|
|
27
|
+
function normalizeRelativeScopePath(inputPath) {
|
|
28
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
29
|
+
const rules = getScopeRules();
|
|
30
|
+
if (rules?.stripPrefixes) {
|
|
31
|
+
for (const prefix of rules.stripPrefixes) {
|
|
32
|
+
if (normalized.startsWith(prefix)) {
|
|
33
|
+
normalized = normalized.slice(prefix.length);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return normalized;
|
|
38
|
+
}
|
|
39
|
+
function normalizePathToScope(projectRoot, monorepoRoot, inputPath) {
|
|
40
|
+
let normalized = inputPath.replace(/^\.\//, "");
|
|
41
|
+
if (normalized.startsWith(projectRoot + "/")) {
|
|
42
|
+
normalized = normalized.slice(projectRoot.length + 1);
|
|
43
|
+
}
|
|
44
|
+
if (normalized.startsWith(monorepoRoot + "/")) {
|
|
45
|
+
normalized = normalized.slice(monorepoRoot.length + 1);
|
|
46
|
+
}
|
|
47
|
+
return normalizeRelativeScopePath(normalized);
|
|
48
|
+
}
|
|
49
|
+
function scopeGlobToRegex(glob) {
|
|
50
|
+
const cached = scopeRegexCache.get(glob);
|
|
51
|
+
if (cached) {
|
|
52
|
+
return cached;
|
|
53
|
+
}
|
|
54
|
+
const escaped = glob.replace(/[.+^${}()|[\]\\]/g, "\\$&").replace(/\*\*/g, "__GLOBSTAR__").replace(/\*/g, "[^/]*").replace(/__GLOBSTAR__/g, ".*");
|
|
55
|
+
const compiled = new RegExp(`^${escaped}$`);
|
|
56
|
+
scopeRegexCache.set(glob, compiled);
|
|
57
|
+
return compiled;
|
|
58
|
+
}
|
|
59
|
+
function scopeMatches(path, scopes) {
|
|
60
|
+
const pathVariants = unique([path, normalizeRelativeScopePath(path)]);
|
|
61
|
+
for (const scope of scopes) {
|
|
62
|
+
const scopeVariants = unique([scope, normalizeRelativeScopePath(scope)]);
|
|
63
|
+
for (const candidatePath of pathVariants) {
|
|
64
|
+
for (const candidateScope of scopeVariants) {
|
|
65
|
+
if (candidatePath === candidateScope || scopeGlobToRegex(candidateScope).test(candidatePath)) {
|
|
66
|
+
return true;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
return false;
|
|
72
|
+
}
|
|
73
|
+
var scopeRegexCache;
|
|
74
|
+
var init_scope = __esm(() => {
|
|
75
|
+
scopeRegexCache = new Map;
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// packages/guard-plugin/src/guard.ts
|
|
79
|
+
import { optimizeNextInvocation } from "bun:jsc";
|
|
80
|
+
import { existsSync, readFileSync, statSync } from "fs";
|
|
81
|
+
import { resolve } from "path";
|
|
82
|
+
import {
|
|
83
|
+
POLICY_VERSION
|
|
84
|
+
} from "@rig/contracts";
|
|
85
|
+
function defaultPolicy() {
|
|
86
|
+
return {
|
|
87
|
+
version: POLICY_VERSION,
|
|
88
|
+
mode: "enforce",
|
|
89
|
+
scope: { ...DEFAULT_SCOPE },
|
|
90
|
+
rules: [],
|
|
91
|
+
sandbox: { ...DEFAULT_SANDBOX },
|
|
92
|
+
isolation: { ...DEFAULT_ISOLATION },
|
|
93
|
+
completion: { ...DEFAULT_COMPLETION },
|
|
94
|
+
runtime_image: {
|
|
95
|
+
deps: { ...DEFAULT_RUNTIME_IMAGE.deps },
|
|
96
|
+
plugins_require_binaries: DEFAULT_RUNTIME_IMAGE.plugins_require_binaries
|
|
97
|
+
},
|
|
98
|
+
runtime_snapshot: { ...DEFAULT_RUNTIME_SNAPSHOT }
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
function seedPolicyFromContent(rawJson) {
|
|
102
|
+
try {
|
|
103
|
+
const parsed = JSON.parse(rawJson);
|
|
104
|
+
seededPolicyConfig = mergeWithDefaults(parsed);
|
|
105
|
+
} catch {}
|
|
106
|
+
}
|
|
107
|
+
function loadPolicy(projectRoot) {
|
|
108
|
+
if (seededPolicyConfig) {
|
|
109
|
+
return seededPolicyConfig;
|
|
110
|
+
}
|
|
111
|
+
const configPath = resolve(projectRoot, "rig/policy/policy.json");
|
|
112
|
+
if (!existsSync(configPath)) {
|
|
113
|
+
return defaultPolicy();
|
|
114
|
+
}
|
|
115
|
+
let mtimeMs;
|
|
116
|
+
try {
|
|
117
|
+
mtimeMs = statSync(configPath).mtimeMs;
|
|
118
|
+
} catch {
|
|
119
|
+
return defaultPolicy();
|
|
120
|
+
}
|
|
121
|
+
if (policyCache && policyCachePath === configPath && policyCache.mtimeMs === mtimeMs) {
|
|
122
|
+
return policyCache.config;
|
|
123
|
+
}
|
|
124
|
+
let parsed;
|
|
125
|
+
try {
|
|
126
|
+
parsed = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
127
|
+
} catch {
|
|
128
|
+
return defaultPolicy();
|
|
129
|
+
}
|
|
130
|
+
const config = mergeWithDefaults(parsed);
|
|
131
|
+
policyCache = { mtimeMs, config };
|
|
132
|
+
policyCachePath = configPath;
|
|
133
|
+
return config;
|
|
134
|
+
}
|
|
135
|
+
function mergeWithDefaults(parsed) {
|
|
136
|
+
const base = defaultPolicy();
|
|
137
|
+
if (typeof parsed.mode === "string" && isValidMode(parsed.mode)) {
|
|
138
|
+
base.mode = parsed.mode;
|
|
139
|
+
}
|
|
140
|
+
if (parsed.scope && typeof parsed.scope === "object" && !Array.isArray(parsed.scope)) {
|
|
141
|
+
const s = parsed.scope;
|
|
142
|
+
if (typeof s.fail_closed === "boolean")
|
|
143
|
+
base.scope.fail_closed = s.fail_closed;
|
|
144
|
+
if (typeof s.harness_paths_exempt === "boolean")
|
|
145
|
+
base.scope.harness_paths_exempt = s.harness_paths_exempt;
|
|
146
|
+
if (typeof s.runtime_paths_exempt === "boolean")
|
|
147
|
+
base.scope.runtime_paths_exempt = s.runtime_paths_exempt;
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(parsed.rules)) {
|
|
150
|
+
base.rules = precompilePolicyRuleRegexes(parsed.rules.filter(isValidRule));
|
|
151
|
+
}
|
|
152
|
+
if (Array.isArray(parsed.deny) && base.rules.length === 0) {
|
|
153
|
+
base.rules = precompilePolicyRuleRegexes(migrateLegacyDeny(parsed.deny));
|
|
154
|
+
}
|
|
155
|
+
if (parsed.sandbox && typeof parsed.sandbox === "object" && !Array.isArray(parsed.sandbox)) {
|
|
156
|
+
const sb = parsed.sandbox;
|
|
157
|
+
if (typeof sb.mode === "string" && isValidMode(sb.mode))
|
|
158
|
+
base.sandbox.mode = sb.mode;
|
|
159
|
+
if (typeof sb.network === "boolean")
|
|
160
|
+
base.sandbox.network = sb.network;
|
|
161
|
+
if (Array.isArray(sb.read_deny))
|
|
162
|
+
base.sandbox.read_deny = sb.read_deny.filter((v) => typeof v === "string");
|
|
163
|
+
if (typeof sb.write_allow_from_runtime === "boolean")
|
|
164
|
+
base.sandbox.write_allow_from_runtime = sb.write_allow_from_runtime;
|
|
165
|
+
}
|
|
166
|
+
if (parsed.isolation && typeof parsed.isolation === "object" && !Array.isArray(parsed.isolation)) {
|
|
167
|
+
const iso = parsed.isolation;
|
|
168
|
+
if (iso.default_mode === "worktree")
|
|
169
|
+
base.isolation.default_mode = iso.default_mode;
|
|
170
|
+
if (typeof iso.repo_symlink_fallback === "boolean")
|
|
171
|
+
base.isolation.repo_symlink_fallback = iso.repo_symlink_fallback;
|
|
172
|
+
if (typeof iso.strict_provisioning === "boolean")
|
|
173
|
+
base.isolation.strict_provisioning = iso.strict_provisioning;
|
|
174
|
+
if (typeof iso.fail_closed_on_provision_error === "boolean")
|
|
175
|
+
base.isolation.fail_closed_on_provision_error = iso.fail_closed_on_provision_error;
|
|
176
|
+
}
|
|
177
|
+
if (parsed.completion && typeof parsed.completion === "object" && !Array.isArray(parsed.completion)) {
|
|
178
|
+
const comp = parsed.completion;
|
|
179
|
+
if (typeof comp.derive_checks_from_scope === "boolean")
|
|
180
|
+
base.completion.derive_checks_from_scope = comp.derive_checks_from_scope;
|
|
181
|
+
if (Array.isArray(comp.checks))
|
|
182
|
+
base.completion.checks = comp.checks.filter((v) => typeof v === "string");
|
|
183
|
+
if (Array.isArray(comp.typescript_config_probe))
|
|
184
|
+
base.completion.typescript_config_probe = comp.typescript_config_probe.filter((v) => typeof v === "string");
|
|
185
|
+
if (Array.isArray(comp.eslint_config_probe))
|
|
186
|
+
base.completion.eslint_config_probe = comp.eslint_config_probe.filter((v) => typeof v === "string");
|
|
187
|
+
}
|
|
188
|
+
if (parsed.runtime_image && typeof parsed.runtime_image === "object" && !Array.isArray(parsed.runtime_image)) {
|
|
189
|
+
const runtimeImage = parsed.runtime_image;
|
|
190
|
+
if (runtimeImage.deps && typeof runtimeImage.deps === "object" && !Array.isArray(runtimeImage.deps)) {
|
|
191
|
+
const deps = runtimeImage.deps;
|
|
192
|
+
if (typeof deps.monorepo_install === "boolean") {
|
|
193
|
+
base.runtime_image.deps.monorepo_install = deps.monorepo_install;
|
|
194
|
+
}
|
|
195
|
+
if (typeof deps.hp_next_install === "boolean") {
|
|
196
|
+
base.runtime_image.deps.hp_next_install = deps.hp_next_install;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
if (typeof runtimeImage.plugins_require_binaries === "boolean") {
|
|
200
|
+
base.runtime_image.plugins_require_binaries = runtimeImage.plugins_require_binaries;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
if (parsed.runtime_snapshot && typeof parsed.runtime_snapshot === "object" && !Array.isArray(parsed.runtime_snapshot)) {
|
|
204
|
+
const runtimeSnapshot = parsed.runtime_snapshot;
|
|
205
|
+
if (typeof runtimeSnapshot.enabled === "boolean") {
|
|
206
|
+
base.runtime_snapshot.enabled = runtimeSnapshot.enabled;
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
return base;
|
|
210
|
+
}
|
|
211
|
+
function isValidMode(value) {
|
|
212
|
+
return value === "off" || value === "observe" || value === "enforce";
|
|
213
|
+
}
|
|
214
|
+
function isValidRule(value) {
|
|
215
|
+
if (!value || typeof value !== "object" || Array.isArray(value))
|
|
216
|
+
return false;
|
|
217
|
+
const r = value;
|
|
218
|
+
return typeof r.id === "string" && typeof r.category === "string" && r.match != null && typeof r.match === "object";
|
|
219
|
+
}
|
|
220
|
+
function migrateLegacyDeny(deny) {
|
|
221
|
+
const rules = [];
|
|
222
|
+
for (const entry of deny) {
|
|
223
|
+
if (typeof entry.id !== "string")
|
|
224
|
+
continue;
|
|
225
|
+
const match = {};
|
|
226
|
+
if (typeof entry.pattern === "string")
|
|
227
|
+
match.pattern = entry.pattern;
|
|
228
|
+
if (typeof entry.regex === "string")
|
|
229
|
+
match.regex = entry.regex;
|
|
230
|
+
if (!match.pattern && !match.regex)
|
|
231
|
+
continue;
|
|
232
|
+
rules.push({
|
|
233
|
+
id: entry.id,
|
|
234
|
+
category: "command",
|
|
235
|
+
match,
|
|
236
|
+
action: "block",
|
|
237
|
+
...typeof entry.description === "string" ? { description: entry.description } : {}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
return rules;
|
|
241
|
+
}
|
|
242
|
+
function precompilePolicyRuleRegexes(rules) {
|
|
243
|
+
return rules.map((rule) => {
|
|
244
|
+
const compiledRegex = rule.match.regex ? compileSafeRegex(rule.match.regex, `rules.${rule.id}.match.regex`, true) : undefined;
|
|
245
|
+
const compiledUnlessRegex = rule.unless?.regex ? compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, true) : undefined;
|
|
246
|
+
return {
|
|
247
|
+
...rule,
|
|
248
|
+
...compiledRegex ? { compiledRegex } : {},
|
|
249
|
+
...compiledUnlessRegex ? { compiledUnlessRegex } : {}
|
|
250
|
+
};
|
|
251
|
+
});
|
|
252
|
+
}
|
|
253
|
+
function getRegexUnsafeReason(pattern) {
|
|
254
|
+
if (pattern.length > 512) {
|
|
255
|
+
return "pattern exceeds max safe length (512 chars)";
|
|
256
|
+
}
|
|
257
|
+
if (/\\[1-9]/.test(pattern)) {
|
|
258
|
+
return "pattern uses backreferences";
|
|
259
|
+
}
|
|
260
|
+
if (/\((?:[^()\\]|\\.)*[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
261
|
+
return "pattern contains nested quantifiers";
|
|
262
|
+
}
|
|
263
|
+
if (/\((?:[^()\\]|\\.)*\.\\?[+*](?:[^()\\]|\\.)*\)\s*[*+{]/.test(pattern)) {
|
|
264
|
+
return "pattern contains nested broad quantifiers";
|
|
265
|
+
}
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
function compileSafeRegex(pattern, sourceLabel, logOnFailure) {
|
|
269
|
+
const cached = compiledRegexCache.get(pattern);
|
|
270
|
+
if (cached !== undefined) {
|
|
271
|
+
return cached ?? undefined;
|
|
272
|
+
}
|
|
273
|
+
const unsafeReason = getRegexUnsafeReason(pattern);
|
|
274
|
+
if (unsafeReason) {
|
|
275
|
+
if (logOnFailure) {
|
|
276
|
+
console.warn(`[policy] Skipping unsafe regex in ${sourceLabel}: ${unsafeReason}`);
|
|
277
|
+
}
|
|
278
|
+
compiledRegexCache.set(pattern, null);
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
try {
|
|
282
|
+
const compiled = new RegExp(pattern);
|
|
283
|
+
compiledRegexCache.set(pattern, compiled);
|
|
284
|
+
return compiled;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
if (logOnFailure) {
|
|
287
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
288
|
+
console.warn(`[policy] Skipping invalid regex in ${sourceLabel}: ${message}`);
|
|
289
|
+
}
|
|
290
|
+
compiledRegexCache.set(pattern, null);
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function matchRule(rule, input) {
|
|
295
|
+
const { match } = rule;
|
|
296
|
+
if (match.pattern && input.includes(match.pattern)) {
|
|
297
|
+
return true;
|
|
298
|
+
}
|
|
299
|
+
if (match.regex) {
|
|
300
|
+
const compiled = rule.compiledRegex || compileSafeRegex(match.regex, `rules.${rule.id}.match.regex`, false);
|
|
301
|
+
if (!compiled) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
return compiled.test(input);
|
|
306
|
+
} catch {
|
|
307
|
+
return false;
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
return false;
|
|
311
|
+
}
|
|
312
|
+
function matchRuleUnless(rule, command, taskId) {
|
|
313
|
+
if (!rule.unless)
|
|
314
|
+
return false;
|
|
315
|
+
if (rule.unless.regex) {
|
|
316
|
+
const compiled = rule.compiledUnlessRegex || compileSafeRegex(rule.unless.regex, `rules.${rule.id}.unless.regex`, false);
|
|
317
|
+
if (!compiled) {
|
|
318
|
+
return false;
|
|
319
|
+
}
|
|
320
|
+
try {
|
|
321
|
+
if (compiled.test(command))
|
|
322
|
+
return true;
|
|
323
|
+
} catch {}
|
|
324
|
+
}
|
|
325
|
+
if (rule.unless.task_in && taskId) {
|
|
326
|
+
if (rule.unless.task_in.includes(taskId))
|
|
327
|
+
return true;
|
|
328
|
+
}
|
|
329
|
+
return false;
|
|
330
|
+
}
|
|
331
|
+
function resolveAction(mode, matched) {
|
|
332
|
+
if (matched.length === 0)
|
|
333
|
+
return "allow";
|
|
334
|
+
if (mode === "off")
|
|
335
|
+
return "allow";
|
|
336
|
+
if (mode === "observe")
|
|
337
|
+
return "warn";
|
|
338
|
+
return "block";
|
|
339
|
+
}
|
|
340
|
+
function resolveAbsolutePath(projectRoot, rawPath) {
|
|
341
|
+
if (rawPath.startsWith("/"))
|
|
342
|
+
return resolve(rawPath);
|
|
343
|
+
return resolve(projectRoot, rawPath);
|
|
344
|
+
}
|
|
345
|
+
function isHarnessPath(projectRoot, rawPath) {
|
|
346
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
347
|
+
const managedRoots = [
|
|
348
|
+
resolve(projectRoot, "rig"),
|
|
349
|
+
resolve(projectRoot, ".rig"),
|
|
350
|
+
resolve(projectRoot, "artifacts")
|
|
351
|
+
];
|
|
352
|
+
return managedRoots.some((root) => absPath === root || absPath.startsWith(root + "/"));
|
|
353
|
+
}
|
|
354
|
+
function isRuntimePath(projectRoot, rawPath, taskWorkspace) {
|
|
355
|
+
const absPath = resolveAbsolutePath(projectRoot, rawPath);
|
|
356
|
+
if (taskWorkspace) {
|
|
357
|
+
const workspaceRigRoot = resolve(taskWorkspace, ".rig");
|
|
358
|
+
const workspaceArtifactsRoot = resolve(taskWorkspace, "artifacts");
|
|
359
|
+
if (absPath === workspaceRigRoot || absPath.startsWith(workspaceRigRoot + "/") || absPath === workspaceArtifactsRoot || absPath.startsWith(workspaceArtifactsRoot + "/")) {
|
|
360
|
+
return true;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
const runtimeRoot = resolve(projectRoot, ".rig/runtime/agents");
|
|
364
|
+
return absPath === runtimeRoot || absPath.startsWith(runtimeRoot + "/");
|
|
365
|
+
}
|
|
366
|
+
function isTestFile(path) {
|
|
367
|
+
return /\.(test|spec)\.(ts|tsx|js|jsx)$/.test(path) || /\/(__tests__|tests|test)\//.test(path);
|
|
368
|
+
}
|
|
369
|
+
function evaluate(context) {
|
|
370
|
+
const policy = loadPolicy(context.projectRoot);
|
|
371
|
+
switch (context.evaluation.type) {
|
|
372
|
+
case "tool-call":
|
|
373
|
+
return evaluateToolCall(policy, context);
|
|
374
|
+
case "command":
|
|
375
|
+
return evaluateCommand(policy, context);
|
|
376
|
+
case "content-write":
|
|
377
|
+
return evaluateContent(policy, context);
|
|
378
|
+
case "file-access":
|
|
379
|
+
return evaluateScope(policy, context, context.evaluation.file_path, context.evaluation.access);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
function evaluateScope(policy, context, filePath, access) {
|
|
383
|
+
const allowed = () => ({
|
|
384
|
+
allowed: true,
|
|
385
|
+
matchedRules: [],
|
|
386
|
+
action: "allow",
|
|
387
|
+
failClosed: false
|
|
388
|
+
});
|
|
389
|
+
if (policy.scope.harness_paths_exempt && isHarnessPath(context.projectRoot, filePath)) {
|
|
390
|
+
return allowed();
|
|
391
|
+
}
|
|
392
|
+
if (policy.scope.runtime_paths_exempt && isRuntimePath(context.projectRoot, filePath, context.taskWorkspace)) {
|
|
393
|
+
return allowed();
|
|
394
|
+
}
|
|
395
|
+
if (!context.taskId) {
|
|
396
|
+
if (access === "write" && policy.scope.fail_closed) {
|
|
397
|
+
return {
|
|
398
|
+
allowed: false,
|
|
399
|
+
matchedRules: [],
|
|
400
|
+
action: resolveAction(policy.mode, [{ id: "scope:no-task", category: "command", reason: "No active task; fail-closed for write operations" }]),
|
|
401
|
+
failClosed: true
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
return allowed();
|
|
405
|
+
}
|
|
406
|
+
const scopes = context.taskScopes || [];
|
|
407
|
+
if (scopes.length === 0) {
|
|
408
|
+
return allowed();
|
|
409
|
+
}
|
|
410
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith("/")) {
|
|
411
|
+
const absPath = resolve(filePath);
|
|
412
|
+
if (!absPath.startsWith(context.taskWorkspace + "/") && !isHarnessPath(context.projectRoot, filePath)) {
|
|
413
|
+
const reason2 = `Absolute path '${filePath}' is outside task runtime boundary. Allowed root: ${context.taskWorkspace}`;
|
|
414
|
+
const matched2 = [{ id: "scope:workspace-boundary", category: "command", reason: reason2 }];
|
|
415
|
+
return {
|
|
416
|
+
allowed: policy.mode !== "enforce",
|
|
417
|
+
matchedRules: matched2,
|
|
418
|
+
action: resolveAction(policy.mode, matched2),
|
|
419
|
+
failClosed: false
|
|
420
|
+
};
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
const monorepoRoot = context.monorepoRoot || process.env.MONOREPO_ROOT?.trim() || context.taskWorkspace || context.projectRoot;
|
|
424
|
+
let normalizedPath = filePath;
|
|
425
|
+
if (context.taskWorkspace && context.taskWorkspace !== context.projectRoot && filePath.startsWith(context.taskWorkspace + "/")) {
|
|
426
|
+
normalizedPath = filePath.slice(context.taskWorkspace.length + 1);
|
|
427
|
+
}
|
|
428
|
+
normalizedPath = normalizePathToScope(context.projectRoot, monorepoRoot, normalizedPath);
|
|
429
|
+
if (scopeMatches(filePath, scopes) || scopeMatches(normalizedPath, scopes)) {
|
|
430
|
+
return allowed();
|
|
431
|
+
}
|
|
432
|
+
const reason = `File '${filePath}' (normalized: '${normalizedPath}') is outside scope of task ${context.taskId}`;
|
|
433
|
+
const matched = [{ id: "scope:out-of-scope", category: "command", reason }];
|
|
434
|
+
return {
|
|
435
|
+
allowed: policy.mode !== "enforce",
|
|
436
|
+
matchedRules: matched,
|
|
437
|
+
action: resolveAction(policy.mode, matched),
|
|
438
|
+
failClosed: false
|
|
439
|
+
};
|
|
440
|
+
}
|
|
441
|
+
function evaluateCommand(policy, context) {
|
|
442
|
+
const evaluation = context.evaluation;
|
|
443
|
+
if (evaluation.type !== "command") {
|
|
444
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
445
|
+
}
|
|
446
|
+
const command = evaluation.command;
|
|
447
|
+
const matchedRules = [];
|
|
448
|
+
for (const rule of policy.rules) {
|
|
449
|
+
if (rule.category !== "command")
|
|
450
|
+
continue;
|
|
451
|
+
if (!matchRule(rule, command))
|
|
452
|
+
continue;
|
|
453
|
+
if (matchRuleUnless(rule, command, context.taskId))
|
|
454
|
+
continue;
|
|
455
|
+
matchedRules.push({
|
|
456
|
+
id: rule.id,
|
|
457
|
+
category: rule.category,
|
|
458
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
459
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
460
|
+
});
|
|
461
|
+
}
|
|
462
|
+
const writeTarget = extractWriteTarget(command);
|
|
463
|
+
if (writeTarget && !/^\/dev\//.test(writeTarget) && !/^\/proc\//.test(writeTarget)) {
|
|
464
|
+
const scopeResult = evaluateScope(policy, context, writeTarget, "write");
|
|
465
|
+
if (!scopeResult.allowed || scopeResult.matchedRules.length > 0) {
|
|
466
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
470
|
+
return {
|
|
471
|
+
allowed: action !== "block",
|
|
472
|
+
matchedRules,
|
|
473
|
+
action,
|
|
474
|
+
failClosed: false
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
function extractWriteTarget(command) {
|
|
478
|
+
const redirect = command.match(/>>?\s+([^\s;|&]+)/);
|
|
479
|
+
if (redirect?.[1])
|
|
480
|
+
return redirect[1];
|
|
481
|
+
const tee = command.match(/tee\s+(-a\s+)?([^\s;|&]+)/);
|
|
482
|
+
if (tee?.[2])
|
|
483
|
+
return tee[2];
|
|
484
|
+
return "";
|
|
485
|
+
}
|
|
486
|
+
function evaluateContent(policy, context) {
|
|
487
|
+
const evaluation = context.evaluation;
|
|
488
|
+
if (evaluation.type !== "content-write") {
|
|
489
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
490
|
+
}
|
|
491
|
+
const { content, file_path } = evaluation;
|
|
492
|
+
const matchedRules = [];
|
|
493
|
+
const scopeResult = evaluateScope(policy, context, file_path, "write");
|
|
494
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
495
|
+
matchedRules.push(...scopeResult.matchedRules);
|
|
496
|
+
}
|
|
497
|
+
for (const rule of policy.rules) {
|
|
498
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
499
|
+
continue;
|
|
500
|
+
if (rule.applies_to === "test-files" && !isTestFile(file_path))
|
|
501
|
+
continue;
|
|
502
|
+
if (!matchRule(rule, content))
|
|
503
|
+
continue;
|
|
504
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
505
|
+
continue;
|
|
506
|
+
matchedRules.push({
|
|
507
|
+
id: rule.id,
|
|
508
|
+
category: rule.category,
|
|
509
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
510
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
511
|
+
});
|
|
512
|
+
}
|
|
513
|
+
const action = resolveAction(policy.mode, matchedRules);
|
|
514
|
+
return {
|
|
515
|
+
allowed: action !== "block",
|
|
516
|
+
matchedRules,
|
|
517
|
+
action,
|
|
518
|
+
failClosed: false
|
|
519
|
+
};
|
|
520
|
+
}
|
|
521
|
+
function evaluateToolCall(policy, context) {
|
|
522
|
+
const evaluation = context.evaluation;
|
|
523
|
+
if (evaluation.type !== "tool-call") {
|
|
524
|
+
return { allowed: true, matchedRules: [], action: "allow", failClosed: false };
|
|
525
|
+
}
|
|
526
|
+
const { tool_name, tool_input } = evaluation;
|
|
527
|
+
const allMatched = [];
|
|
528
|
+
const filePaths = extractFilePathsFromToolInput(tool_name, tool_input);
|
|
529
|
+
for (const fp of filePaths) {
|
|
530
|
+
const access = isWriteTool(tool_name) ? "write" : "read";
|
|
531
|
+
const scopeResult = evaluateScope(policy, context, fp, access);
|
|
532
|
+
if (scopeResult.matchedRules.length > 0) {
|
|
533
|
+
allMatched.push(...scopeResult.matchedRules);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
const content = extractContentFromToolInput(tool_input);
|
|
537
|
+
if (content) {
|
|
538
|
+
const filePath = filePaths[0] || "";
|
|
539
|
+
const contentContext = {
|
|
540
|
+
...context,
|
|
541
|
+
evaluation: { type: "content-write", file_path: filePath, content }
|
|
542
|
+
};
|
|
543
|
+
const contentPolicy = loadPolicy(context.projectRoot);
|
|
544
|
+
for (const rule of contentPolicy.rules) {
|
|
545
|
+
if (rule.category !== "content" && rule.category !== "import" && rule.category !== "test-integrity")
|
|
546
|
+
continue;
|
|
547
|
+
if (rule.applies_to === "test-files" && !isTestFile(filePath))
|
|
548
|
+
continue;
|
|
549
|
+
if (!matchRule(rule, content))
|
|
550
|
+
continue;
|
|
551
|
+
if (matchRuleUnless(rule, content, context.taskId))
|
|
552
|
+
continue;
|
|
553
|
+
allMatched.push({
|
|
554
|
+
id: rule.id,
|
|
555
|
+
category: rule.category,
|
|
556
|
+
...rule.description !== undefined ? { description: rule.description } : {},
|
|
557
|
+
reason: rule.description || `Matched rule ${rule.id}`
|
|
558
|
+
});
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (tool_name === "Bash") {
|
|
562
|
+
const command = String(tool_input.command || tool_input.cmd || "");
|
|
563
|
+
if (command) {
|
|
564
|
+
const cmdContext = {
|
|
565
|
+
...context,
|
|
566
|
+
evaluation: { type: "command", command }
|
|
567
|
+
};
|
|
568
|
+
const cmdResult = evaluateCommand(policy, cmdContext);
|
|
569
|
+
if (cmdResult.matchedRules.length > 0) {
|
|
570
|
+
allMatched.push(...cmdResult.matchedRules);
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
const seen = new Set;
|
|
575
|
+
const deduplicated = [];
|
|
576
|
+
for (const rule of allMatched) {
|
|
577
|
+
if (!seen.has(rule.id)) {
|
|
578
|
+
seen.add(rule.id);
|
|
579
|
+
deduplicated.push(rule);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
const action = resolveAction(policy.mode, deduplicated);
|
|
583
|
+
return {
|
|
584
|
+
allowed: action !== "block",
|
|
585
|
+
matchedRules: deduplicated,
|
|
586
|
+
action,
|
|
587
|
+
failClosed: false
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function isWriteTool(toolName) {
|
|
591
|
+
return toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit";
|
|
592
|
+
}
|
|
593
|
+
function extractFilePathsFromToolInput(toolName, input) {
|
|
594
|
+
const paths = [];
|
|
595
|
+
const add = (value) => {
|
|
596
|
+
if (typeof value === "string" && value.trim()) {
|
|
597
|
+
paths.push(value.trim());
|
|
598
|
+
}
|
|
599
|
+
};
|
|
600
|
+
if (toolName === "Read" || toolName === "Write" || toolName === "Edit" || toolName === "MultiEdit") {
|
|
601
|
+
add(input.file_path);
|
|
602
|
+
add(input.path);
|
|
603
|
+
} else if (toolName === "Glob") {
|
|
604
|
+
add(input.path);
|
|
605
|
+
} else if (toolName === "Grep") {
|
|
606
|
+
add(input.path);
|
|
607
|
+
} else {
|
|
608
|
+
add(input.file_path);
|
|
609
|
+
add(input.path);
|
|
610
|
+
}
|
|
611
|
+
return paths;
|
|
612
|
+
}
|
|
613
|
+
function extractContentFromToolInput(input) {
|
|
614
|
+
if (typeof input.content === "string")
|
|
615
|
+
return input.content;
|
|
616
|
+
if (typeof input.new_string === "string")
|
|
617
|
+
return input.new_string;
|
|
618
|
+
return "";
|
|
619
|
+
}
|
|
620
|
+
function primeGuardHotPaths() {
|
|
621
|
+
if (guardHotPathPrimed) {
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
guardHotPathPrimed = true;
|
|
625
|
+
try {
|
|
626
|
+
optimizeNextInvocation(matchRule);
|
|
627
|
+
optimizeNextInvocation(evaluate);
|
|
628
|
+
} catch {}
|
|
629
|
+
}
|
|
630
|
+
var DEFAULT_SCOPE, DEFAULT_SANDBOX, DEFAULT_ISOLATION, DEFAULT_COMPLETION, DEFAULT_RUNTIME_IMAGE, DEFAULT_RUNTIME_SNAPSHOT, policyCache = null, policyCachePath = null, seededPolicyConfig = null, compiledRegexCache, guardHotPathPrimed = false;
|
|
631
|
+
var init_guard = __esm(() => {
|
|
632
|
+
init_scope();
|
|
633
|
+
DEFAULT_SCOPE = {
|
|
634
|
+
fail_closed: true,
|
|
635
|
+
harness_paths_exempt: true,
|
|
636
|
+
runtime_paths_exempt: true
|
|
637
|
+
};
|
|
638
|
+
DEFAULT_SANDBOX = {
|
|
639
|
+
mode: "enforce",
|
|
640
|
+
network: true,
|
|
641
|
+
read_deny: [],
|
|
642
|
+
write_allow_from_runtime: true
|
|
643
|
+
};
|
|
644
|
+
DEFAULT_ISOLATION = {
|
|
645
|
+
default_mode: "worktree",
|
|
646
|
+
repo_symlink_fallback: false,
|
|
647
|
+
strict_provisioning: true,
|
|
648
|
+
fail_closed_on_provision_error: true
|
|
649
|
+
};
|
|
650
|
+
DEFAULT_COMPLETION = {
|
|
651
|
+
derive_checks_from_scope: true,
|
|
652
|
+
checks: [],
|
|
653
|
+
typescript_config_probe: ["tsconfig.json"],
|
|
654
|
+
eslint_config_probe: [".eslintrc.js", ".eslintrc.json", "eslint.config.js"]
|
|
655
|
+
};
|
|
656
|
+
DEFAULT_RUNTIME_IMAGE = {
|
|
657
|
+
deps: {
|
|
658
|
+
monorepo_install: false,
|
|
659
|
+
hp_next_install: false
|
|
660
|
+
},
|
|
661
|
+
plugins_require_binaries: true
|
|
662
|
+
};
|
|
663
|
+
DEFAULT_RUNTIME_SNAPSHOT = {
|
|
664
|
+
enabled: true
|
|
665
|
+
};
|
|
666
|
+
compiledRegexCache = new Map;
|
|
667
|
+
primeGuardHotPaths();
|
|
668
|
+
});
|
|
4
669
|
|
|
5
670
|
// packages/guard-plugin/src/hooks/safety-guard.ts
|
|
671
|
+
var exports_safety_guard = {};
|
|
672
|
+
__export(exports_safety_guard, {
|
|
673
|
+
safetyGuardHandler: () => safetyGuardHandler,
|
|
674
|
+
SAFETY_GUARD_HOOK_ID: () => SAFETY_GUARD_HOOK_ID
|
|
675
|
+
});
|
|
6
676
|
import { resolveTaskScopes, resolvePolicyContent, runTypedHook } from "@rig/hook-kit";
|
|
7
|
-
import { evaluate, seedPolicyFromContent } from "@rig/runtime/control-plane/runtime/guard";
|
|
8
|
-
var SAFETY_GUARD_HOOK_ID = "@rig/guard-plugin:safety-guard";
|
|
9
677
|
async function safetyGuardHandler(ctx) {
|
|
10
678
|
const projectRoot = ctx.projectRoot;
|
|
11
679
|
seedPolicyFromContent(resolvePolicyContent(projectRoot));
|
|
@@ -62,17 +730,23 @@ async function safetyGuardHandler(ctx) {
|
|
|
62
730
|
}
|
|
63
731
|
return { decision: "allow" };
|
|
64
732
|
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
733
|
+
var init_safety_guard = __esm(() => {
|
|
734
|
+
init_guard();
|
|
735
|
+
if (process.env.RIG_HOOK_ROLE === "safety-guard") {
|
|
736
|
+
runTypedHook(safetyGuardHandler, { event: "PreToolUse" });
|
|
737
|
+
}
|
|
738
|
+
});
|
|
68
739
|
|
|
69
740
|
// packages/guard-plugin/src/hooks/scope-guard.ts
|
|
741
|
+
var exports_scope_guard = {};
|
|
742
|
+
__export(exports_scope_guard, {
|
|
743
|
+
scopeGuardHandler: () => scopeGuardHandler,
|
|
744
|
+
SCOPE_GUARD_HOOK_ID: () => SCOPE_GUARD_HOOK_ID
|
|
745
|
+
});
|
|
70
746
|
import { resolveTaskScopes as resolveTaskScopes2, resolvePolicyContent as resolvePolicyContent2, runTypedHook as runTypedHook2 } from "@rig/hook-kit";
|
|
71
|
-
import { evaluate as evaluate2, seedPolicyFromContent as seedPolicyFromContent2 } from "@rig/runtime/control-plane/runtime/guard";
|
|
72
|
-
var SCOPE_GUARD_HOOK_ID = "@rig/guard-plugin:scope-guard";
|
|
73
747
|
async function scopeGuardHandler(ctx) {
|
|
74
748
|
const projectRoot = ctx.projectRoot;
|
|
75
|
-
|
|
749
|
+
seedPolicyFromContent(resolvePolicyContent2(projectRoot));
|
|
76
750
|
const tool = ctx.toolName ?? "";
|
|
77
751
|
const toolInput = ctx.toolInput;
|
|
78
752
|
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
@@ -111,7 +785,7 @@ async function scopeGuardHandler(ctx) {
|
|
|
111
785
|
if (scopes.length === 0) {
|
|
112
786
|
return { decision: "allow" };
|
|
113
787
|
}
|
|
114
|
-
const decision =
|
|
788
|
+
const decision = evaluate({
|
|
115
789
|
projectRoot,
|
|
116
790
|
taskScopes: scopes,
|
|
117
791
|
evaluation,
|
|
@@ -125,17 +799,23 @@ async function scopeGuardHandler(ctx) {
|
|
|
125
799
|
}
|
|
126
800
|
return { decision: "allow" };
|
|
127
801
|
}
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
802
|
+
var init_scope_guard = __esm(() => {
|
|
803
|
+
init_guard();
|
|
804
|
+
if (process.env.RIG_HOOK_ROLE === "scope-guard") {
|
|
805
|
+
runTypedHook2(scopeGuardHandler, { event: "PreToolUse" });
|
|
806
|
+
}
|
|
807
|
+
});
|
|
131
808
|
|
|
132
809
|
// packages/guard-plugin/src/hooks/import-guard.ts
|
|
810
|
+
var exports_import_guard = {};
|
|
811
|
+
__export(exports_import_guard, {
|
|
812
|
+
importGuardHandler: () => importGuardHandler,
|
|
813
|
+
IMPORT_GUARD_HOOK_ID: () => IMPORT_GUARD_HOOK_ID
|
|
814
|
+
});
|
|
133
815
|
import { resolveTaskScopes as resolveTaskScopes3, resolvePolicyContent as resolvePolicyContent3, runTypedHook as runTypedHook3 } from "@rig/hook-kit";
|
|
134
|
-
import { evaluate as evaluate3, seedPolicyFromContent as seedPolicyFromContent3 } from "@rig/runtime/control-plane/runtime/guard";
|
|
135
|
-
var IMPORT_GUARD_HOOK_ID = "@rig/guard-plugin:import-guard";
|
|
136
816
|
async function importGuardHandler(ctx) {
|
|
137
817
|
const projectRoot = ctx.projectRoot;
|
|
138
|
-
|
|
818
|
+
seedPolicyFromContent(resolvePolicyContent3(projectRoot));
|
|
139
819
|
const toolInput = ctx.toolInput;
|
|
140
820
|
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
141
821
|
if (!content) {
|
|
@@ -150,7 +830,7 @@ async function importGuardHandler(ctx) {
|
|
|
150
830
|
const taskId = ctx.taskId || "";
|
|
151
831
|
const scopes = taskId ? await resolveTaskScopes3(projectRoot, taskId) : [];
|
|
152
832
|
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
153
|
-
const decision =
|
|
833
|
+
const decision = evaluate({
|
|
154
834
|
projectRoot,
|
|
155
835
|
taskScopes: scopes,
|
|
156
836
|
evaluation,
|
|
@@ -170,17 +850,23 @@ Only import from a module public API (index.ts) or npm packages.`
|
|
|
170
850
|
}
|
|
171
851
|
return { decision: "allow" };
|
|
172
852
|
}
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
853
|
+
var init_import_guard = __esm(() => {
|
|
854
|
+
init_guard();
|
|
855
|
+
if (process.env.RIG_HOOK_ROLE === "import-guard") {
|
|
856
|
+
runTypedHook3(importGuardHandler, { event: "PreToolUse" });
|
|
857
|
+
}
|
|
858
|
+
});
|
|
176
859
|
|
|
177
860
|
// packages/guard-plugin/src/hooks/test-integrity-guard.ts
|
|
861
|
+
var exports_test_integrity_guard = {};
|
|
862
|
+
__export(exports_test_integrity_guard, {
|
|
863
|
+
testIntegrityGuardHandler: () => testIntegrityGuardHandler,
|
|
864
|
+
TEST_INTEGRITY_GUARD_HOOK_ID: () => TEST_INTEGRITY_GUARD_HOOK_ID
|
|
865
|
+
});
|
|
178
866
|
import { resolveTaskScopes as resolveTaskScopes4, resolvePolicyContent as resolvePolicyContent4, isTestFilePath, runTypedHook as runTypedHook4 } from "@rig/hook-kit";
|
|
179
|
-
import { evaluate as evaluate4, seedPolicyFromContent as seedPolicyFromContent4 } from "@rig/runtime/control-plane/runtime/guard";
|
|
180
|
-
var TEST_INTEGRITY_GUARD_HOOK_ID = "@rig/guard-plugin:test-integrity-guard";
|
|
181
867
|
async function testIntegrityGuardHandler(ctx) {
|
|
182
868
|
const projectRoot = ctx.projectRoot;
|
|
183
|
-
|
|
869
|
+
seedPolicyFromContent(resolvePolicyContent4(projectRoot));
|
|
184
870
|
const toolInput = ctx.toolInput;
|
|
185
871
|
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
186
872
|
const content = String(toolInput.content ?? toolInput.new_string ?? "");
|
|
@@ -195,7 +881,7 @@ async function testIntegrityGuardHandler(ctx) {
|
|
|
195
881
|
const taskId = ctx.taskId || "";
|
|
196
882
|
const scopes = taskId ? await resolveTaskScopes4(projectRoot, taskId) : [];
|
|
197
883
|
const taskWorkspace = process.env.RIG_TASK_WORKSPACE || "";
|
|
198
|
-
const decision =
|
|
884
|
+
const decision = evaluate({
|
|
199
885
|
projectRoot,
|
|
200
886
|
taskScopes: scopes,
|
|
201
887
|
evaluation,
|
|
@@ -216,21 +902,28 @@ async function testIntegrityGuardHandler(ctx) {
|
|
|
216
902
|
}
|
|
217
903
|
return { decision: "allow" };
|
|
218
904
|
}
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
905
|
+
var init_test_integrity_guard = __esm(() => {
|
|
906
|
+
init_guard();
|
|
907
|
+
if (process.env.RIG_HOOK_ROLE === "test-integrity-guard") {
|
|
908
|
+
runTypedHook4(testIntegrityGuardHandler, { event: "PreToolUse" });
|
|
909
|
+
}
|
|
910
|
+
});
|
|
222
911
|
|
|
223
912
|
// packages/guard-plugin/src/hooks/audit-trail.ts
|
|
224
|
-
|
|
225
|
-
|
|
913
|
+
var exports_audit_trail = {};
|
|
914
|
+
__export(exports_audit_trail, {
|
|
915
|
+
auditTrailHandler: () => auditTrailHandler,
|
|
916
|
+
AUDIT_TRAIL_HOOK_ID: () => AUDIT_TRAIL_HOOK_ID
|
|
917
|
+
});
|
|
918
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "fs";
|
|
919
|
+
import { resolve as resolve4 } from "path";
|
|
226
920
|
import { runTypedHook as runTypedHook5 } from "@rig/hook-kit";
|
|
227
|
-
import {
|
|
228
|
-
var AUDIT_TRAIL_HOOK_ID = "@rig/guard-plugin:audit-trail";
|
|
921
|
+
import { resolveRigLayout } from "@rig/core/layout";
|
|
229
922
|
function auditTrailHandler(ctx) {
|
|
230
923
|
const projectRoot = ctx.projectRoot;
|
|
231
924
|
const toolInput = ctx.toolInput;
|
|
232
|
-
const
|
|
233
|
-
|
|
925
|
+
const logsDir = process.env.RIG_LOGS_DIR?.trim() || resolveRigLayout(projectRoot).logsDir;
|
|
926
|
+
mkdirSync3(logsDir, { recursive: true });
|
|
234
927
|
const taskId = ctx.taskId || "unknown";
|
|
235
928
|
const tool = ctx.toolName || "unknown";
|
|
236
929
|
const filePath = String(toolInput.file_path ?? toolInput.path ?? "none");
|
|
@@ -244,25 +937,31 @@ function auditTrailHandler(ctx) {
|
|
|
244
937
|
if (tool === "Bash" && command) {
|
|
245
938
|
payload.command_preview = command;
|
|
246
939
|
}
|
|
247
|
-
appendFileSync(
|
|
940
|
+
appendFileSync(resolve4(logsDir, "audit.jsonl"), `${JSON.stringify(payload)}
|
|
248
941
|
`, "utf-8");
|
|
249
942
|
return { decision: "allow" };
|
|
250
943
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
}
|
|
944
|
+
var init_audit_trail = __esm(() => {
|
|
945
|
+
if (process.env.RIG_HOOK_ROLE === "audit-trail") {
|
|
946
|
+
runTypedHook5(auditTrailHandler, { event: "PreToolUse" });
|
|
947
|
+
}
|
|
948
|
+
});
|
|
254
949
|
|
|
255
950
|
// packages/guard-plugin/src/hooks/post-edit-lint.ts
|
|
256
|
-
|
|
951
|
+
var exports_post_edit_lint = {};
|
|
952
|
+
__export(exports_post_edit_lint, {
|
|
953
|
+
postEditLintHandler: () => postEditLintHandler,
|
|
954
|
+
POST_EDIT_LINT_HOOK_ID: () => POST_EDIT_LINT_HOOK_ID
|
|
955
|
+
});
|
|
956
|
+
import { existsSync as existsSync3, readFileSync as readFileSync2 } from "fs";
|
|
257
957
|
import { runTypedHook as runTypedHook6 } from "@rig/hook-kit";
|
|
258
|
-
var POST_EDIT_LINT_HOOK_ID = "@rig/guard-plugin:post-edit-lint";
|
|
259
958
|
function postEditLintHandler(ctx) {
|
|
260
959
|
const toolInput = ctx.toolInput;
|
|
261
960
|
const filePath = String(toolInput.file_path ?? toolInput.path ?? "");
|
|
262
|
-
if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !
|
|
961
|
+
if (!filePath || !/\.(ts|tsx|js|jsx)$/.test(filePath) || !existsSync3(filePath)) {
|
|
263
962
|
return { decision: "allow" };
|
|
264
963
|
}
|
|
265
|
-
const content =
|
|
964
|
+
const content = readFileSync2(filePath, "utf-8");
|
|
266
965
|
const warnings = [];
|
|
267
966
|
const consoleCount = (content.match(/console\.(log|debug)\(/g) || []).length;
|
|
268
967
|
if (consoleCount > 3) {
|
|
@@ -291,8 +990,306 @@ These are not blocking, but must be resolved before task completion.`
|
|
|
291
990
|
}
|
|
292
991
|
return { decision: "allow" };
|
|
293
992
|
}
|
|
294
|
-
|
|
295
|
-
|
|
993
|
+
var init_post_edit_lint = __esm(() => {
|
|
994
|
+
if (process.env.RIG_HOOK_ROLE === "post-edit-lint") {
|
|
995
|
+
runTypedHook6(postEditLintHandler, { event: "PostToolUse" });
|
|
996
|
+
}
|
|
997
|
+
});
|
|
998
|
+
|
|
999
|
+
// packages/guard-plugin/src/plugin.ts
|
|
1000
|
+
import { definePlugin } from "@rig/core/config";
|
|
1001
|
+
import { defineCapability } from "@rig/core/capability";
|
|
1002
|
+
import { CLI_RUNNER, GUARD_TOOLCHAIN_SOURCES } from "@rig/contracts";
|
|
1003
|
+
|
|
1004
|
+
// packages/guard-plugin/src/agent-shell.ts
|
|
1005
|
+
init_guard();
|
|
1006
|
+
var {$ } = globalThis.Bun;
|
|
1007
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
1008
|
+
import { resolve as resolve3 } from "path";
|
|
1009
|
+
import { EventBus } from "@rig/core/runtime-events";
|
|
1010
|
+
import { CliError } from "@rig/contracts";
|
|
1011
|
+
|
|
1012
|
+
// packages/guard-plugin/src/agent-binary-build.ts
|
|
1013
|
+
import { chmodSync, copyFileSync, linkSync, mkdirSync, rmSync, writeFileSync } from "fs";
|
|
1014
|
+
import { basename, dirname, resolve as resolve2 } from "path";
|
|
1015
|
+
import { runtimeProvisioningEnv } from "@rig/core/runtime-provisioning-env";
|
|
1016
|
+
|
|
1017
|
+
// packages/guard-plugin/src/embedded-native-assets.ts
|
|
1018
|
+
var embeddedNatives = null;
|
|
1019
|
+
|
|
1020
|
+
// packages/guard-plugin/src/agent-binary-build.ts
|
|
1021
|
+
function materializeSelfExecRole(outputPath, define) {
|
|
1022
|
+
const selfPath = process.execPath;
|
|
1023
|
+
mkdirSync(dirname(outputPath), { recursive: true });
|
|
1024
|
+
rmSync(outputPath, { force: true });
|
|
1025
|
+
try {
|
|
1026
|
+
linkSync(selfPath, outputPath);
|
|
1027
|
+
} catch {
|
|
1028
|
+
copyFileSync(selfPath, outputPath);
|
|
1029
|
+
}
|
|
1030
|
+
chmodSync(outputPath, 493);
|
|
1031
|
+
const role = basename(outputPath).replace(/\.exe$/i, "");
|
|
1032
|
+
writeFileSync(`${outputPath}.rig-runconfig.json`, `${JSON.stringify({ role, define: define ?? {} }, null, 2)}
|
|
1033
|
+
`, { mode: 384 });
|
|
1034
|
+
}
|
|
1035
|
+
function hasEmbeddedNatives() {
|
|
1036
|
+
return embeddedNatives !== null;
|
|
1037
|
+
}
|
|
1038
|
+
async function buildAgentBinary(entrypoint, outputPath, cwd, defines) {
|
|
1039
|
+
if (hasEmbeddedNatives()) {
|
|
1040
|
+
materializeSelfExecRole(outputPath, defines);
|
|
1041
|
+
return;
|
|
1042
|
+
}
|
|
1043
|
+
await buildRuntimeBinary({
|
|
1044
|
+
entrypoint,
|
|
1045
|
+
outputPath,
|
|
1046
|
+
cwd,
|
|
1047
|
+
...defines ? { define: defines } : {},
|
|
1048
|
+
env: runtimeProvisioningEnv()
|
|
1049
|
+
});
|
|
1050
|
+
}
|
|
1051
|
+
async function buildRuntimeBinary(options) {
|
|
1052
|
+
mkdirSync(dirname(options.outputPath), { recursive: true });
|
|
1053
|
+
await withTemporaryEnv({
|
|
1054
|
+
...options.env,
|
|
1055
|
+
...options.define ? { RIG_BUILD_CONFIG_JSON: JSON.stringify(options.define) } : {}
|
|
1056
|
+
}, async () => {
|
|
1057
|
+
const result = await Bun.build({
|
|
1058
|
+
entrypoints: [resolve2(options.cwd, options.entrypoint)],
|
|
1059
|
+
compile: { outfile: options.outputPath },
|
|
1060
|
+
target: "bun",
|
|
1061
|
+
format: "esm",
|
|
1062
|
+
minify: true,
|
|
1063
|
+
bytecode: true,
|
|
1064
|
+
...options.define ? { define: { __RIG_BUILD_CONFIG__: JSON.stringify(options.define) } } : {}
|
|
1065
|
+
});
|
|
1066
|
+
if (!result.success) {
|
|
1067
|
+
throw new Error(result.logs.map((log) => log.message).join(`
|
|
1068
|
+
`) || `Failed to compile ${options.entrypoint}`);
|
|
1069
|
+
}
|
|
1070
|
+
});
|
|
1071
|
+
chmodSync(options.outputPath, 493);
|
|
1072
|
+
}
|
|
1073
|
+
async function withTemporaryEnv(env, callback) {
|
|
1074
|
+
const previous = {};
|
|
1075
|
+
for (const [key, value] of Object.entries(env)) {
|
|
1076
|
+
previous[key] = process.env[key];
|
|
1077
|
+
if (value === undefined) {
|
|
1078
|
+
delete process.env[key];
|
|
1079
|
+
} else {
|
|
1080
|
+
process.env[key] = value;
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
try {
|
|
1084
|
+
return await callback();
|
|
1085
|
+
} finally {
|
|
1086
|
+
for (const [key, value] of Object.entries(previous)) {
|
|
1087
|
+
if (value === undefined) {
|
|
1088
|
+
delete process.env[key];
|
|
1089
|
+
} else {
|
|
1090
|
+
process.env[key] = value;
|
|
1091
|
+
}
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// packages/guard-plugin/src/agent-shell.ts
|
|
1097
|
+
init_guard();
|
|
1098
|
+
function withProjectRoot(projectRoot) {
|
|
1099
|
+
$.cwd(projectRoot);
|
|
1100
|
+
}
|
|
1101
|
+
function formatCommand(parts) {
|
|
1102
|
+
return parts.map((part) => /[^a-zA-Z0-9_./:-]/.test(part) ? JSON.stringify(part) : part).join(" ");
|
|
1103
|
+
}
|
|
1104
|
+
var AGENT_DISPATCH_SOURCE = "packages/provider-plugin/src/agent-harness/agent-wrapper.ts";
|
|
1105
|
+
function hasAgentDispatchSource(root) {
|
|
1106
|
+
return existsSync2(resolve3(root, AGENT_DISPATCH_SOURCE));
|
|
1107
|
+
}
|
|
1108
|
+
function resolveAgentMaterializationSourceRoot(projectRoot, options = {}) {
|
|
1109
|
+
const env = options.env ?? process.env;
|
|
1110
|
+
const candidates = [
|
|
1111
|
+
env.RIG_HOST_PROJECT_ROOT?.trim(),
|
|
1112
|
+
env.PROJECT_RIG_ROOT?.trim(),
|
|
1113
|
+
options.cwd ?? process.cwd(),
|
|
1114
|
+
resolve3(options.moduleDir ?? import.meta.dir, "../../.."),
|
|
1115
|
+
projectRoot
|
|
1116
|
+
].filter((value) => Boolean(value));
|
|
1117
|
+
for (const candidate of candidates) {
|
|
1118
|
+
const root = resolve3(candidate);
|
|
1119
|
+
if (hasAgentDispatchSource(root)) {
|
|
1120
|
+
return root;
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
return resolve3(projectRoot);
|
|
1124
|
+
}
|
|
1125
|
+
async function ensureAgentShellBinary(projectRoot, options = {}) {
|
|
1126
|
+
const agentBinary = resolve3(projectRoot, ".rig", "bin", "rig-agent");
|
|
1127
|
+
if (existsSync2(agentBinary)) {
|
|
1128
|
+
return agentBinary;
|
|
1129
|
+
}
|
|
1130
|
+
const sourceRoot = resolveAgentMaterializationSourceRoot(projectRoot, options);
|
|
1131
|
+
if (!hasAgentDispatchSource(sourceRoot)) {
|
|
1132
|
+
throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and no Rig source checkout with ${AGENT_DISPATCH_SOURCE} was found to materialize it automatically.`, 2);
|
|
1133
|
+
}
|
|
1134
|
+
mkdirSync2(resolve3(agentBinary, ".."), { recursive: true });
|
|
1135
|
+
try {
|
|
1136
|
+
await (options.build ?? buildAgentBinary)(AGENT_DISPATCH_SOURCE, agentBinary, sourceRoot, {
|
|
1137
|
+
AGENT_PROJECT_ROOT: projectRoot
|
|
1138
|
+
});
|
|
1139
|
+
} catch (error) {
|
|
1140
|
+
throw new CliError(`Missing compiled agent dispatch binary at ${agentBinary}, and automatic materialization from ${sourceRoot} failed: ${error instanceof Error ? error.message : String(error)}`, 2);
|
|
1141
|
+
}
|
|
1142
|
+
if (!existsSync2(agentBinary)) {
|
|
1143
|
+
throw new CliError(`Automatic agent dispatch materialization from ${sourceRoot} did not create ${agentBinary}.`, 2);
|
|
1144
|
+
}
|
|
1145
|
+
return agentBinary;
|
|
1146
|
+
}
|
|
1147
|
+
async function initializeRuntime(options) {
|
|
1148
|
+
const eventBus = new EventBus({
|
|
1149
|
+
projectRoot: options.projectRoot,
|
|
1150
|
+
...options.runId ? { runId: options.runId } : {}
|
|
1151
|
+
});
|
|
1152
|
+
let context;
|
|
1153
|
+
const baseContext = {
|
|
1154
|
+
projectRoot: options.projectRoot,
|
|
1155
|
+
dryRun: options.dryRun,
|
|
1156
|
+
outputMode: options.outputMode,
|
|
1157
|
+
runId: eventBus.getRunId(),
|
|
1158
|
+
...options.policyMode ? { policyMode: options.policyMode } : {},
|
|
1159
|
+
eventBus,
|
|
1160
|
+
emitEvent: async (type, payload) => {
|
|
1161
|
+
await eventBus.emit(type, payload);
|
|
1162
|
+
}
|
|
1163
|
+
};
|
|
1164
|
+
context = {
|
|
1165
|
+
...baseContext,
|
|
1166
|
+
runCommand: async (parts) => runCommand(context, parts)
|
|
1167
|
+
};
|
|
1168
|
+
await context.emitEvent("runtime.init", {
|
|
1169
|
+
runId: context.runId,
|
|
1170
|
+
outputMode: context.outputMode,
|
|
1171
|
+
dryRun: context.dryRun,
|
|
1172
|
+
policyMode: context.policyMode ?? loadPolicy(options.projectRoot).mode,
|
|
1173
|
+
policyFile: resolve3(options.projectRoot, "rig/policy/policy.json")
|
|
1174
|
+
});
|
|
1175
|
+
return context;
|
|
1176
|
+
}
|
|
1177
|
+
async function runCommand(context, parts) {
|
|
1178
|
+
if (parts.length === 0) {
|
|
1179
|
+
throw new CliError("Cannot execute an empty command.");
|
|
1180
|
+
}
|
|
1181
|
+
const envMode = process.env.RIG_BASH_MODE;
|
|
1182
|
+
const effectiveMode = context.policyMode || (envMode === "off" || envMode === "observe" || envMode === "enforce" ? envMode : loadPolicy(context.projectRoot).mode);
|
|
1183
|
+
const controlledPath = `${resolve3(context.projectRoot, ".rig", "bin")}:${context.projectRoot}/rig/tools:${process.env.PATH ?? ""}`;
|
|
1184
|
+
const usesInfrastructureBinary = parts[0] === "rig-server";
|
|
1185
|
+
const controlledBash = usesInfrastructureBinary ? null : await ensureAgentShellBinary(context.projectRoot);
|
|
1186
|
+
const commandEnv = [
|
|
1187
|
+
"env",
|
|
1188
|
+
`PATH=${controlledPath}`,
|
|
1189
|
+
`PROJECT_RIG_ROOT=${context.projectRoot}`,
|
|
1190
|
+
...controlledBash ? [`BASH=${controlledBash}`] : [],
|
|
1191
|
+
`RIG_BASH_MODE=${effectiveMode}`,
|
|
1192
|
+
`RIG_POLICY_FILE=${resolve3(context.projectRoot, "rig/policy/policy.json")}`,
|
|
1193
|
+
...context.eventBus.getEventsFile() ? [`RIG_EVENTS_FILE=${context.eventBus.getEventsFile()}`] : []
|
|
1194
|
+
];
|
|
1195
|
+
const commandWithEnv = [...commandEnv, ...parts];
|
|
1196
|
+
const formatted = formatCommand(commandWithEnv);
|
|
1197
|
+
await context.emitEvent("command.received", {
|
|
1198
|
+
command: commandWithEnv,
|
|
1199
|
+
formattedCommand: formatted
|
|
1200
|
+
});
|
|
1201
|
+
const guardDecision = evaluate({
|
|
1202
|
+
projectRoot: context.projectRoot,
|
|
1203
|
+
evaluation: { type: "command", command: formatted }
|
|
1204
|
+
});
|
|
1205
|
+
const guardAction = resolveAction(effectiveMode, guardDecision.matchedRules);
|
|
1206
|
+
await context.emitEvent("policy.decision", {
|
|
1207
|
+
target: "command",
|
|
1208
|
+
command: formatted,
|
|
1209
|
+
mode: effectiveMode,
|
|
1210
|
+
allowed: guardAction !== "block",
|
|
1211
|
+
matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
|
|
1212
|
+
reasons: guardDecision.matchedRules.map((r) => r.reason)
|
|
1213
|
+
});
|
|
1214
|
+
if (guardAction === "block") {
|
|
1215
|
+
await context.emitEvent("command.failed", {
|
|
1216
|
+
command: commandWithEnv,
|
|
1217
|
+
formattedCommand: formatted,
|
|
1218
|
+
reason: "Policy denied command",
|
|
1219
|
+
matchedRuleIds: guardDecision.matchedRules.map((r) => r.id),
|
|
1220
|
+
mode: effectiveMode
|
|
1221
|
+
});
|
|
1222
|
+
throw new CliError(`Policy blocked command: ${formatted}`, 126, { hint: "Review rig/policy/policy.json, or re-run with `--policy-mode observe` to log instead of block." });
|
|
1223
|
+
}
|
|
1224
|
+
const startedAt = new Date;
|
|
1225
|
+
await context.emitEvent("command.started", {
|
|
1226
|
+
command: commandWithEnv,
|
|
1227
|
+
formattedCommand: formatted,
|
|
1228
|
+
startedAt: startedAt.toISOString(),
|
|
1229
|
+
dryRun: context.dryRun
|
|
1230
|
+
});
|
|
1231
|
+
if (context.dryRun) {
|
|
1232
|
+
const dryResult = {
|
|
1233
|
+
command: commandWithEnv,
|
|
1234
|
+
formattedCommand: formatted,
|
|
1235
|
+
exitCode: 0,
|
|
1236
|
+
durationMs: 0,
|
|
1237
|
+
startedAt: startedAt.toISOString(),
|
|
1238
|
+
finishedAt: startedAt.toISOString()
|
|
1239
|
+
};
|
|
1240
|
+
if (context.outputMode === "text") {
|
|
1241
|
+
console.log(`$ ${formatted}`);
|
|
1242
|
+
}
|
|
1243
|
+
await context.emitEvent("command.finished", {
|
|
1244
|
+
...dryResult,
|
|
1245
|
+
dryRun: true
|
|
1246
|
+
});
|
|
1247
|
+
return dryResult;
|
|
1248
|
+
}
|
|
1249
|
+
let exitCode = 0;
|
|
1250
|
+
let stdout = "";
|
|
1251
|
+
let stderr = "";
|
|
1252
|
+
if (context.outputMode === "json") {
|
|
1253
|
+
const child = Bun.spawn(commandWithEnv, {
|
|
1254
|
+
cwd: context.projectRoot,
|
|
1255
|
+
stdin: "inherit",
|
|
1256
|
+
stdout: "pipe",
|
|
1257
|
+
stderr: "pipe"
|
|
1258
|
+
});
|
|
1259
|
+
exitCode = await child.exited;
|
|
1260
|
+
stdout = await new Response(child.stdout).text();
|
|
1261
|
+
stderr = await new Response(child.stderr).text();
|
|
1262
|
+
} else {
|
|
1263
|
+
const child = Bun.spawn(commandWithEnv, {
|
|
1264
|
+
cwd: context.projectRoot,
|
|
1265
|
+
stdin: "inherit",
|
|
1266
|
+
stdout: "inherit",
|
|
1267
|
+
stderr: "inherit"
|
|
1268
|
+
});
|
|
1269
|
+
exitCode = await child.exited;
|
|
1270
|
+
}
|
|
1271
|
+
const finishedAt = new Date;
|
|
1272
|
+
const result = {
|
|
1273
|
+
command: commandWithEnv,
|
|
1274
|
+
formattedCommand: formatted,
|
|
1275
|
+
exitCode,
|
|
1276
|
+
durationMs: finishedAt.getTime() - startedAt.getTime(),
|
|
1277
|
+
startedAt: startedAt.toISOString(),
|
|
1278
|
+
finishedAt: finishedAt.toISOString(),
|
|
1279
|
+
...context.outputMode === "json" ? { stdout, stderr } : {}
|
|
1280
|
+
};
|
|
1281
|
+
if (exitCode !== 0) {
|
|
1282
|
+
await context.emitEvent("command.failed", {
|
|
1283
|
+
...result
|
|
1284
|
+
});
|
|
1285
|
+
const errorSuffix = context.outputMode === "json" && stderr ? `
|
|
1286
|
+
${stderr.trim()}` : "";
|
|
1287
|
+
throw new CliError(`Command failed (${exitCode}): ${formatted}${errorSuffix}`, exitCode);
|
|
1288
|
+
}
|
|
1289
|
+
await context.emitEvent("command.finished", {
|
|
1290
|
+
...result
|
|
1291
|
+
});
|
|
1292
|
+
return result;
|
|
296
1293
|
}
|
|
297
1294
|
|
|
298
1295
|
// packages/guard-plugin/src/plugin.ts
|
|
@@ -303,42 +1300,123 @@ var GUARD_HOOKS = [
|
|
|
303
1300
|
event: "PreToolUse",
|
|
304
1301
|
matcher: { kind: "all" },
|
|
305
1302
|
description: "Blocks dangerous commands and content patterns.",
|
|
306
|
-
handler: safetyGuardHandler
|
|
1303
|
+
handler: (input) => Promise.resolve().then(() => (init_safety_guard(), exports_safety_guard)).then((m) => m.safetyGuardHandler(input))
|
|
307
1304
|
},
|
|
308
1305
|
{
|
|
309
1306
|
id: SCOPE_GUARD_HOOK_ID,
|
|
310
1307
|
event: "PreToolUse",
|
|
311
1308
|
matcher: { kind: "all" },
|
|
312
1309
|
description: "Enforces task scope boundaries.",
|
|
313
|
-
handler: scopeGuardHandler
|
|
1310
|
+
handler: (input) => Promise.resolve().then(() => (init_scope_guard(), exports_scope_guard)).then((m) => m.scopeGuardHandler(input))
|
|
314
1311
|
},
|
|
315
1312
|
{
|
|
316
1313
|
id: IMPORT_GUARD_HOOK_ID,
|
|
317
1314
|
event: "PreToolUse",
|
|
318
1315
|
matcher: { kind: "all" },
|
|
319
1316
|
description: "Blocks cross-module internal imports.",
|
|
320
|
-
handler: importGuardHandler
|
|
1317
|
+
handler: (input) => Promise.resolve().then(() => (init_import_guard(), exports_import_guard)).then((m) => m.importGuardHandler(input))
|
|
321
1318
|
},
|
|
322
1319
|
{
|
|
323
1320
|
id: TEST_INTEGRITY_GUARD_HOOK_ID,
|
|
324
1321
|
event: "PreToolUse",
|
|
325
1322
|
matcher: { kind: "all" },
|
|
326
1323
|
description: "Prevents test tampering (.skip, .only, missing assertions).",
|
|
327
|
-
handler: testIntegrityGuardHandler
|
|
1324
|
+
handler: (input) => Promise.resolve().then(() => (init_test_integrity_guard(), exports_test_integrity_guard)).then((m) => m.testIntegrityGuardHandler(input))
|
|
328
1325
|
},
|
|
329
1326
|
{
|
|
330
1327
|
id: AUDIT_TRAIL_HOOK_ID,
|
|
331
1328
|
event: "PreToolUse",
|
|
332
1329
|
matcher: { kind: "all" },
|
|
333
1330
|
description: "Logs tool invocations to audit.jsonl.",
|
|
334
|
-
handler: auditTrailHandler
|
|
1331
|
+
handler: (input) => Promise.resolve().then(() => (init_audit_trail(), exports_audit_trail)).then((m) => m.auditTrailHandler(input))
|
|
335
1332
|
},
|
|
336
1333
|
{
|
|
337
1334
|
id: POST_EDIT_LINT_HOOK_ID,
|
|
338
1335
|
event: "PostToolUse",
|
|
339
1336
|
matcher: { kind: "all" },
|
|
340
1337
|
description: "Warns about console.log spam, TODO markers, and empty catches.",
|
|
341
|
-
handler: postEditLintHandler
|
|
1338
|
+
handler: (input) => Promise.resolve().then(() => (init_post_edit_lint(), exports_post_edit_lint)).then((m) => m.postEditLintHandler(input))
|
|
1339
|
+
}
|
|
1340
|
+
];
|
|
1341
|
+
var ToolchainSourcesCap = defineCapability(GUARD_TOOLCHAIN_SOURCES);
|
|
1342
|
+
var CliRunnerCap = defineCapability(CLI_RUNNER);
|
|
1343
|
+
var GUARD_TOOLCHAIN_SOURCE_CONTRIBUTION = {
|
|
1344
|
+
hookBinaries: [
|
|
1345
|
+
{ name: "scope-guard", source: "packages/guard-plugin/src/hooks/scope-guard.ts" },
|
|
1346
|
+
{ name: "import-guard", source: "packages/guard-plugin/src/hooks/import-guard.ts" },
|
|
1347
|
+
{ name: "safety-guard", source: "packages/guard-plugin/src/hooks/safety-guard.ts" },
|
|
1348
|
+
{ name: "test-integrity-guard", source: "packages/guard-plugin/src/hooks/test-integrity-guard.ts" },
|
|
1349
|
+
{ name: "audit-trail", source: "packages/guard-plugin/src/hooks/audit-trail.ts" },
|
|
1350
|
+
{ name: "post-edit-lint", source: "packages/guard-plugin/src/hooks/post-edit-lint.ts" }
|
|
1351
|
+
],
|
|
1352
|
+
namedSources: {
|
|
1353
|
+
"controlled-bash": "packages/guard-plugin/src/controlled-bash.ts"
|
|
1354
|
+
}
|
|
1355
|
+
};
|
|
1356
|
+
var GUARD_CLI_RUNNER = {
|
|
1357
|
+
initializeRuntime: async (options) => initializeRuntime(options),
|
|
1358
|
+
runCommand: async (context, parts) => runCommand(context, parts),
|
|
1359
|
+
ensureAgentShellBinary,
|
|
1360
|
+
resolveAgentMaterializationSourceRoot,
|
|
1361
|
+
withProjectRoot,
|
|
1362
|
+
formatCommand,
|
|
1363
|
+
loadPolicy
|
|
1364
|
+
};
|
|
1365
|
+
var GUARD_CAPABILITIES = [
|
|
1366
|
+
ToolchainSourcesCap.provide(() => GUARD_TOOLCHAIN_SOURCE_CONTRIBUTION, {
|
|
1367
|
+
title: "Guard toolchain sources",
|
|
1368
|
+
description: "Source paths for guard hook binaries and the controlled-bash wrapper, compiled by the isolation runtime toolchain."
|
|
1369
|
+
}),
|
|
1370
|
+
CliRunnerCap.provide(() => GUARD_CLI_RUNNER, {
|
|
1371
|
+
title: "CLI guarded runner",
|
|
1372
|
+
description: "Policy-guarded command runner used by CLI-surface commands through the host seam."
|
|
1373
|
+
})
|
|
1374
|
+
];
|
|
1375
|
+
async function runHookRunner(pluginName, hookId, role) {
|
|
1376
|
+
process.env.RIG_HOOK_ROLE = role;
|
|
1377
|
+
const { main } = await import("@rig/core/hook-runner");
|
|
1378
|
+
await main(["--plugin", pluginName, "--hook", hookId]);
|
|
1379
|
+
}
|
|
1380
|
+
var GUARD_SEED_ENTRYPOINTS = [
|
|
1381
|
+
{
|
|
1382
|
+
id: `${GUARD_PLUGIN_NAME}:controlled-bash`,
|
|
1383
|
+
basename: "controlled-bash",
|
|
1384
|
+
description: "Policy-guarded shell wrapper used by per-run toolchains.",
|
|
1385
|
+
run: async ({ argv, execPath }) => {
|
|
1386
|
+
const { runControlledBash } = await import("@rig/guard-plugin/controlled-bash");
|
|
1387
|
+
const { dirname: dirname2 } = await import("path");
|
|
1388
|
+
process.exit(await runControlledBash(argv.slice(2), { projectRootFallbackDir: dirname2(execPath ?? process.cwd()) }));
|
|
1389
|
+
}
|
|
1390
|
+
},
|
|
1391
|
+
{
|
|
1392
|
+
id: `${GUARD_PLUGIN_NAME}:scope-guard-entrypoint`,
|
|
1393
|
+
basename: "scope-guard",
|
|
1394
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, SCOPE_GUARD_HOOK_ID, basename2 ?? "scope-guard")
|
|
1395
|
+
},
|
|
1396
|
+
{
|
|
1397
|
+
id: `${GUARD_PLUGIN_NAME}:import-guard-entrypoint`,
|
|
1398
|
+
basename: "import-guard",
|
|
1399
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, IMPORT_GUARD_HOOK_ID, basename2 ?? "import-guard")
|
|
1400
|
+
},
|
|
1401
|
+
{
|
|
1402
|
+
id: `${GUARD_PLUGIN_NAME}:safety-guard-entrypoint`,
|
|
1403
|
+
basename: "safety-guard",
|
|
1404
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, SAFETY_GUARD_HOOK_ID, basename2 ?? "safety-guard")
|
|
1405
|
+
},
|
|
1406
|
+
{
|
|
1407
|
+
id: `${GUARD_PLUGIN_NAME}:test-integrity-guard-entrypoint`,
|
|
1408
|
+
basename: "test-integrity-guard",
|
|
1409
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, TEST_INTEGRITY_GUARD_HOOK_ID, basename2 ?? "test-integrity-guard")
|
|
1410
|
+
},
|
|
1411
|
+
{
|
|
1412
|
+
id: `${GUARD_PLUGIN_NAME}:audit-trail-entrypoint`,
|
|
1413
|
+
basename: "audit-trail",
|
|
1414
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, AUDIT_TRAIL_HOOK_ID, basename2 ?? "audit-trail")
|
|
1415
|
+
},
|
|
1416
|
+
{
|
|
1417
|
+
id: `${GUARD_PLUGIN_NAME}:post-edit-lint-entrypoint`,
|
|
1418
|
+
basename: "post-edit-lint",
|
|
1419
|
+
run: ({ basename: basename2 }) => runHookRunner(GUARD_PLUGIN_NAME, POST_EDIT_LINT_HOOK_ID, basename2 ?? "post-edit-lint")
|
|
342
1420
|
}
|
|
343
1421
|
];
|
|
344
1422
|
function createGuardPlugin() {
|
|
@@ -346,7 +1424,9 @@ function createGuardPlugin() {
|
|
|
346
1424
|
name: GUARD_PLUGIN_NAME,
|
|
347
1425
|
version: "0.0.0-alpha.1",
|
|
348
1426
|
contributes: {
|
|
349
|
-
hooks: GUARD_HOOKS
|
|
1427
|
+
hooks: GUARD_HOOKS,
|
|
1428
|
+
capabilities: GUARD_CAPABILITIES,
|
|
1429
|
+
seedEntrypoints: GUARD_SEED_ENTRYPOINTS
|
|
350
1430
|
}
|
|
351
1431
|
});
|
|
352
1432
|
}
|