@seanmozeik/tripwire 0.4.0 → 0.5.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.
@@ -40,6 +40,13 @@ const SPECS: readonly Spec[] = [
40
40
  message: 'Fork bomb pattern detected. Refuse.',
41
41
  match: (_seg, raw) => /:\(\)\s*\{\s*:\s*\|\s*:\s*&\s*\}\s*;/.test(raw),
42
42
  },
43
+ {
44
+ rule: 'source-script',
45
+ action: 'deny',
46
+ message:
47
+ '`source` / `.` executes a file in the current shell. Refuse arbitrary sourced scripts.',
48
+ match: (seg) => (seg.head === 'source' || seg.head === '.') && seg.tokens.length > 1,
49
+ },
43
50
  {
44
51
  rule: 'dd-raw-device',
45
52
  action: 'deny',
@@ -329,18 +336,52 @@ const SPECS: readonly Spec[] = [
329
336
  },
330
337
  ];
331
338
 
339
+ // Rules in this set ignore `# tripwire-allow` on the command line. They
340
+ // Are catastrophic / irreversible operations and hard policy rules that
341
+ // Have no legitimate prompt-line override. If the user genuinely needs
342
+ // One of these to run, they should do it in a terminal themselves.
343
+ const UNBYPASSABLE_RULES: ReadonlySet<string> = new Set([
344
+ // Catastrophic / irreversible
345
+ 'rm-rf-root',
346
+ 'rm-rf-home',
347
+ 'fork-bomb',
348
+ 'dd-raw-device',
349
+ 'mkfs',
350
+ 'kill-all',
351
+ 'diskutil-destructive',
352
+ 'tmutil-destructive',
353
+ // System control
354
+ 'shutdown',
355
+ 'csrutil',
356
+ 'nvram',
357
+ 'kextload',
358
+ 'spctl-disable',
359
+ 'xattr-quarantine-bypass',
360
+ 'topgrade',
361
+ 'softwareupdate-install',
362
+ 'systemsetup',
363
+ 'scutil-set',
364
+ 'security-keychain-destructive',
365
+ // Hard policy rules
366
+ 'no-verify',
367
+ 'no-gpg-sign',
368
+ ]);
369
+
332
370
  const bashDeny = (segments: readonly Segment[], cmd: string): Decision => {
333
- if (hasBypass(cmd)) {
334
- return allow('bash-deny');
335
- }
371
+ const bypass = hasBypass(cmd);
336
372
  for (const seg of segments) {
337
373
  for (const s of SPECS) {
338
- if (s.match(seg, cmd)) {
339
- return s.action === 'deny' ? deny(s.rule, s.message) : ask(s.rule, s.message);
374
+ if (!s.match(seg, cmd)) {
375
+ continue;
376
+ }
377
+ if (bypass && !UNBYPASSABLE_RULES.has(s.rule)) {
378
+ // Caller asserted in-turn approval; honor it for this rule.
379
+ continue;
340
380
  }
381
+ return s.action === 'deny' ? deny(s.rule, s.message) : ask(s.rule, s.message);
341
382
  }
342
383
  }
343
384
  return allow('bash-deny');
344
385
  };
345
386
 
346
- export { bashDeny };
387
+ export { bashDeny, UNBYPASSABLE_RULES };
@@ -1,4 +1,4 @@
1
- import { type Segment, hasBypass } from '../lib/bash';
1
+ import { type Segment, hasBypass, unwrapStaticString } from '../lib/bash';
2
2
  import type { GitConfig } from '../lib/config';
3
3
  import { type Decision, allow, ask, deny, warn } from '../lib/decision';
4
4
 
@@ -111,15 +111,17 @@ const messageOf = (subArgs: readonly string[]): string | null => {
111
111
  for (let i = 0; i < subArgs.length; i++) {
112
112
  const t = subArgs[i]!;
113
113
  if (t === '-m' || t === '--message') {
114
- return subArgs[i + 1] ?? null;
114
+ const raw = subArgs[i + 1];
115
+ return raw === undefined ? null : unwrapStaticString(raw);
115
116
  }
116
117
  if (t.startsWith('--message=')) {
117
- return t.slice('--message='.length);
118
+ return unwrapStaticString(t.slice('--message='.length));
118
119
  }
119
120
  // Combined short flags like `-am`, `-ma`, `-amS` carry the message
120
121
  // In the next positional arg — same as `-m` alone.
121
122
  if (/^-[a-zA-Z]*m[a-zA-Z]*$/.test(t)) {
122
- return subArgs[i + 1] ?? null;
123
+ const raw = subArgs[i + 1];
124
+ return raw === undefined ? null : unwrapStaticString(raw);
123
125
  }
124
126
  }
125
127
  return null;