@seanmozeik/tripwire 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +186 -0
- package/dist/tripwire-cli.js +6 -0
- package/dist/tripwire-cli.js.jsc +0 -0
- package/dist/tripwire.js +90 -0
- package/dist/tripwire.js.jsc +0 -0
- package/package.json +49 -0
- package/src/cli.ts +148 -0
- package/src/dispatch.ts +340 -0
- package/src/index.ts +4 -0
- package/src/lib/bash.ts +428 -0
- package/src/lib/config.ts +106 -0
- package/src/lib/decision.ts +49 -0
- package/src/lib/diff.ts +26 -0
- package/src/lib/event.ts +106 -0
- package/src/lib/log.ts +23 -0
- package/src/lib/rtk.ts +96 -0
- package/src/lib/secrets.ts +120 -0
- package/src/rules/bash-deny.ts +346 -0
- package/src/rules/bash-git.ts +592 -0
- package/src/rules/bash-network-install.ts +72 -0
- package/src/rules/bash-redirect.ts +91 -0
- package/src/rules/bash-scoped-rm.ts +84 -0
- package/src/rules/bash-tar-explosion.ts +76 -0
- package/src/rules/bash-tool-policy.ts +134 -0
- package/src/rules/config-custom.ts +51 -0
- package/src/rules/lazy-code.ts +95 -0
- package/src/rules/path-protect.ts +59 -0
- package/src/rules/post-secret-scrub.ts +38 -0
- package/src/rules/read-protect.ts +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# `@seanmozeik/tripwire`
|
|
2
|
+
|
|
3
|
+
Opinionated hooks dispatcher for AI coding agents (Claude Code, Codex, Devin, etc.) with configurable safety rules. Blocks or rewrites dangerous commands with actionable error messages.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
bun install @seanmozeik/tripwire
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## CLI
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
tripwire test '<command>' # Test a command
|
|
15
|
+
tripwire test --tool=Read --path=.env # Test Read tool
|
|
16
|
+
tripwire test --post --tool=Bash --stdout='ghp_TOKEN' # Test PostToolUse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Hook Configuration
|
|
20
|
+
|
|
21
|
+
Configure your AI agent to call `tripwire-hook` for hook events:
|
|
22
|
+
|
|
23
|
+
### Claude Code
|
|
24
|
+
|
|
25
|
+
`~/.claude/settings.json`:
|
|
26
|
+
|
|
27
|
+
```jsonc
|
|
28
|
+
{
|
|
29
|
+
"hooks": {
|
|
30
|
+
"PreToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
|
|
31
|
+
"PostToolUse": [{ "hooks": [{ "type": "command", "command": "/path/to/tripwire-hook" }] }],
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
### Codex
|
|
37
|
+
|
|
38
|
+
Same as Claude Code — Codex uses the same hook format.
|
|
39
|
+
|
|
40
|
+
### Devin
|
|
41
|
+
|
|
42
|
+
Configure in your Devin settings to call `tripwire-hook` for tool events.
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Create `~/.config/tripwire/config.json` to customize behavior:
|
|
47
|
+
|
|
48
|
+
```json
|
|
49
|
+
{
|
|
50
|
+
"rtk": { "enabled": true, "path": "/opt/homebrew/bin/rtk" },
|
|
51
|
+
"git": {
|
|
52
|
+
"protectedBranches": ["main", "master", "develop", "production", "release"],
|
|
53
|
+
"enforceConventionalCommits": true
|
|
54
|
+
},
|
|
55
|
+
"safePaths": {
|
|
56
|
+
"relative": ["dist", "build", ".next", "node_modules"],
|
|
57
|
+
"absolute": ["/tmp", "/var/tmp"]
|
|
58
|
+
},
|
|
59
|
+
"blockedCommands": [
|
|
60
|
+
{ "pattern": "dangerous-tool", "message": "Use safer-alternative instead", "action": "deny" }
|
|
61
|
+
],
|
|
62
|
+
"allowedCommands": [
|
|
63
|
+
{
|
|
64
|
+
"pattern": "my-custom-tool",
|
|
65
|
+
"message": "Allowing my-custom-tool per your configuration",
|
|
66
|
+
"action": "allow"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Configuration Options
|
|
73
|
+
|
|
74
|
+
#### `rtk`
|
|
75
|
+
|
|
76
|
+
- `enabled` (boolean, default: `false`) — Enable rtk token-saver integration
|
|
77
|
+
- `path` (string, optional) — Path to rtk binary. If not specified, searches common locations.
|
|
78
|
+
|
|
79
|
+
#### `git`
|
|
80
|
+
|
|
81
|
+
- `protectedBranches` (string[], default: `["main", "master", "develop", "production", "release"]`) — Branches that require PR for push
|
|
82
|
+
- `enforceConventionalCommits` (boolean, default: `true`) — Enforce Conventional Commits format for commit messages
|
|
83
|
+
|
|
84
|
+
#### `safePaths`
|
|
85
|
+
|
|
86
|
+
- `relative` (string[], optional) — Additional relative paths considered safe for destructive operations
|
|
87
|
+
- `absolute` (string[], optional) — Additional absolute paths considered safe for destructive operations
|
|
88
|
+
|
|
89
|
+
Default safe paths include: `dist`, `build`, `.next`, `node_modules`, `/tmp`, `/var/tmp`, and other common build/cache directories.
|
|
90
|
+
|
|
91
|
+
#### `blockedCommands`
|
|
92
|
+
|
|
93
|
+
Array of custom command blocks:
|
|
94
|
+
|
|
95
|
+
- `pattern` (string) — Command pattern to block (uses shell parsing for matching)
|
|
96
|
+
- `message` (string) — Error message shown when blocked
|
|
97
|
+
- `action` (`"deny"` | `"ask"`, default: `"deny"`) — Whether to deny or ask for confirmation
|
|
98
|
+
|
|
99
|
+
#### `allowedCommands`
|
|
100
|
+
|
|
101
|
+
Array of custom command allows (overrides blocks):
|
|
102
|
+
|
|
103
|
+
- `pattern` (string) — Command pattern to allow
|
|
104
|
+
- `message` (string) — Message shown when allowed
|
|
105
|
+
- `action` (string, default: `"allow"`) — Always `"allow"` for this context
|
|
106
|
+
|
|
107
|
+
### Shell-Based Command Matching
|
|
108
|
+
|
|
109
|
+
Command patterns in `blockedCommands` and `allowedCommands` use the same shell parsing as the rest of tripwire. This means:
|
|
110
|
+
|
|
111
|
+
- `rm` matches any `rm` command
|
|
112
|
+
- `git push` matches `git push` with any arguments
|
|
113
|
+
- Patterns are parsed using shell-quote for accurate matching
|
|
114
|
+
- More sophisticated than simple regex
|
|
115
|
+
|
|
116
|
+
Example:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"blockedCommands": [
|
|
121
|
+
{
|
|
122
|
+
"pattern": "brew install",
|
|
123
|
+
"message": "Use brew install with explicit version pinning",
|
|
124
|
+
"action": "ask"
|
|
125
|
+
}
|
|
126
|
+
]
|
|
127
|
+
}
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Default Behavior
|
|
131
|
+
|
|
132
|
+
Tripwire comes with opinionated but reasonable defaults:
|
|
133
|
+
|
|
134
|
+
### Bash Safety
|
|
135
|
+
|
|
136
|
+
- Blocks catastrophic commands: `rm -rf /`, fork bombs, `dd` to disks
|
|
137
|
+
- Blocks macOS system mutations: `defaults write`, `launchctl`, `diskutil erase`
|
|
138
|
+
- Blocks cloud destructive operations: `gh repo delete`, `flyctl destroy`
|
|
139
|
+
- Scopes `rm` and `find -delete` to safe paths (build outputs, cache directories)
|
|
140
|
+
- Blocks network install scripts: `curl | bash`, `wget | sh`
|
|
141
|
+
- Enforces package manager policy: Bun-only, no npm/pnpm/yarn/pip
|
|
142
|
+
|
|
143
|
+
### Git Policy
|
|
144
|
+
|
|
145
|
+
- Read-only operations allowed: `status`, `log`, `diff`, `fetch`, etc.
|
|
146
|
+
- Blocks working-tree destruction: `reset --hard`, `clean -fd`, `checkout .`
|
|
147
|
+
- Blocks history rewriting: `rebase -i`, `filter-branch`, `commit --amend`
|
|
148
|
+
- Blocks force push and protected branch pushes
|
|
149
|
+
- Enforces Conventional Commits format (configurable)
|
|
150
|
+
- Requires `-m "message"` for commits (no editor mode)
|
|
151
|
+
- Asks for confirmation on merge/rebase/cherry-pick
|
|
152
|
+
|
|
153
|
+
### File Protection
|
|
154
|
+
|
|
155
|
+
- Blocks reads/writes to `.env`, `.ssh/`, `*.pem`, `id_rsa*`, etc.
|
|
156
|
+
- Warns on TODO/FIXME/placeholder in code (configurable)
|
|
157
|
+
- Scrubs secrets from tool output
|
|
158
|
+
|
|
159
|
+
## Bypass
|
|
160
|
+
|
|
161
|
+
Add `# tripwire-allow: <reason>` to bypass any rule:
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
rm -rf /tmp/test # tripwire-allow: cleaning test directory
|
|
165
|
+
git reset --hard HEAD~1 # tripwire-allow: undoing mistaken commit
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Library Usage
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
import { allow, deny, ask, warn } from '@seanmozeik/tripwire';
|
|
172
|
+
import type { Decision, Config } from '@seanmozeik/tripwire';
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
## Development
|
|
176
|
+
|
|
177
|
+
```bash
|
|
178
|
+
bun install
|
|
179
|
+
bun run build # Build dist/tripwire.js and dist/tripwire-cli.js
|
|
180
|
+
bun run check # Format + lint + typecheck
|
|
181
|
+
bun test # Run tests
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
## License
|
|
185
|
+
|
|
186
|
+
MIT
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
#!/usr/bin/env bun
|
|
2
|
+
// @bun @bytecode @bun-cjs
|
|
3
|
+
(function(exports, require, module, __filename, __dirname) {var L=require("child_process"),M="/Users/sean/dev/tripwire/src/../dist/tripwire.js",O=(f)=>{let j="Bash",z=!1,x,F,G,J,K;for(let q of f){if(q==="--post"){z=!0;continue}if(q.startsWith("--tool=")){j=q.slice(7);continue}if(q.startsWith("--path=")){x=q.slice(7);continue}if(q.startsWith("--stdout=")){G=q.slice(9);continue}if(q.startsWith("--stderr=")){J=q.slice(9);continue}if(q.startsWith("--content=")){K=q.slice(10);continue}F??=q}return{tool:j,post:z,path:x,command:F,stdout:G,stderr:J,content:K}},Q=(f,j)=>{if(f==="Bash")return{command:j.command??""};if(f==="Read")return{file_path:j.path??""};if(f==="Write")return{file_path:j.path??"",content:j.content??""};if(f==="Edit"||f==="MultiEdit")return{file_path:j.path??"",old_string:"",new_string:j.content??""};return},R=(f)=>{let j=f.post?"PostToolUse":"PreToolUse",z=f.tool,x={hook_event_name:j,tool_name:z,cwd:process.cwd(),session_id:"tripwire-cli-test",tool_input:Q(z,f)};if(f.post)x.tool_response=z==="Bash"?{stdout:f.stdout??"",stderr:f.stderr??""}:{content:f.content??""};return x},V=()=>{process.stdout.write(["tripwire CLI \u2014 synthetic-event tester","","Usage:"," tripwire test '<command>' # PreToolUse Bash"," tripwire test --tool=Read --path=.env # PreToolUse Read"," tripwire test --tool=Write --path=foo.ts --content='TODO finish'"," tripwire test --post --tool=Bash --stdout='ghp_REAL_TOKEN' # PostToolUse",""].join(`
|
|
4
|
+
`))},W=()=>{let f=process.argv.slice(2);if(f.length===0||f[0]!=="test")V(),process.exit(0);let j=O(f.slice(1)),z=R(j),x=L.spawnSync(M,[],{input:JSON.stringify(z),encoding:"utf8",timeout:1e4});if(x.error!==void 0)process.stderr.write(`error: ${String(x.error)}
|
|
5
|
+
`),process.exit(1);let F=x.stdout;try{let G=JSON.parse(F);process.stdout.write(`${JSON.stringify(G,null,2)}
|
|
6
|
+
`)}catch{process.stdout.write(F)}};W();})
|
|
Binary file
|