@rafter-security/cli 0.5.1 → 0.5.5
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 +1 -1
- package/dist/commands/agent/audit-skill.js +7 -1
- package/dist/commands/agent/baseline.js +203 -0
- package/dist/commands/agent/index.js +8 -0
- package/dist/commands/agent/init.js +83 -32
- package/dist/commands/agent/install-hook.js +43 -48
- package/dist/commands/agent/scan.js +109 -12
- package/dist/commands/agent/status.js +115 -0
- package/dist/commands/agent/update-gitleaks.js +40 -0
- package/dist/commands/agent/verify.js +117 -0
- package/dist/commands/completion.js +368 -0
- package/dist/commands/hook/index.js +2 -0
- package/dist/commands/hook/posttool.js +73 -0
- package/dist/core/audit-logger.js +41 -0
- package/dist/core/config-defaults.js +4 -0
- package/dist/core/config-manager.js +16 -0
- package/dist/core/custom-patterns.js +157 -0
- package/dist/core/risk-rules.js +8 -1
- package/dist/index.js +4 -1
- package/dist/scanners/regex-scanner.js +7 -11
- package/dist/utils/binary-manager.js +150 -19
- package/dist/utils/skill-manager.js +22 -9
- package/package.json +1 -1
- package/resources/pre-push-hook.sh +60 -0
- package/resources/rafter-security-skill.md +7 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
const BASH_COMPLETION = `# rafter bash completion
|
|
3
|
+
_rafter_completions() {
|
|
4
|
+
local cur prev commands
|
|
5
|
+
COMPREPLY=()
|
|
6
|
+
cur="\${COMP_WORDS[COMP_CWORD]}"
|
|
7
|
+
prev="\${COMP_WORDS[COMP_CWORD-1]}"
|
|
8
|
+
|
|
9
|
+
# Top-level commands
|
|
10
|
+
commands="run scan get usage agent ci hook mcp policy completion help"
|
|
11
|
+
|
|
12
|
+
case "\${prev}" in
|
|
13
|
+
rafter)
|
|
14
|
+
COMPREPLY=( $(compgen -W "\${commands} --agent --version --help" -- "\${cur}") )
|
|
15
|
+
return 0
|
|
16
|
+
;;
|
|
17
|
+
agent)
|
|
18
|
+
COMPREPLY=( $(compgen -W "scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline --help" -- "\${cur}") )
|
|
19
|
+
return 0
|
|
20
|
+
;;
|
|
21
|
+
config)
|
|
22
|
+
if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
|
|
23
|
+
COMPREPLY=( $(compgen -W "show get set --help" -- "\${cur}") )
|
|
24
|
+
fi
|
|
25
|
+
return 0
|
|
26
|
+
;;
|
|
27
|
+
ci)
|
|
28
|
+
COMPREPLY=( $(compgen -W "init --help" -- "\${cur}") )
|
|
29
|
+
return 0
|
|
30
|
+
;;
|
|
31
|
+
hook)
|
|
32
|
+
COMPREPLY=( $(compgen -W "pretool --help" -- "\${cur}") )
|
|
33
|
+
return 0
|
|
34
|
+
;;
|
|
35
|
+
mcp)
|
|
36
|
+
COMPREPLY=( $(compgen -W "serve --help" -- "\${cur}") )
|
|
37
|
+
return 0
|
|
38
|
+
;;
|
|
39
|
+
policy)
|
|
40
|
+
COMPREPLY=( $(compgen -W "export --help" -- "\${cur}") )
|
|
41
|
+
return 0
|
|
42
|
+
;;
|
|
43
|
+
completion)
|
|
44
|
+
COMPREPLY=( $(compgen -W "bash zsh fish" -- "\${cur}") )
|
|
45
|
+
return 0
|
|
46
|
+
;;
|
|
47
|
+
scan)
|
|
48
|
+
if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
|
|
49
|
+
COMPREPLY=( $(compgen -W "--quiet --json --staged --diff --engine --help" -- "\${cur}") )
|
|
50
|
+
else
|
|
51
|
+
COMPREPLY=( $(compgen -W "--repo --branch --api-key --format --skip-interactive --quiet --help" -- "\${cur}") )
|
|
52
|
+
fi
|
|
53
|
+
return 0
|
|
54
|
+
;;
|
|
55
|
+
run)
|
|
56
|
+
COMPREPLY=( $(compgen -W "--repo --branch --api-key --format --skip-interactive --quiet --help" -- "\${cur}") )
|
|
57
|
+
return 0
|
|
58
|
+
;;
|
|
59
|
+
get)
|
|
60
|
+
COMPREPLY=( $(compgen -W "--api-key --format --interactive --quiet --help" -- "\${cur}") )
|
|
61
|
+
return 0
|
|
62
|
+
;;
|
|
63
|
+
init)
|
|
64
|
+
if [[ "\${COMP_WORDS[1]}" == "agent" ]]; then
|
|
65
|
+
COMPREPLY=( $(compgen -W "--risk-level --skip-openclaw --skip-claude-code --claude-code --skip-gitleaks --help" -- "\${cur}") )
|
|
66
|
+
elif [[ "\${COMP_WORDS[1]}" == "ci" ]]; then
|
|
67
|
+
COMPREPLY=( $(compgen -W "--platform --output --with-backend --help" -- "\${cur}") )
|
|
68
|
+
fi
|
|
69
|
+
return 0
|
|
70
|
+
;;
|
|
71
|
+
esac
|
|
72
|
+
}
|
|
73
|
+
complete -F _rafter_completions rafter
|
|
74
|
+
`;
|
|
75
|
+
const ZSH_COMPLETION = `#compdef rafter
|
|
76
|
+
|
|
77
|
+
_rafter() {
|
|
78
|
+
local -a commands
|
|
79
|
+
commands=(
|
|
80
|
+
'run:Submit a security scan to the Rafter backend'
|
|
81
|
+
'scan:Alias for run'
|
|
82
|
+
'get:Retrieve scan results'
|
|
83
|
+
'usage:Check API usage quota'
|
|
84
|
+
'agent:Agent security commands'
|
|
85
|
+
'ci:CI/CD pipeline setup'
|
|
86
|
+
'hook:Git hook handlers'
|
|
87
|
+
'mcp:MCP server'
|
|
88
|
+
'policy:Policy management'
|
|
89
|
+
'completion:Generate shell completions'
|
|
90
|
+
'help:Display help'
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
local -a agent_commands
|
|
94
|
+
agent_commands=(
|
|
95
|
+
'scan:Scan files for secrets locally'
|
|
96
|
+
'init:Initialize agent security'
|
|
97
|
+
'audit:View audit log'
|
|
98
|
+
'config:Manage configuration'
|
|
99
|
+
'exec:Execute command with security'
|
|
100
|
+
'audit-skill:Audit a Claude Code skill'
|
|
101
|
+
'install-hook:Install git hook (pre-commit or pre-push)'
|
|
102
|
+
'verify:Check integration status'
|
|
103
|
+
'status:Show agent status'
|
|
104
|
+
'update-gitleaks:Update gitleaks binary'
|
|
105
|
+
'baseline:Manage findings baseline'
|
|
106
|
+
)
|
|
107
|
+
|
|
108
|
+
local -a config_commands
|
|
109
|
+
config_commands=(
|
|
110
|
+
'show:Show current configuration'
|
|
111
|
+
'get:Get a configuration value'
|
|
112
|
+
'set:Set a configuration value'
|
|
113
|
+
)
|
|
114
|
+
|
|
115
|
+
_arguments -C \\
|
|
116
|
+
'(-a --agent)'{-a,--agent}'[Plain output for AI agents]' \\
|
|
117
|
+
'(-V --version)'{-V,--version}'[Show version]' \\
|
|
118
|
+
'(-h --help)'{-h,--help}'[Show help]' \\
|
|
119
|
+
'1:command:->command' \\
|
|
120
|
+
'*::arg:->args'
|
|
121
|
+
|
|
122
|
+
case "\$state" in
|
|
123
|
+
command)
|
|
124
|
+
_describe 'command' commands
|
|
125
|
+
;;
|
|
126
|
+
args)
|
|
127
|
+
case "\$words[1]" in
|
|
128
|
+
agent)
|
|
129
|
+
_arguments -C \\
|
|
130
|
+
'1:subcommand:->subcmd' \\
|
|
131
|
+
'*::arg:->subargs'
|
|
132
|
+
case "\$state" in
|
|
133
|
+
subcmd)
|
|
134
|
+
_describe 'agent command' agent_commands
|
|
135
|
+
;;
|
|
136
|
+
subargs)
|
|
137
|
+
case "\$words[1]" in
|
|
138
|
+
scan)
|
|
139
|
+
_arguments \\
|
|
140
|
+
'(-q --quiet)'{-q,--quiet}'[Only output if secrets found]' \\
|
|
141
|
+
'--json[Output as JSON]' \\
|
|
142
|
+
'--staged[Scan only staged files]' \\
|
|
143
|
+
'--diff[Scan files changed since ref]:ref:' \\
|
|
144
|
+
'--engine[Scanner engine]:engine:(gitleaks patterns)' \\
|
|
145
|
+
'1:path:_files'
|
|
146
|
+
;;
|
|
147
|
+
init)
|
|
148
|
+
_arguments \\
|
|
149
|
+
'--risk-level[Risk level]:level:(minimal moderate aggressive)' \\
|
|
150
|
+
'--skip-openclaw[Skip OpenClaw installation]' \\
|
|
151
|
+
'--skip-claude-code[Skip Claude Code installation]' \\
|
|
152
|
+
'--claude-code[Force Claude Code installation]' \\
|
|
153
|
+
'--skip-gitleaks[Skip Gitleaks download]'
|
|
154
|
+
;;
|
|
155
|
+
audit)
|
|
156
|
+
_arguments \\
|
|
157
|
+
'--last[Show last N entries]:count:' \\
|
|
158
|
+
'--event[Filter by event type]:type:' \\
|
|
159
|
+
'--agent[Filter by agent type]:agent:(openclaw claude-code)' \\
|
|
160
|
+
'--since[Show entries since date]:date:'
|
|
161
|
+
;;
|
|
162
|
+
config)
|
|
163
|
+
_arguments -C '1:subcommand:->cfgcmd'
|
|
164
|
+
case "\$state" in
|
|
165
|
+
cfgcmd)
|
|
166
|
+
_describe 'config command' config_commands
|
|
167
|
+
;;
|
|
168
|
+
esac
|
|
169
|
+
;;
|
|
170
|
+
exec)
|
|
171
|
+
_arguments \\
|
|
172
|
+
'--skip-scan[Skip pre-execution scanning]' \\
|
|
173
|
+
'--force[Skip approval prompts]' \\
|
|
174
|
+
'1:command:'
|
|
175
|
+
;;
|
|
176
|
+
audit-skill)
|
|
177
|
+
_arguments \\
|
|
178
|
+
'--skip-openclaw[Skip OpenClaw integration]' \\
|
|
179
|
+
'--json[Output as JSON]' \\
|
|
180
|
+
'1:skill-path:_files'
|
|
181
|
+
;;
|
|
182
|
+
install-hook)
|
|
183
|
+
_arguments \\
|
|
184
|
+
'--global[Install globally]'
|
|
185
|
+
;;
|
|
186
|
+
esac
|
|
187
|
+
;;
|
|
188
|
+
esac
|
|
189
|
+
;;
|
|
190
|
+
run|scan)
|
|
191
|
+
_arguments \\
|
|
192
|
+
'(-r --repo)'{-r,--repo}'[Repository]:repo:' \\
|
|
193
|
+
'(-b --branch)'{-b,--branch}'[Branch]:branch:' \\
|
|
194
|
+
'(-k --api-key)'{-k,--api-key}'[API key]:key:' \\
|
|
195
|
+
'(-f --format)'{-f,--format}'[Output format]:format:(json md)' \\
|
|
196
|
+
'--skip-interactive[Do not wait for scan]' \\
|
|
197
|
+
'--quiet[Suppress status messages]'
|
|
198
|
+
;;
|
|
199
|
+
get)
|
|
200
|
+
_arguments \\
|
|
201
|
+
'(-k --api-key)'{-k,--api-key}'[API key]:key:' \\
|
|
202
|
+
'(-f --format)'{-f,--format}'[Output format]:format:(json md)' \\
|
|
203
|
+
'--interactive[Poll until done]' \\
|
|
204
|
+
'--quiet[Suppress status messages]' \\
|
|
205
|
+
'1:scan_id:'
|
|
206
|
+
;;
|
|
207
|
+
usage)
|
|
208
|
+
_arguments \\
|
|
209
|
+
'(-k --api-key)'{-k,--api-key}'[API key]:key:'
|
|
210
|
+
;;
|
|
211
|
+
ci)
|
|
212
|
+
_arguments -C '1:subcommand:(init)'
|
|
213
|
+
;;
|
|
214
|
+
hook)
|
|
215
|
+
_arguments -C '1:subcommand:(pretool)'
|
|
216
|
+
;;
|
|
217
|
+
mcp)
|
|
218
|
+
_arguments -C '1:subcommand:(serve)'
|
|
219
|
+
;;
|
|
220
|
+
policy)
|
|
221
|
+
_arguments -C \\
|
|
222
|
+
'1:subcommand:(export)' \\
|
|
223
|
+
'*::arg:->policyargs'
|
|
224
|
+
case "\$state" in
|
|
225
|
+
policyargs)
|
|
226
|
+
_arguments \\
|
|
227
|
+
'--format[Target format]:format:(claude codex)' \\
|
|
228
|
+
'--output[Output file]:path:_files'
|
|
229
|
+
;;
|
|
230
|
+
esac
|
|
231
|
+
;;
|
|
232
|
+
completion)
|
|
233
|
+
_arguments '1:shell:(bash zsh fish)'
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
;;
|
|
237
|
+
esac
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
_rafter
|
|
241
|
+
`;
|
|
242
|
+
const FISH_COMPLETION = `# rafter fish completion
|
|
243
|
+
|
|
244
|
+
# Disable file completions by default
|
|
245
|
+
complete -c rafter -f
|
|
246
|
+
|
|
247
|
+
# Global options
|
|
248
|
+
complete -c rafter -s a -l agent -d 'Plain output for AI agents'
|
|
249
|
+
complete -c rafter -s V -l version -d 'Show version'
|
|
250
|
+
complete -c rafter -s h -l help -d 'Show help'
|
|
251
|
+
|
|
252
|
+
# Top-level commands
|
|
253
|
+
complete -c rafter -n '__fish_use_subcommand' -a run -d 'Submit a security scan'
|
|
254
|
+
complete -c rafter -n '__fish_use_subcommand' -a scan -d 'Alias for run'
|
|
255
|
+
complete -c rafter -n '__fish_use_subcommand' -a get -d 'Retrieve scan results'
|
|
256
|
+
complete -c rafter -n '__fish_use_subcommand' -a usage -d 'Check API usage quota'
|
|
257
|
+
complete -c rafter -n '__fish_use_subcommand' -a agent -d 'Agent security commands'
|
|
258
|
+
complete -c rafter -n '__fish_use_subcommand' -a ci -d 'CI/CD pipeline setup'
|
|
259
|
+
complete -c rafter -n '__fish_use_subcommand' -a hook -d 'Git hook handlers'
|
|
260
|
+
complete -c rafter -n '__fish_use_subcommand' -a mcp -d 'MCP server'
|
|
261
|
+
complete -c rafter -n '__fish_use_subcommand' -a policy -d 'Policy management'
|
|
262
|
+
complete -c rafter -n '__fish_use_subcommand' -a completion -d 'Generate shell completions'
|
|
263
|
+
|
|
264
|
+
# run / scan options
|
|
265
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -s r -l repo -d 'Repository (org/repo)' -r
|
|
266
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -s b -l branch -d 'Branch' -r
|
|
267
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -s k -l api-key -d 'API key' -r
|
|
268
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -s f -l format -d 'Output format' -ra 'json md'
|
|
269
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -l skip-interactive -d 'Do not wait for scan'
|
|
270
|
+
complete -c rafter -n '__fish_seen_subcommand_from run scan' -l quiet -d 'Suppress status messages'
|
|
271
|
+
|
|
272
|
+
# get options
|
|
273
|
+
complete -c rafter -n '__fish_seen_subcommand_from get' -s k -l api-key -d 'API key' -r
|
|
274
|
+
complete -c rafter -n '__fish_seen_subcommand_from get' -s f -l format -d 'Output format' -ra 'json md'
|
|
275
|
+
complete -c rafter -n '__fish_seen_subcommand_from get' -l interactive -d 'Poll until done'
|
|
276
|
+
complete -c rafter -n '__fish_seen_subcommand_from get' -l quiet -d 'Suppress status messages'
|
|
277
|
+
|
|
278
|
+
# usage options
|
|
279
|
+
complete -c rafter -n '__fish_seen_subcommand_from usage' -s k -l api-key -d 'API key' -r
|
|
280
|
+
|
|
281
|
+
# agent subcommands
|
|
282
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a scan -d 'Scan files for secrets'
|
|
283
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a init -d 'Initialize agent security'
|
|
284
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a audit -d 'View audit log'
|
|
285
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a config -d 'Manage configuration'
|
|
286
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a exec -d 'Execute with security'
|
|
287
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a audit-skill -d 'Audit a skill file'
|
|
288
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a install-hook -d 'Install pre-commit hook'
|
|
289
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and not __fish_seen_subcommand_from scan init audit config exec audit-skill install-hook verify status update-gitleaks baseline' -a verify -d 'Check integration status'
|
|
290
|
+
|
|
291
|
+
# agent scan options
|
|
292
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -s q -l quiet -d 'Only output if secrets found'
|
|
293
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l json -d 'Output as JSON'
|
|
294
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l staged -d 'Scan only staged files'
|
|
295
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l diff -d 'Scan changed since ref' -r
|
|
296
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from scan' -l engine -d 'Scanner engine' -ra 'gitleaks patterns'
|
|
297
|
+
|
|
298
|
+
# agent init options
|
|
299
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l risk-level -d 'Risk level' -ra 'minimal moderate aggressive'
|
|
300
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l skip-openclaw -d 'Skip OpenClaw'
|
|
301
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l skip-claude-code -d 'Skip Claude Code'
|
|
302
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l claude-code -d 'Force Claude Code'
|
|
303
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from init' -l skip-gitleaks -d 'Skip Gitleaks'
|
|
304
|
+
|
|
305
|
+
# agent audit options
|
|
306
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l last -d 'Show last N entries' -r
|
|
307
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l event -d 'Filter by event type' -r
|
|
308
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l agent -d 'Filter by agent type' -ra 'openclaw claude-code'
|
|
309
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit' -l since -d 'Since date' -r
|
|
310
|
+
|
|
311
|
+
# agent config subcommands
|
|
312
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a show -d 'Show configuration'
|
|
313
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a get -d 'Get a config value'
|
|
314
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from config' -a set -d 'Set a config value'
|
|
315
|
+
|
|
316
|
+
# agent exec options
|
|
317
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from exec' -l skip-scan -d 'Skip pre-execution scanning'
|
|
318
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from exec' -l force -d 'Skip approval prompts'
|
|
319
|
+
|
|
320
|
+
# agent audit-skill options
|
|
321
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit-skill' -l skip-openclaw -d 'Skip OpenClaw integration'
|
|
322
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from audit-skill' -l json -d 'Output as JSON'
|
|
323
|
+
|
|
324
|
+
# agent install-hook options
|
|
325
|
+
complete -c rafter -n '__fish_seen_subcommand_from agent; and __fish_seen_subcommand_from install-hook' -l global -d 'Install globally'
|
|
326
|
+
|
|
327
|
+
# ci subcommands
|
|
328
|
+
complete -c rafter -n '__fish_seen_subcommand_from ci' -a init -d 'Initialize CI pipeline'
|
|
329
|
+
complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l platform -d 'CI platform' -ra 'github gitlab circleci'
|
|
330
|
+
complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l output -d 'Output path' -r
|
|
331
|
+
complete -c rafter -n '__fish_seen_subcommand_from ci; and __fish_seen_subcommand_from init' -l with-backend -d 'Include backend audit'
|
|
332
|
+
|
|
333
|
+
# hook subcommands
|
|
334
|
+
complete -c rafter -n '__fish_seen_subcommand_from hook' -a pretool -d 'PreToolUse hook handler'
|
|
335
|
+
|
|
336
|
+
# mcp subcommands
|
|
337
|
+
complete -c rafter -n '__fish_seen_subcommand_from mcp' -a serve -d 'Start MCP server'
|
|
338
|
+
complete -c rafter -n '__fish_seen_subcommand_from mcp; and __fish_seen_subcommand_from serve' -l transport -d 'Transport type' -r
|
|
339
|
+
|
|
340
|
+
# policy subcommands
|
|
341
|
+
complete -c rafter -n '__fish_seen_subcommand_from policy' -a export -d 'Export policy'
|
|
342
|
+
complete -c rafter -n '__fish_seen_subcommand_from policy; and __fish_seen_subcommand_from export' -l format -d 'Target format' -ra 'claude codex'
|
|
343
|
+
complete -c rafter -n '__fish_seen_subcommand_from policy; and __fish_seen_subcommand_from export' -l output -d 'Output file' -r
|
|
344
|
+
|
|
345
|
+
# completion subcommand
|
|
346
|
+
complete -c rafter -n '__fish_seen_subcommand_from completion' -a 'bash zsh fish' -d 'Shell type'
|
|
347
|
+
`;
|
|
348
|
+
export function createCompletionCommand() {
|
|
349
|
+
return new Command("completion")
|
|
350
|
+
.description("Generate shell completion scripts")
|
|
351
|
+
.argument("<shell>", "Shell type: bash, zsh, or fish")
|
|
352
|
+
.action((shell) => {
|
|
353
|
+
switch (shell) {
|
|
354
|
+
case "bash":
|
|
355
|
+
process.stdout.write(BASH_COMPLETION);
|
|
356
|
+
break;
|
|
357
|
+
case "zsh":
|
|
358
|
+
process.stdout.write(ZSH_COMPLETION);
|
|
359
|
+
break;
|
|
360
|
+
case "fish":
|
|
361
|
+
process.stdout.write(FISH_COMPLETION);
|
|
362
|
+
break;
|
|
363
|
+
default:
|
|
364
|
+
process.stderr.write(`Unknown shell: ${shell}. Supported: bash, zsh, fish\n`);
|
|
365
|
+
process.exit(1);
|
|
366
|
+
}
|
|
367
|
+
});
|
|
368
|
+
}
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { Command } from "commander";
|
|
2
2
|
import { createHookPretoolCommand } from "./pretool.js";
|
|
3
|
+
import { createHookPosttoolCommand } from "./posttool.js";
|
|
3
4
|
export function createHookCommand() {
|
|
4
5
|
const hook = new Command("hook")
|
|
5
6
|
.description("Hook handlers for agent platform integration");
|
|
6
7
|
hook.addCommand(createHookPretoolCommand());
|
|
8
|
+
hook.addCommand(createHookPosttoolCommand());
|
|
7
9
|
return hook;
|
|
8
10
|
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { RegexScanner } from "../../scanners/regex-scanner.js";
|
|
3
|
+
import { AuditLogger } from "../../core/audit-logger.js";
|
|
4
|
+
export function createHookPosttoolCommand() {
|
|
5
|
+
return new Command("posttool")
|
|
6
|
+
.description("PostToolUse hook handler (reads stdin, redacts secrets in output, writes JSON to stdout)")
|
|
7
|
+
.action(async () => {
|
|
8
|
+
const input = await readStdin();
|
|
9
|
+
let payload;
|
|
10
|
+
try {
|
|
11
|
+
payload = JSON.parse(input);
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
writeOutput({ action: "continue" });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
const output = evaluateToolResponse(payload);
|
|
18
|
+
writeOutput(output);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
function evaluateToolResponse(payload) {
|
|
22
|
+
const { tool_response } = payload;
|
|
23
|
+
// No response body — pass through
|
|
24
|
+
if (!tool_response) {
|
|
25
|
+
return { action: "continue" };
|
|
26
|
+
}
|
|
27
|
+
const scanner = new RegexScanner();
|
|
28
|
+
let modified = false;
|
|
29
|
+
const redacted = { ...tool_response };
|
|
30
|
+
// Scan and redact output
|
|
31
|
+
if (typeof tool_response.output === "string" && tool_response.output) {
|
|
32
|
+
if (scanner.hasSecrets(tool_response.output)) {
|
|
33
|
+
redacted.output = scanner.redact(tool_response.output);
|
|
34
|
+
modified = true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
// Scan and redact content (used by some tools)
|
|
38
|
+
if (typeof tool_response.content === "string" && tool_response.content) {
|
|
39
|
+
if (scanner.hasSecrets(tool_response.content)) {
|
|
40
|
+
redacted.content = scanner.redact(tool_response.content);
|
|
41
|
+
modified = true;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (modified) {
|
|
45
|
+
const audit = new AuditLogger();
|
|
46
|
+
const matchCount = countMatches(scanner, tool_response);
|
|
47
|
+
audit.logContentSanitized(`${payload.tool_name} tool response`, matchCount);
|
|
48
|
+
return { action: "modify", tool_response: redacted };
|
|
49
|
+
}
|
|
50
|
+
return { action: "continue" };
|
|
51
|
+
}
|
|
52
|
+
function countMatches(scanner, tool_response) {
|
|
53
|
+
let count = 0;
|
|
54
|
+
if (typeof tool_response?.output === "string" && tool_response.output) {
|
|
55
|
+
count += scanner.scanText(tool_response.output).length;
|
|
56
|
+
}
|
|
57
|
+
if (typeof tool_response?.content === "string" && tool_response.content) {
|
|
58
|
+
count += scanner.scanText(tool_response.content).length;
|
|
59
|
+
}
|
|
60
|
+
return count;
|
|
61
|
+
}
|
|
62
|
+
function readStdin() {
|
|
63
|
+
return new Promise((resolve) => {
|
|
64
|
+
let data = "";
|
|
65
|
+
process.stdin.setEncoding("utf-8");
|
|
66
|
+
process.stdin.on("data", (chunk) => { data += chunk; });
|
|
67
|
+
process.stdin.on("end", () => { resolve(data); });
|
|
68
|
+
process.stdin.resume();
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
function writeOutput(output) {
|
|
72
|
+
process.stdout.write(JSON.stringify(output) + "\n");
|
|
73
|
+
}
|
|
@@ -3,6 +3,12 @@ import path from "path";
|
|
|
3
3
|
import { getAuditLogPath } from "./config-defaults.js";
|
|
4
4
|
import { ConfigManager } from "./config-manager.js";
|
|
5
5
|
import { assessCommandRisk } from "./risk-rules.js";
|
|
6
|
+
export const RISK_SEVERITY = {
|
|
7
|
+
low: 0,
|
|
8
|
+
medium: 1,
|
|
9
|
+
high: 2,
|
|
10
|
+
critical: 3,
|
|
11
|
+
};
|
|
6
12
|
export class AuditLogger {
|
|
7
13
|
constructor(logPath) {
|
|
8
14
|
this.logPath = logPath || getAuditLogPath();
|
|
@@ -31,6 +37,41 @@ export class AuditLogger {
|
|
|
31
37
|
// Append to log file
|
|
32
38
|
const line = JSON.stringify(fullEntry) + "\n";
|
|
33
39
|
fs.appendFileSync(this.logPath, line, "utf-8");
|
|
40
|
+
// Send webhook notification if configured and risk meets threshold
|
|
41
|
+
this.sendNotification(fullEntry, config);
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Send webhook notification for high-risk events
|
|
45
|
+
*/
|
|
46
|
+
sendNotification(entry, config) {
|
|
47
|
+
const webhookUrl = config.agent?.notifications?.webhook;
|
|
48
|
+
if (!webhookUrl)
|
|
49
|
+
return;
|
|
50
|
+
const eventRisk = entry.action?.riskLevel || "low";
|
|
51
|
+
const minRisk = config.agent?.notifications?.minRiskLevel || "high";
|
|
52
|
+
if ((RISK_SEVERITY[eventRisk] ?? 0) < (RISK_SEVERITY[minRisk] ?? 2)) {
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
const payload = {
|
|
56
|
+
event: entry.eventType,
|
|
57
|
+
risk: eventRisk,
|
|
58
|
+
command: entry.action?.command || null,
|
|
59
|
+
timestamp: entry.timestamp,
|
|
60
|
+
agent: entry.agentType || null,
|
|
61
|
+
// Slack-compatible text field
|
|
62
|
+
text: `[rafter] ${eventRisk}-risk event: ${entry.eventType}${entry.action?.command ? ` — ${entry.action.command}` : ""}`,
|
|
63
|
+
// Discord-compatible content field
|
|
64
|
+
content: `[rafter] ${eventRisk}-risk event: ${entry.eventType}${entry.action?.command ? ` — ${entry.action.command}` : ""}`,
|
|
65
|
+
};
|
|
66
|
+
// Fire-and-forget POST — never block audit logging
|
|
67
|
+
fetch(webhookUrl, {
|
|
68
|
+
method: "POST",
|
|
69
|
+
headers: { "Content-Type": "application/json" },
|
|
70
|
+
body: JSON.stringify(payload),
|
|
71
|
+
signal: AbortSignal.timeout(5000),
|
|
72
|
+
}).catch(() => {
|
|
73
|
+
// Silently ignore webhook failures
|
|
74
|
+
});
|
|
34
75
|
}
|
|
35
76
|
/**
|
|
36
77
|
* Log a command interception
|
|
@@ -96,6 +96,22 @@ export class ConfigManager {
|
|
|
96
96
|
fs.mkdirSync(dir, { recursive: true });
|
|
97
97
|
}
|
|
98
98
|
}
|
|
99
|
+
// Write patterns/ README if missing
|
|
100
|
+
const patternsReadme = path.join(rafterDir, "patterns", "README.md");
|
|
101
|
+
if (!fs.existsSync(patternsReadme)) {
|
|
102
|
+
fs.writeFileSync(patternsReadme, [
|
|
103
|
+
"# Custom Secret Patterns",
|
|
104
|
+
"",
|
|
105
|
+
"Place custom secret-detection pattern files here.",
|
|
106
|
+
"Each file should contain one regex pattern per line.",
|
|
107
|
+
"",
|
|
108
|
+
"Rafter ships 21 built-in patterns (AWS, GitHub, Stripe, etc.).",
|
|
109
|
+
"Files in this directory extend that set for your environment.",
|
|
110
|
+
"",
|
|
111
|
+
"Support for loading custom patterns from this directory is planned",
|
|
112
|
+
"for a future release.",
|
|
113
|
+
].join("\n"), "utf-8");
|
|
114
|
+
}
|
|
99
115
|
// Create default config if it doesn't exist
|
|
100
116
|
if (!fs.existsSync(this.configPath)) {
|
|
101
117
|
const config = getDefaultConfig();
|