@orc-brain/core 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (126) hide show
  1. package/LICENSE +21 -0
  2. package/dist/autoLoop.d.ts +70 -0
  3. package/dist/autoLoop.d.ts.map +1 -0
  4. package/dist/autoLoop.js +178 -0
  5. package/dist/autoLoop.js.map +1 -0
  6. package/dist/backpressure.d.ts +38 -0
  7. package/dist/backpressure.d.ts.map +1 -0
  8. package/dist/backpressure.js +103 -0
  9. package/dist/backpressure.js.map +1 -0
  10. package/dist/budgetTracker.d.ts +53 -0
  11. package/dist/budgetTracker.d.ts.map +1 -0
  12. package/dist/budgetTracker.js +93 -0
  13. package/dist/budgetTracker.js.map +1 -0
  14. package/dist/config.d.ts +9 -0
  15. package/dist/config.d.ts.map +1 -0
  16. package/dist/config.js +107 -0
  17. package/dist/config.js.map +1 -0
  18. package/dist/escalation.d.ts +43 -0
  19. package/dist/escalation.d.ts.map +1 -0
  20. package/dist/escalation.js +81 -0
  21. package/dist/escalation.js.map +1 -0
  22. package/dist/eventBus.d.ts +30 -0
  23. package/dist/eventBus.d.ts.map +1 -0
  24. package/dist/eventBus.js +42 -0
  25. package/dist/eventBus.js.map +1 -0
  26. package/dist/goalJudge.d.ts +92 -0
  27. package/dist/goalJudge.d.ts.map +1 -0
  28. package/dist/goalJudge.js +254 -0
  29. package/dist/goalJudge.js.map +1 -0
  30. package/dist/index.d.ts +30 -0
  31. package/dist/index.d.ts.map +1 -0
  32. package/dist/index.js +30 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/modelRouter.d.ts +40 -0
  35. package/dist/modelRouter.d.ts.map +1 -0
  36. package/dist/modelRouter.js +114 -0
  37. package/dist/modelRouter.js.map +1 -0
  38. package/dist/orchestrator.d.ts +242 -0
  39. package/dist/orchestrator.d.ts.map +1 -0
  40. package/dist/orchestrator.js +975 -0
  41. package/dist/orchestrator.js.map +1 -0
  42. package/dist/pacing.d.ts +34 -0
  43. package/dist/pacing.d.ts.map +1 -0
  44. package/dist/pacing.js +49 -0
  45. package/dist/pacing.js.map +1 -0
  46. package/dist/planValidation.d.ts +24 -0
  47. package/dist/planValidation.d.ts.map +1 -0
  48. package/dist/planValidation.js +134 -0
  49. package/dist/planValidation.js.map +1 -0
  50. package/dist/planner.d.ts +70 -0
  51. package/dist/planner.d.ts.map +1 -0
  52. package/dist/planner.js +210 -0
  53. package/dist/planner.js.map +1 -0
  54. package/dist/plugins/host.d.ts +39 -0
  55. package/dist/plugins/host.d.ts.map +1 -0
  56. package/dist/plugins/host.js +109 -0
  57. package/dist/plugins/host.js.map +1 -0
  58. package/dist/plugins/registry.d.ts +71 -0
  59. package/dist/plugins/registry.d.ts.map +1 -0
  60. package/dist/plugins/registry.js +269 -0
  61. package/dist/plugins/registry.js.map +1 -0
  62. package/dist/plugins/secrets.d.ts +30 -0
  63. package/dist/plugins/secrets.d.ts.map +1 -0
  64. package/dist/plugins/secrets.js +99 -0
  65. package/dist/plugins/secrets.js.map +1 -0
  66. package/dist/preflight.d.ts +38 -0
  67. package/dist/preflight.d.ts.map +1 -0
  68. package/dist/preflight.js +108 -0
  69. package/dist/preflight.js.map +1 -0
  70. package/dist/reporting.d.ts +41 -0
  71. package/dist/reporting.d.ts.map +1 -0
  72. package/dist/reporting.js +229 -0
  73. package/dist/reporting.js.map +1 -0
  74. package/dist/safety/denyRules.d.ts +57 -0
  75. package/dist/safety/denyRules.d.ts.map +1 -0
  76. package/dist/safety/denyRules.js +498 -0
  77. package/dist/safety/denyRules.js.map +1 -0
  78. package/dist/safety/envClassifier.d.ts +45 -0
  79. package/dist/safety/envClassifier.d.ts.map +1 -0
  80. package/dist/safety/envClassifier.js +99 -0
  81. package/dist/safety/envClassifier.js.map +1 -0
  82. package/dist/safety/index.d.ts +83 -0
  83. package/dist/safety/index.d.ts.map +1 -0
  84. package/dist/safety/index.js +184 -0
  85. package/dist/safety/index.js.map +1 -0
  86. package/dist/safety/limitSignals.d.ts +36 -0
  87. package/dist/safety/limitSignals.d.ts.map +1 -0
  88. package/dist/safety/limitSignals.js +79 -0
  89. package/dist/safety/limitSignals.js.map +1 -0
  90. package/dist/safety/paths.d.ts +32 -0
  91. package/dist/safety/paths.d.ts.map +1 -0
  92. package/dist/safety/paths.js +102 -0
  93. package/dist/safety/paths.js.map +1 -0
  94. package/dist/safety/redact.d.ts +17 -0
  95. package/dist/safety/redact.d.ts.map +1 -0
  96. package/dist/safety/redact.js +85 -0
  97. package/dist/safety/redact.js.map +1 -0
  98. package/dist/spawnEnv.d.ts +18 -0
  99. package/dist/spawnEnv.d.ts.map +1 -0
  100. package/dist/spawnEnv.js +43 -0
  101. package/dist/spawnEnv.js.map +1 -0
  102. package/dist/store/auditLog.d.ts +28 -0
  103. package/dist/store/auditLog.d.ts.map +1 -0
  104. package/dist/store/auditLog.js +48 -0
  105. package/dist/store/auditLog.js.map +1 -0
  106. package/dist/store/index.d.ts +140 -0
  107. package/dist/store/index.d.ts.map +1 -0
  108. package/dist/store/index.js +648 -0
  109. package/dist/store/index.js.map +1 -0
  110. package/dist/store/schema.d.ts +17 -0
  111. package/dist/store/schema.d.ts.map +1 -0
  112. package/dist/store/schema.js +197 -0
  113. package/dist/store/schema.js.map +1 -0
  114. package/dist/system.d.ts +69 -0
  115. package/dist/system.d.ts.map +1 -0
  116. package/dist/system.js +135 -0
  117. package/dist/system.js.map +1 -0
  118. package/dist/workerManager.d.ts +80 -0
  119. package/dist/workerManager.d.ts.map +1 -0
  120. package/dist/workerManager.js +280 -0
  121. package/dist/workerManager.js.map +1 -0
  122. package/dist/worktrees.d.ts +74 -0
  123. package/dist/worktrees.d.ts.map +1 -0
  124. package/dist/worktrees.js +210 -0
  125. package/dist/worktrees.js.map +1 -0
  126. package/package.json +48 -0
@@ -0,0 +1,498 @@
1
+ /**
2
+ * Destructive-command interception (§8.2). Bash input is parsed with a real
3
+ * shell tokenizer (not regex-only) to unwrap `sh -c`, `xargs`, `env`, `sudo`,
4
+ * command substitution, and `&&` chains, then each resolved command is matched
5
+ * against rule classes. A regex backstop covers unparseable input and
6
+ * substitution-hidden binaries; unparseable input denies in production scopes.
7
+ */
8
+ import { parse as shellParse } from "shell-quote";
9
+ import { isProductionLike } from "./envClassifier.js";
10
+ import { isCredentialPath, isDotenvPath, isPathAllowed, toAbsolute, } from "./paths.js";
11
+ const SEVERITY = {
12
+ deny: 3,
13
+ require_approval: 2,
14
+ allow_with_audit: 1,
15
+ allow: 0,
16
+ };
17
+ const ALLOW = { verdict: "allow", match: null };
18
+ /** Maps a matched rule class to a verdict given the scope environment (§8.2). */
19
+ function verdictFor(cls, ctx) {
20
+ // Production-flagged scopes always deny. Not configurable off (§2, §8).
21
+ if (isProductionLike(ctx.environment))
22
+ return "deny";
23
+ const posture = ctx.dev_posture[cls] ?? "require_approval";
24
+ if (posture === "allow_with_audit")
25
+ return "allow_with_audit";
26
+ if (posture === "deny")
27
+ return "deny";
28
+ return "require_approval";
29
+ }
30
+ /** Wraps a rule match into an environment-aware decision. */
31
+ function decide(match, ctx) {
32
+ return { verdict: verdictFor(match.rule_class, ctx), match };
33
+ }
34
+ /** Keeps the most-severe of two decisions. */
35
+ function worse(a, b) {
36
+ return SEVERITY[b.verdict] > SEVERITY[a.verdict] ? b : a;
37
+ }
38
+ const CONTROL_OPS = new Set([";", "&&", "||", "|", "&", "\n"]);
39
+ const REDIR_OPS = new Set([">", ">>", "<", ">&", "&>", "<<", "<<<"]);
40
+ /** Splits a shell-quote token stream into individual commands. */
41
+ function splitCommands(tokens) {
42
+ const cmds = [];
43
+ let cur = { args: [], redirects: [] };
44
+ let expectRedirTarget = false;
45
+ const flush = (op) => {
46
+ if (cur.args.length || cur.redirects.length) {
47
+ cur.nextOp = op;
48
+ cmds.push(cur);
49
+ }
50
+ cur = { args: [], redirects: [] };
51
+ };
52
+ for (const tok of tokens) {
53
+ if (typeof tok === "string") {
54
+ if (expectRedirTarget) {
55
+ cur.redirects.push(tok);
56
+ expectRedirTarget = false;
57
+ }
58
+ else {
59
+ cur.args.push(tok);
60
+ }
61
+ continue;
62
+ }
63
+ // Object token: operator or glob.
64
+ if ("pattern" in tok && typeof tok.pattern === "string") {
65
+ if (expectRedirTarget) {
66
+ cur.redirects.push(tok.pattern);
67
+ expectRedirTarget = false;
68
+ }
69
+ else {
70
+ cur.args.push(tok.pattern);
71
+ }
72
+ continue;
73
+ }
74
+ const op = "op" in tok ? tok.op : undefined;
75
+ if (!op)
76
+ continue;
77
+ if (CONTROL_OPS.has(op)) {
78
+ flush(op);
79
+ }
80
+ else if (REDIR_OPS.has(op)) {
81
+ expectRedirTarget = true;
82
+ }
83
+ else if (op === "(" || op === ")") {
84
+ // Subshell boundary — treat inner commands as their own segments.
85
+ flush();
86
+ }
87
+ // `$`, other ops ignored.
88
+ }
89
+ flush();
90
+ return cmds;
91
+ }
92
+ const WRAPPERS = new Set([
93
+ "sudo",
94
+ "nice",
95
+ "nohup",
96
+ "time",
97
+ "command",
98
+ "builtin",
99
+ "doas",
100
+ ]);
101
+ const SHELLS = new Set(["sh", "bash", "zsh", "dash", "ksh", "fish"]);
102
+ /**
103
+ * Resolves wrapper commands to the real command they invoke, returning the
104
+ * inner shell scripts that must be parsed recursively.
105
+ */
106
+ function unwrap(cmd) {
107
+ let args = [...cmd.args];
108
+ const nestedScripts = [];
109
+ // Strip `env VAR=val ...`.
110
+ if (args[0] === "env") {
111
+ args = args.slice(1);
112
+ while (args.length && /^[A-Za-z_][A-Za-z0-9_]*=/.test(args[0])) {
113
+ args = args.slice(1);
114
+ }
115
+ }
116
+ // Strip wrappers and any options they carry, so `nice -n 10 rm -rf` and
117
+ // `sudo -u root rm -rf` resolve to the real command (§13.1 bypass class).
118
+ const OPT_WITH_VALUE = new Set([
119
+ "-n",
120
+ "-u",
121
+ "-g",
122
+ "-p",
123
+ "--user",
124
+ "--group",
125
+ "--adjustment",
126
+ ]);
127
+ while (args.length && WRAPPERS.has(args[0])) {
128
+ args = args.slice(1);
129
+ while (args.length && args[0].startsWith("-")) {
130
+ const opt = args[0];
131
+ args = args.slice(1);
132
+ if (OPT_WITH_VALUE.has(opt) && args.length && !args[0].startsWith("-")) {
133
+ args = args.slice(1); // consume the option's value (e.g. `-n 10`)
134
+ }
135
+ }
136
+ }
137
+ // `timeout <dur> cmd...`
138
+ if (args[0] === "timeout" && args.length > 2)
139
+ args = args.slice(2);
140
+ // `xargs [opts] cmd ...` — the real command follows the options.
141
+ if (args[0] === "xargs") {
142
+ let i = 1;
143
+ while (i < args.length && args[i].startsWith("-"))
144
+ i++;
145
+ args = args.slice(i);
146
+ }
147
+ // `sh -c "<script>"` / `bash -c ...`
148
+ if (args.length >= 3 && SHELLS.has(args[0]) && args.includes("-c")) {
149
+ const ci = args.indexOf("-c");
150
+ const script = args[ci + 1];
151
+ if (typeof script === "string")
152
+ nestedScripts.push(script);
153
+ }
154
+ return { resolved: { ...cmd, args }, nestedScripts };
155
+ }
156
+ // ---------------------------------------------------------------------------
157
+ // Rule matchers
158
+ // ---------------------------------------------------------------------------
159
+ /** Collects short-flag characters from an argv (e.g. `-rf` → "rf"). */
160
+ function collectFlags(args) {
161
+ return args
162
+ .filter((a) => /^-[^-]/.test(a))
163
+ .map((a) => a.slice(1))
164
+ .join("");
165
+ }
166
+ /** Detects destructive SQL inside a string (DROP/TRUNCATE, unqualified DML). */
167
+ export function matchSql(sql) {
168
+ const s = sql.replace(/\s+/g, " ").trim();
169
+ if (/\bDROP\s+(TABLE|DATABASE|SCHEMA|INDEX)\b/i.test(s)) {
170
+ return {
171
+ rule_id: "DB-1",
172
+ rule_class: "database",
173
+ reason: "DROP statement",
174
+ };
175
+ }
176
+ if (/\bTRUNCATE\b/i.test(s)) {
177
+ return {
178
+ rule_id: "DB-2",
179
+ rule_class: "database",
180
+ reason: "TRUNCATE statement",
181
+ };
182
+ }
183
+ if (/\b(DELETE\s+FROM|UPDATE)\b/i.test(s) && !/\bWHERE\b/i.test(s)) {
184
+ return {
185
+ rule_id: "DB-3",
186
+ rule_class: "database",
187
+ reason: "DELETE/UPDATE without WHERE",
188
+ };
189
+ }
190
+ return null;
191
+ }
192
+ const DB_CLIENTS = new Set([
193
+ "psql",
194
+ "mysql",
195
+ "mariadb",
196
+ "sqlite3",
197
+ "mongo",
198
+ "mongosh",
199
+ "redis-cli",
200
+ "cockroach",
201
+ "clickhouse-client",
202
+ ]);
203
+ const PROTECTED_BRANCHES = /(^|[\s,])(main|master|prod|production|release\/)/i;
204
+ /** Matches a single resolved command against every rule class (§8.2). */
205
+ export function matchCommand(cmd, ctx) {
206
+ const [name, ...args] = cmd.args;
207
+ if (!name)
208
+ return null;
209
+ const joined = cmd.args.join(" ");
210
+ const flags = collectFlags(args);
211
+ // --- filesystem destruction ---
212
+ if (name === "rm") {
213
+ const recursive = /r/.test(flags) || args.includes("--recursive");
214
+ if (recursive) {
215
+ return {
216
+ rule_id: "FS-1",
217
+ rule_class: "filesystem",
218
+ reason: "recursive rm",
219
+ };
220
+ }
221
+ }
222
+ if (name === "find" && args.includes("-delete")) {
223
+ return {
224
+ rule_id: "FS-2",
225
+ rule_class: "filesystem",
226
+ reason: "find -delete",
227
+ };
228
+ }
229
+ if (name === "shred") {
230
+ return { rule_id: "FS-3", rule_class: "filesystem", reason: "shred" };
231
+ }
232
+ if (name === "mkfs" || name.startsWith("mkfs.")) {
233
+ return { rule_id: "FS-4", rule_class: "filesystem", reason: "mkfs" };
234
+ }
235
+ if (name === "dd" && args.some((a) => /^of=\/dev\//.test(a))) {
236
+ return {
237
+ rule_id: "FS-5",
238
+ rule_class: "filesystem",
239
+ reason: "dd to device",
240
+ };
241
+ }
242
+ if (cmd.redirects.some((r) => /^\/dev\//.test(r) && !/^\/dev\/(null|stdout|stderr)$/.test(r))) {
243
+ return {
244
+ rule_id: "FS-5",
245
+ rule_class: "filesystem",
246
+ reason: "write to device",
247
+ };
248
+ }
249
+ // --- VCS destruction ---
250
+ if (name === "git") {
251
+ const sub = args[0];
252
+ if (sub === "push" &&
253
+ args.some((a) => a === "--force" || a === "-f" || a === "--force-with-lease")) {
254
+ return {
255
+ rule_id: "VCS-1",
256
+ rule_class: "vcs",
257
+ reason: "git push --force",
258
+ };
259
+ }
260
+ if (sub === "push" && args.some((a) => a.startsWith("+"))) {
261
+ return {
262
+ rule_id: "VCS-1",
263
+ rule_class: "vcs",
264
+ reason: "git push +ref (force)",
265
+ };
266
+ }
267
+ if (sub === "push" && args.includes("--delete")) {
268
+ return {
269
+ rule_id: "VCS-2",
270
+ rule_class: "vcs",
271
+ reason: "git push --delete",
272
+ };
273
+ }
274
+ if (sub === "reset" && args.includes("--hard")) {
275
+ return {
276
+ rule_id: "VCS-3",
277
+ rule_class: "vcs",
278
+ reason: "git reset --hard",
279
+ };
280
+ }
281
+ if (sub === "clean" && args.some((a) => /^-\w*f/.test(a))) {
282
+ return { rule_id: "VCS-4", rule_class: "vcs", reason: "git clean -f" };
283
+ }
284
+ if (sub === "branch" &&
285
+ args.some((a) => /^-\w*D/.test(a)) &&
286
+ PROTECTED_BRANCHES.test(joined)) {
287
+ return {
288
+ rule_id: "VCS-5",
289
+ rule_class: "vcs",
290
+ reason: "delete protected branch",
291
+ };
292
+ }
293
+ if (sub === "filter-repo" || sub === "filter-branch") {
294
+ return { rule_id: "VCS-6", rule_class: "vcs", reason: "history rewrite" };
295
+ }
296
+ }
297
+ // --- database destruction ---
298
+ if (DB_CLIENTS.has(name)) {
299
+ const sqlMatch = matchSql(joined);
300
+ if (sqlMatch)
301
+ return sqlMatch;
302
+ }
303
+ // --- infra teardown ---
304
+ if (name === "terraform" && (args[0] === "destroy" || args[0] === "apply")) {
305
+ return {
306
+ rule_id: "INF-1",
307
+ rule_class: "infra",
308
+ reason: `terraform ${args[0]}`,
309
+ };
310
+ }
311
+ if (name === "kubectl" && args[0] === "delete") {
312
+ return { rule_id: "INF-2", rule_class: "infra", reason: "kubectl delete" };
313
+ }
314
+ if (name === "docker" && args[0] === "system" && args[1] === "prune") {
315
+ return {
316
+ rule_id: "INF-3",
317
+ rule_class: "infra",
318
+ reason: "docker system prune",
319
+ };
320
+ }
321
+ if (name === "helm" && args[0] === "uninstall") {
322
+ return { rule_id: "INF-5", rule_class: "infra", reason: "helm uninstall" };
323
+ }
324
+ if ((name === "aws" || name === "gcloud" || name === "az") &&
325
+ args.some((a) => /^(delete|rm|destroy|terminate|remove|drop)$/.test(a))) {
326
+ return {
327
+ rule_id: "INF-4",
328
+ rule_class: "infra",
329
+ reason: `${name} mutating verb`,
330
+ };
331
+ }
332
+ // --- publish / deploy ---
333
+ if ((name === "npm" || name === "pnpm" || name === "yarn") &&
334
+ args.includes("publish")) {
335
+ return {
336
+ rule_id: "PUB-1",
337
+ rule_class: "publish",
338
+ reason: "package publish",
339
+ };
340
+ }
341
+ if (name === "cargo" && args.includes("publish")) {
342
+ return { rule_id: "PUB-1", rule_class: "publish", reason: "cargo publish" };
343
+ }
344
+ if (name === "docker" && args[0] === "push") {
345
+ return { rule_id: "PUB-2", rule_class: "publish", reason: "docker push" };
346
+ }
347
+ if (name === "gh" && args[0] === "release") {
348
+ return { rule_id: "PUB-3", rule_class: "publish", reason: "gh release" };
349
+ }
350
+ if (name === "vercel" && args.includes("--prod")) {
351
+ return { rule_id: "PUB-4", rule_class: "publish", reason: "vercel --prod" };
352
+ }
353
+ if ((name === "flyctl" || name === "fly") && args[0] === "deploy") {
354
+ return { rule_id: "PUB-5", rule_class: "publish", reason: "flyctl deploy" };
355
+ }
356
+ // --- credential access ---
357
+ if ((name === "cat" ||
358
+ name === "less" ||
359
+ name === "head" ||
360
+ name === "tail" ||
361
+ name === "cp") &&
362
+ args.some((a) => isCredentialPath(toAbsolute(a, ctx.cwd)))) {
363
+ return {
364
+ rule_id: "CRED-1",
365
+ rule_class: "credential",
366
+ reason: "reads credential file",
367
+ };
368
+ }
369
+ if (name === "security" || name === "keychain") {
370
+ return {
371
+ rule_id: "CRED-2",
372
+ rule_class: "credential",
373
+ reason: "keychain access",
374
+ };
375
+ }
376
+ return null;
377
+ }
378
+ // Backstop patterns for substitution-hidden or unparseable danger (§8.2, §13.1).
379
+ const BACKSTOP_PATTERNS = [
380
+ {
381
+ re: /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/,
382
+ match: { rule_id: "FS-7", rule_class: "filesystem", reason: "fork bomb" },
383
+ },
384
+ {
385
+ re: /\|\s*(sudo\s+)?(sh|bash|zsh)\b/,
386
+ match: { rule_id: "NET-1", rule_class: "network", reason: "pipe to shell" },
387
+ },
388
+ ];
389
+ /**
390
+ * Evaluates a Bash command string (§8.2). Recursively unwraps shells, xargs,
391
+ * env, and command substitution; on parse failure denies in production scopes.
392
+ */
393
+ export function evaluateBash(command, ctx) {
394
+ let decision = ALLOW;
395
+ let tokens;
396
+ try {
397
+ tokens = shellParse(command);
398
+ }
399
+ catch {
400
+ // Unparseable ⇒ deny in production scopes; audit elsewhere (§8.2).
401
+ return isProductionLike(ctx.environment)
402
+ ? {
403
+ verdict: "deny",
404
+ match: {
405
+ rule_id: "PARSE-1",
406
+ rule_class: "filesystem",
407
+ reason: "unparseable command",
408
+ },
409
+ }
410
+ : { verdict: "allow_with_audit", match: null };
411
+ }
412
+ const commands = splitCommands(tokens);
413
+ // curl|wget … | sh network-exfil detection across the pipeline (§8.2).
414
+ for (let i = 0; i < commands.length - 1; i++) {
415
+ const c = commands[i];
416
+ const nxt = commands[i + 1];
417
+ const head = c.args[0];
418
+ const pipedTo = nxt.args[0];
419
+ if (c.nextOp === "|" &&
420
+ (head === "curl" || head === "wget") &&
421
+ (pipedTo === "sh" || pipedTo === "bash" || pipedTo === "zsh")) {
422
+ decision = worse(decision, decide({
423
+ rule_id: "NET-1",
424
+ rule_class: "network",
425
+ reason: "curl|wget → shell",
426
+ }, ctx));
427
+ }
428
+ }
429
+ for (const cmd of commands) {
430
+ const { resolved, nestedScripts } = unwrap(cmd);
431
+ const m = matchCommand(resolved, ctx);
432
+ if (m)
433
+ decision = worse(decision, decide(m, ctx));
434
+ for (const script of nestedScripts) {
435
+ decision = worse(decision, evaluateBash(script, ctx));
436
+ }
437
+ }
438
+ // Command-substitution / backstop regex sweep (§13.1). A binary hidden inside
439
+ // `$(which rm) -rf .` is caught here even when the token walk cannot bind it.
440
+ const hasSubstitution = /\$\(|`/.test(command);
441
+ for (const { re, match } of BACKSTOP_PATTERNS) {
442
+ if (re.test(command))
443
+ decision = worse(decision, decide(match, ctx));
444
+ }
445
+ if (hasSubstitution &&
446
+ isProductionLike(ctx.environment) &&
447
+ /\brm\b|\bdd\b|\bmkfs\b|\bshred\b/.test(command)) {
448
+ decision = worse(decision, decide({
449
+ rule_id: "SUBST-1",
450
+ rule_class: "filesystem",
451
+ reason: "destructive binary inside command substitution",
452
+ }, ctx));
453
+ }
454
+ return decision;
455
+ }
456
+ /**
457
+ * Evaluates a Write/Edit file target against the scope allowlist (§8.2, §8.3).
458
+ * A write outside the allowlist (or inside the denylist) is filesystem-class.
459
+ */
460
+ export function evaluatePathWrite(filePath, ctx) {
461
+ const abs = toAbsolute(filePath, ctx.cwd);
462
+ if (isCredentialPath(abs)) {
463
+ return decide({
464
+ rule_id: "CRED-1",
465
+ rule_class: "credential",
466
+ reason: "write to credential path",
467
+ }, ctx);
468
+ }
469
+ if (!isPathAllowed(abs, ctx.path_allowlist, ctx.path_denylist, ctx.cwd)) {
470
+ return decide({
471
+ rule_id: "FS-6",
472
+ rule_class: "filesystem",
473
+ reason: "write outside path allowlist",
474
+ }, ctx);
475
+ }
476
+ return ALLOW;
477
+ }
478
+ /** Evaluates a Read file target — blocks credential/dotenv reads (§8.2). */
479
+ export function evaluatePathRead(filePath, ctx) {
480
+ const abs = toAbsolute(filePath, ctx.cwd);
481
+ if (isCredentialPath(abs)) {
482
+ return decide({
483
+ rule_id: "CRED-1",
484
+ rule_class: "credential",
485
+ reason: "read credential file",
486
+ }, ctx);
487
+ }
488
+ if (isDotenvPath(abs) &&
489
+ !isPathAllowed(abs, ctx.path_allowlist, ctx.path_denylist, ctx.cwd)) {
490
+ return decide({
491
+ rule_id: "CRED-3",
492
+ rule_class: "credential",
493
+ reason: ".env read outside allowlist",
494
+ }, ctx);
495
+ }
496
+ return ALLOW;
497
+ }
498
+ //# sourceMappingURL=denyRules.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"denyRules.js","sourceRoot":"","sources":["../../src/safety/denyRules.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Environment classifier (§8.1). A scope's `environment` is set explicitly in
3
+ * the plan, but the classifier re-evaluates continuously and can only *raise*
4
+ * severity, never lower it. The ambiguity rule is absolute: `unknown` is
5
+ * treated as `production`.
6
+ */
7
+ import type { Environment, SafetyConfig } from "@orc-brain/shared";
8
+ /** True when this environment must be treated as production for enforcement. */
9
+ export declare function isProductionLike(env: Environment): boolean;
10
+ /** Returns whichever environment carries the higher (never-lower) severity. */
11
+ export declare function raise(a: Environment, b: Environment): Environment;
12
+ /** True when `branch` matches any configured production branch pattern (§8.1). */
13
+ export declare function isProductionBranch(branch: string, prodBranches: string[]): boolean;
14
+ /** Extracts a bare host from a URL, connection string, or host:port token. */
15
+ export declare function extractHost(raw: string): string | null;
16
+ /**
17
+ * Classifies a single host reference (§8.1). Local/RFC-1918 hosts are
18
+ * `development`; anything explicitly listed as a prod indicator is
19
+ * `production`; everything else is `unknown` (⇒ production).
20
+ */
21
+ export declare function classifyHost(host: string, prodIndicators: string[]): Environment;
22
+ /** Signals fed to {@link classifyEnvironment}. */
23
+ export interface EnvSignals {
24
+ /** The scope's declared environment (operator-reviewed). */
25
+ declared: Environment;
26
+ /** Current git branch of the scope's cwd, if known. */
27
+ branch?: string;
28
+ /** Host/URL/connection-string tokens seen in tool input or path allowlist. */
29
+ hosts?: string[];
30
+ /** Operator explicitly forced production. */
31
+ forcedProduction?: boolean;
32
+ }
33
+ /** Result of a classification pass. */
34
+ export interface EnvClassification {
35
+ environment: Environment;
36
+ /** Human-readable reasons the classification landed where it did. */
37
+ signals: string[];
38
+ }
39
+ /**
40
+ * Continuous re-classification (§8.1). Starts from the declared environment and
41
+ * only ever raises. Returns the effective environment plus the signals that
42
+ * produced it, for the audit log and the UI's "why prod?" explanation.
43
+ */
44
+ export declare function classifyEnvironment(input: EnvSignals, config: SafetyConfig): EnvClassification;
45
+ //# sourceMappingURL=envClassifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envClassifier.d.ts","sourceRoot":"","sources":["../../src/safety/envClassifier.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AAUnE,gFAAgF;AAChF,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,WAAW,GAAG,OAAO,CAE1D;AAED,+EAA+E;AAC/E,wBAAgB,KAAK,CAAC,CAAC,EAAE,WAAW,EAAE,CAAC,EAAE,WAAW,GAAG,WAAW,CAOjE;AAUD,kFAAkF;AAClF,wBAAgB,kBAAkB,CAChC,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,MAAM,EAAE,GACrB,OAAO,CAET;AAOD,8EAA8E;AAC9E,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAYtD;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAC1B,IAAI,EAAE,MAAM,EACZ,cAAc,EAAE,MAAM,EAAE,GACvB,WAAW,CAOb;AAED,kDAAkD;AAClD,MAAM,WAAW,UAAU;IACzB,4DAA4D;IAC5D,QAAQ,EAAE,WAAW,CAAC;IACtB,uDAAuD;IACvD,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,8EAA8E;IAC9E,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,6CAA6C;IAC7C,gBAAgB,CAAC,EAAE,OAAO,CAAC;CAC5B;AAED,uCAAuC;AACvC,MAAM,WAAW,iBAAiB;IAChC,WAAW,EAAE,WAAW,CAAC;IACzB,qEAAqE;IACrE,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,KAAK,EAAE,UAAU,EACjB,MAAM,EAAE,YAAY,GACnB,iBAAiB,CAyBnB"}
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Environment classifier (§8.1). A scope's `environment` is set explicitly in
3
+ * the plan, but the classifier re-evaluates continuously and can only *raise*
4
+ * severity, never lower it. The ambiguity rule is absolute: `unknown` is
5
+ * treated as `production`.
6
+ */
7
+ /** Severity ordering. `unknown` sits at production level (§8.1). */
8
+ const SEVERITY = {
9
+ development: 0,
10
+ staging: 1,
11
+ unknown: 2,
12
+ production: 2,
13
+ };
14
+ /** True when this environment must be treated as production for enforcement. */
15
+ export function isProductionLike(env) {
16
+ return SEVERITY[env] >= SEVERITY.production;
17
+ }
18
+ /** Returns whichever environment carries the higher (never-lower) severity. */
19
+ export function raise(a, b) {
20
+ if (SEVERITY[b] > SEVERITY[a])
21
+ return b;
22
+ // Prefer the more specific label when severities tie (unknown vs production).
23
+ if (SEVERITY[a] === SEVERITY[b] && a === "unknown" && b === "production") {
24
+ return "production";
25
+ }
26
+ return a;
27
+ }
28
+ /** Compiles a branch glob like `release/*` into an anchored RegExp. */
29
+ function branchGlobToRegExp(glob) {
30
+ const escaped = glob
31
+ .replace(/[.+^${}()|[\]\\]/g, "\\$&")
32
+ .replace(/\*/g, ".*");
33
+ return new RegExp(`^${escaped}$`);
34
+ }
35
+ /** True when `branch` matches any configured production branch pattern (§8.1). */
36
+ export function isProductionBranch(branch, prodBranches) {
37
+ return prodBranches.some((p) => branchGlobToRegExp(p).test(branch));
38
+ }
39
+ const LOCAL_HOST = /^(localhost|127\.0\.0\.1|::1|.*\.local)$/i;
40
+ // RFC-1918 private ranges + link-local — treated as non-production.
41
+ const PRIVATE_HOST = /^(10\.|127\.|192\.168\.|172\.(1[6-9]|2\d|3[01])\.|169\.254\.|fe80:|fc00:|fd00:)/i;
42
+ /** Extracts a bare host from a URL, connection string, or host:port token. */
43
+ export function extractHost(raw) {
44
+ const trimmed = raw.trim();
45
+ if (!trimmed)
46
+ return null;
47
+ // scheme://[user:pass@]host[:port]/...
48
+ const urlMatch = trimmed.match(/^[a-z][a-z0-9+.-]*:\/\/(?:[^/@]*@)?([^/:?#]+)/i);
49
+ if (urlMatch?.[1])
50
+ return urlMatch[1].toLowerCase();
51
+ // host:port or bare host
52
+ const hostMatch = trimmed.match(/^([a-z0-9._-]+)(?::\d+)?$/i);
53
+ if (hostMatch?.[1])
54
+ return hostMatch[1].toLowerCase();
55
+ return null;
56
+ }
57
+ /**
58
+ * Classifies a single host reference (§8.1). Local/RFC-1918 hosts are
59
+ * `development`; anything explicitly listed as a prod indicator is
60
+ * `production`; everything else is `unknown` (⇒ production).
61
+ */
62
+ export function classifyHost(host, prodIndicators) {
63
+ const h = host.toLowerCase();
64
+ if (prodIndicators.some((ind) => h.includes(ind.toLowerCase()))) {
65
+ return "production";
66
+ }
67
+ if (LOCAL_HOST.test(h) || PRIVATE_HOST.test(h))
68
+ return "development";
69
+ return "unknown";
70
+ }
71
+ /**
72
+ * Continuous re-classification (§8.1). Starts from the declared environment and
73
+ * only ever raises. Returns the effective environment plus the signals that
74
+ * produced it, for the audit log and the UI's "why prod?" explanation.
75
+ */
76
+ export function classifyEnvironment(input, config) {
77
+ let env = input.declared;
78
+ const signals = [`declared=${input.declared}`];
79
+ if (input.forcedProduction) {
80
+ env = raise(env, "production");
81
+ signals.push("operator forced production");
82
+ }
83
+ if (input.branch && isProductionBranch(input.branch, config.prod_branches)) {
84
+ env = raise(env, "production");
85
+ signals.push(`branch '${input.branch}' is a protected/production branch`);
86
+ }
87
+ for (const raw of input.hosts ?? []) {
88
+ const host = extractHost(raw);
89
+ if (!host)
90
+ continue;
91
+ const hostEnv = classifyHost(host, config.prod_host_indicators);
92
+ if (hostEnv !== "development") {
93
+ env = raise(env, hostEnv);
94
+ signals.push(`host '${host}' classified ${hostEnv}`);
95
+ }
96
+ }
97
+ return { environment: env, signals };
98
+ }
99
+ //# sourceMappingURL=envClassifier.js.map