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