slaunt 0.1.16 → 0.1.18

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 (164) hide show
  1. package/README.md +17 -106
  2. package/bin/slaunt-hooks.js +96 -0
  3. package/package.json +9 -59
  4. package/release/SlauntKillBar.zip +0 -0
  5. package/release/manifest.json +7 -0
  6. package/scripts/killbar-portability-check.js +67 -0
  7. package/scripts/package-killbar-macos.sh +38 -0
  8. package/src/approval-manager.js +53 -0
  9. package/src/cli-auth.js +230 -0
  10. package/src/compact-wordmark.js +13 -0
  11. package/src/desktop/broker-daemon.js +18 -0
  12. package/src/desktop/broker-grants.js +37 -0
  13. package/src/desktop/broker-hook-command.js +23 -0
  14. package/src/desktop/broker-keychain.js +29 -0
  15. package/src/desktop/broker-launch-agent.js +34 -0
  16. package/src/desktop/broker-service.js +40 -0
  17. package/src/desktop/chat-capture-command.js +7 -0
  18. package/src/desktop/chat-hook-capture.js +445 -0
  19. package/src/desktop/claude-lifecycle.js +143 -0
  20. package/src/desktop/client-identity.js +19 -0
  21. package/src/desktop/native-claude-broker.js +34 -0
  22. package/src/desktop/native-claude-connection.js +42 -0
  23. package/src/desktop/tool-guard-command.js +13 -0
  24. package/src/desktop/tool-guard.js +225 -0
  25. package/src/discovery/catalog.js +734 -0
  26. package/src/full-screen.js +88 -0
  27. package/src/helper-release.js +27 -0
  28. package/src/hidden-oauth.js +70 -0
  29. package/src/independent-grants.js +38 -0
  30. package/src/install-flow.js +71 -0
  31. package/src/killbar-background-install.js +47 -0
  32. package/src/killbar-enrollment.js +74 -0
  33. package/src/killbar-launch-agent.js +10 -0
  34. package/src/mcp-identity.js +6 -0
  35. package/src/native-config.js +150 -0
  36. package/src/native-oauth.js +33 -0
  37. package/src/oauth-cohort.js +43 -0
  38. package/src/pty-wrapper.py +57 -0
  39. package/src/read-json.js +8 -0
  40. package/src/setup/client-installer.js +93 -0
  41. package/src/setup/codex-guard-supervisor.js +34 -0
  42. package/src/setup/hook-installer.js +496 -0
  43. package/src/setup/managed-policy.js +26 -0
  44. package/src/setup/mcp-config-installer.js +204 -0
  45. package/src/setup-flow.js +66 -0
  46. package/src/tui.js +205 -0
  47. package/test/approval-manager.test.js +27 -0
  48. package/test/broker-grants.test.js +14 -0
  49. package/test/broker-keychain.test.js +14 -0
  50. package/test/broker-launch-agent.test.js +15 -0
  51. package/test/broker-service.test.js +42 -0
  52. package/test/cli-auth-recovery.test.js +15 -0
  53. package/test/codex-guard-supervisor.test.js +25 -0
  54. package/test/full-screen.test.js +49 -0
  55. package/test/helper-release.test.js +22 -0
  56. package/test/hidden-oauth.test.js +44 -0
  57. package/test/hook-installer.test.js +232 -0
  58. package/test/independent-grants.test.js +16 -0
  59. package/test/install-flow.test.js +24 -0
  60. package/test/killbar-background-install.test.js +15 -0
  61. package/test/killbar-enrollment.test.js +115 -0
  62. package/test/killbar-launch-agent.test.js +12 -0
  63. package/test/killbar-portability.test.js +17 -0
  64. package/test/managed-policy.test.js +18 -0
  65. package/test/native-claude-broker.test.js +19 -0
  66. package/test/native-claude-connection.test.js +17 -0
  67. package/test/native-claude-hooks.test.js +9 -0
  68. package/test/native-config.test.js +3 -0
  69. package/test/native-integration.cjs +29 -0
  70. package/test/native-oauth.test.js +23 -0
  71. package/test/oauth-cohort.test.js +26 -0
  72. package/test/tui.test.js +100 -0
  73. package/LICENSE +0 -201
  74. package/dist/audit/risk-engine.d.ts +0 -4
  75. package/dist/audit/risk-engine.d.ts.map +0 -1
  76. package/dist/audit/risk-engine.js +0 -174
  77. package/dist/audit/risk-engine.js.map +0 -1
  78. package/dist/audit/run.d.ts +0 -16
  79. package/dist/audit/run.d.ts.map +0 -1
  80. package/dist/audit/run.js +0 -66
  81. package/dist/audit/run.js.map +0 -1
  82. package/dist/classification/builtin-catalog.d.ts +0 -3
  83. package/dist/classification/builtin-catalog.d.ts.map +0 -1
  84. package/dist/classification/builtin-catalog.js +0 -283
  85. package/dist/classification/builtin-catalog.js.map +0 -1
  86. package/dist/classification/catalog.d.ts +0 -10
  87. package/dist/classification/catalog.d.ts.map +0 -1
  88. package/dist/classification/catalog.js +0 -194
  89. package/dist/classification/catalog.js.map +0 -1
  90. package/dist/classification/classifier.d.ts +0 -3
  91. package/dist/classification/classifier.d.ts.map +0 -1
  92. package/dist/classification/classifier.js +0 -64
  93. package/dist/classification/classifier.js.map +0 -1
  94. package/dist/classification/public-catalog-config.d.ts +0 -3
  95. package/dist/classification/public-catalog-config.d.ts.map +0 -1
  96. package/dist/classification/public-catalog-config.js +0 -5
  97. package/dist/classification/public-catalog-config.js.map +0 -1
  98. package/dist/cli.d.ts +0 -3
  99. package/dist/cli.d.ts.map +0 -1
  100. package/dist/cli.js +0 -220
  101. package/dist/cli.js.map +0 -1
  102. package/dist/config/discover.d.ts +0 -8
  103. package/dist/config/discover.d.ts.map +0 -1
  104. package/dist/config/discover.js +0 -163
  105. package/dist/config/discover.js.map +0 -1
  106. package/dist/config/install.d.ts +0 -10
  107. package/dist/config/install.d.ts.map +0 -1
  108. package/dist/config/install.js +0 -143
  109. package/dist/config/install.js.map +0 -1
  110. package/dist/config/paths.d.ts +0 -24
  111. package/dist/config/paths.d.ts.map +0 -1
  112. package/dist/config/paths.js +0 -90
  113. package/dist/config/paths.js.map +0 -1
  114. package/dist/demo.d.ts +0 -4
  115. package/dist/demo.d.ts.map +0 -1
  116. package/dist/demo.js +0 -99
  117. package/dist/demo.js.map +0 -1
  118. package/dist/explanation/local.d.ts +0 -24
  119. package/dist/explanation/local.d.ts.map +0 -1
  120. package/dist/explanation/local.js +0 -667
  121. package/dist/explanation/local.js.map +0 -1
  122. package/dist/mcp/introspect.d.ts +0 -14
  123. package/dist/mcp/introspect.d.ts.map +0 -1
  124. package/dist/mcp/introspect.js +0 -186
  125. package/dist/mcp/introspect.js.map +0 -1
  126. package/dist/presentation/plain-language.d.ts +0 -12
  127. package/dist/presentation/plain-language.d.ts.map +0 -1
  128. package/dist/presentation/plain-language.js +0 -120
  129. package/dist/presentation/plain-language.js.map +0 -1
  130. package/dist/privacy/redaction.d.ts +0 -2
  131. package/dist/privacy/redaction.d.ts.map +0 -1
  132. package/dist/privacy/redaction.js +0 -50
  133. package/dist/privacy/redaction.js.map +0 -1
  134. package/dist/report/html.d.ts +0 -4
  135. package/dist/report/html.d.ts.map +0 -1
  136. package/dist/report/html.js +0 -101
  137. package/dist/report/html.js.map +0 -1
  138. package/dist/tui/analysis-path.d.ts +0 -17
  139. package/dist/tui/analysis-path.d.ts.map +0 -1
  140. package/dist/tui/analysis-path.js +0 -184
  141. package/dist/tui/analysis-path.js.map +0 -1
  142. package/dist/tui/render.d.ts +0 -4
  143. package/dist/tui/render.d.ts.map +0 -1
  144. package/dist/tui/render.js +0 -273
  145. package/dist/tui/render.js.map +0 -1
  146. package/dist/tui/splash.d.ts +0 -15
  147. package/dist/tui/splash.d.ts.map +0 -1
  148. package/dist/tui/splash.js +0 -130
  149. package/dist/tui/splash.js.map +0 -1
  150. package/dist/tui/stream.d.ts +0 -6
  151. package/dist/tui/stream.d.ts.map +0 -1
  152. package/dist/tui/stream.js +0 -56
  153. package/dist/tui/stream.js.map +0 -1
  154. package/dist/types.d.ts +0 -131
  155. package/dist/types.d.ts.map +0 -1
  156. package/dist/types.js +0 -2
  157. package/dist/types.js.map +0 -1
  158. package/dist/version.d.ts +0 -2
  159. package/dist/version.d.ts.map +0 -1
  160. package/dist/version.js +0 -8
  161. package/dist/version.js.map +0 -1
  162. package/models/APACHE-2.0.txt +0 -202
  163. package/models/MODEL.json +0 -29
  164. package/models/NOTICE.md +0 -23
package/README.md CHANGED
@@ -1,122 +1,33 @@
1
- # Slaunt Agent Access Audit
1
+ # Slaunt native OAuth installer — development build
2
2
 
3
- See what Claude and Codex can do through their configured MCP servers.
3
+ This package contains the client-side one-sign-in OAuth handoff for Codex and Claude Code. Each native client stores its own OAuth credentials; bootstrap credentials stay in CLI memory during setup. The Desktop vault is not used for the native OAuth handoff.
4
4
 
5
- ```sh
6
- npx slaunt audit
7
- ```
8
-
9
- No account or database configuration is required.
10
-
11
- Slaunt discovers local MCP configurations—including the active Claude Code project scope—requests each server's declared tool list, classifies those tools, and identifies risky permissions or combinations. It then writes a private HTML report on your machine.
5
+ The Codex hook configuration calls `tool_authorize_codex` through the configured remote Slaunt MCP server. It passes only session, turn, tool name and tool-use metadata. The companion Central development package contains the authenticated server-side decision. For the default Codex configuration no local command hook is required. Optional legacy chat-capture and the standalone command supervisor are separate development components.
12
6
 
13
- Every discovered server also receives a grounded explanation. The npm install
14
- includes a quantized Apache-2.0 TinyLlama 1.1B model through two pinned data
15
- dependencies and runs it locally through `node-llama-cpp` with a fixed
16
- evidence-only prompt containing the server's declared `tools/list` inventory.
17
- The model parts are reassembled into a user cache and SHA-256 verified before
18
- loading. Slaunt probes the selected native backend before generating; if a GPU
19
- backend is present but cannot create a context, it retries with a cached or
20
- locally built CPU backend. The first CPU recovery may take a little longer while
21
- `node-llama-cpp` builds its backend. If no usable runtime is available, the
22
- report still explains every server with a deterministic local fallback and marks
23
- that source explicitly. Generated text must pass a local completeness and
24
- evidence-grounding check; malformed model output is discarded rather than shown
25
- in the report. No server configuration values or audit data are sent to a
26
- remote model.
7
+ **Not deployed or verified for production installation.** The CLI and persistent Claude broker are wired locally, but production setup remains disabled: the companion Central changes and migration 031 are not deployed, and the complete native OAuth and Keychain flow has not been verified on a clean Mac. OAuth retries use proof-bound receipts and recover interrupted Supabase refreshes under a database lease. Codex can continue a tool call when an MCP hook is unavailable or fails; users accepted that tradeoff for this flow. A managed Codex policy prevents disabling the hook through Codex's hook browser, subject to the organization's managed configuration and client support.
27
8
 
28
- Reports count actual client/scope bindings separately from unique server names,
29
- so the same server name configured for Claude and Codex is not silently merged.
9
+ `src/setup/managed-policy.js` can generate a Codex managed MCP policy with `createCodexManagedPolicy()`. It does not install or activate the policy. Claude policy generation still requires a separately deployed guard executable. Claude command guards use `onFailure: "block"` and require Claude Code 2.1.295 or later.
30
10
 
31
- The audit-only release does not include `slaunt connect`.
11
+ The local OAuth integration uses installed native clients in isolated temporary homes and a simulated identity provider. The direct authorization hook has server-side runtime tests; an actual Codex tool call against production and a real authenticated user has not been verified.
32
12
 
33
- ## What the audit checks
34
-
35
- - Claude Code, Claude Desktop, Codex CLI, and Codex Desktop configurations
36
- - Claude Code user-level and active project-scoped MCP server entries
37
- - MCP servers declared in JSON or TOML
38
- - Tools exposed by each server
39
- - Sensitive capabilities such as shell execution, secret access, code changes, and production deployment
40
- - Risky combinations across servers and clients
41
- - Unknown tools that still need classification review
42
-
43
- Slaunt performs the MCP initialization handshake and `tools/list`. It never calls an MCP tool.
44
-
45
- ## Run modes
13
+ Requirements for local tests: Node.js 20 or newer; Python 3 and both native client CLIs for the native integration test.
46
14
 
47
15
  ```sh
48
- # Normal interactive audit
49
- npx slaunt audit
50
-
51
- # Inspect configuration without starting local MCP processes
52
- npx slaunt audit --no-server-starts
53
-
54
- # Run non-interactively and allow declared tool-list requests
55
- npx slaunt audit --yes
56
-
57
- # Skip the paced terminal presentation
58
- npx slaunt audit --quick
59
-
60
- # Use only the bundled classification rules
61
- npx slaunt audit --offline
62
-
63
- # Produce redacted JSON without a report
64
- npx slaunt audit --json --no-report --no-server-starts
65
- ```
66
-
67
- The interactive terminal view is a graphical evidence dashboard: the posture
68
- signal, inventory coverage, risk bars, client/server access map, findings, and
69
- full server explanations are grouped into bordered panels. Long explanations
70
- wrap across lines instead of being shortened to fit a single row. Use
71
- `--quick` when a non-animated report is easier to scan.
72
-
73
- Run `npx slaunt audit --help` for all options. Node.js 20.17 or newer is required.
74
-
75
- ## Privacy and consent
76
-
77
- - Tool names, descriptions, schemas, configuration values, findings, and reports stay local.
78
- - Slaunt downloads approved classification rules from a read-only Supabase catalog. It does not upload audit data.
79
- - Starting a configured stdio server requires interactive consent or `--allow-server-starts`.
80
- - Stdio starts receive configured variables and the MCP SDK's safe defaults; the full parent environment is passed only with explicit `--inherit-parent-env`.
81
- - Slaunt MCP installation happens only with `--install-slaunt`.
82
- - The HTML report opens only with `--open-report`.
83
- - Environment variables and HTTP header values are redacted from JSON and reports.
84
-
85
- If the remote catalog is unavailable, Slaunt uses a seven-day local cache and then its bundled rules. `--offline` disables the refresh entirely.
86
-
87
- The bundled model is the default. To use an already-running local Ollama
88
- model instead, set `SLAUNT_AUDIT_LLM_URL` (it must resolve to `localhost`,
89
- `127.0.0.1`, or `::1`) and optionally `SLAUNT_AUDIT_LLM_MODEL`.
90
- `SLAUNT_AUDIT_LLM_TIMEOUT_MS` controls either local path. The model manifest
91
- and attribution are in `models/`; `npm run verify:model` checks the pinned
92
- size and SHA-256 before publication.
93
-
94
- ## How classification works
95
-
96
- Approved database rules and bundled rules map tool declarations to capabilities and risk levels. Matching happens locally. Slaunt then checks direct authority and cross-server attack paths, such as a client that can execute shell commands and read secrets.
97
-
98
- Unknown does not mean malicious. It means no approved rule matched the tool, so Slaunt treats it as potentially sensitive until reviewed.
99
-
100
- ## Optional Slaunt MCP installation
101
-
102
- ```sh
103
- npx slaunt audit --install-slaunt
16
+ npm ci --ignore-scripts
17
+ npm test
18
+ npm run test:integration
104
19
  ```
105
20
 
106
- Installation is explicit, idempotent, backed up, atomic, permission-preserving, and symlink-safe. The default audit never edits client configuration.
21
+ ## Interactive setup
107
22
 
108
- ## Audit limits
23
+ Run `node bin/slaunt-hooks.js install` in an interactive terminal for a centered full-screen setup with an animated splash, logo, selection and progress. The layout adapts to terminal size. The original terminal screen is restored on exit, with the result printed afterward. Choose Codex, Claude Code, or both with the arrow keys or j/k. The recommended default is both clients. Pass `--client codex`, `--client claude`, or `--client both` to skip the picker. Escape, Ctrl-C, or Ctrl-D cancels the picker. The installer displays its current availability and exits without changing settings while the production integration remains gated.
109
24
 
110
- This is a declared-access audit. It does not inspect prompts, file contents, credentials, non-MCP agents, hidden provider scopes, client approval policy, or whether a tool has been used. If a server cannot be inspected, its tool inventory remains unavailable; Slaunt does not treat that server as safe.
25
+ Set `SLAUNT_REDUCED_MOTION=1` to disable animation, or set `NO_COLOR` to disable color. Small terminals use a compact terminal-cell rendering of the workspace Neue Haas wordmark; the ASCII logo remains on larger screens. Noninteractive and `TERM=dumb` terminals receive plain output and select both clients by default; CI skips interactive prompts and animation.
111
26
 
112
- ## Development
27
+ ## Packaged KillBar helper
113
28
 
114
- ```sh
115
- npm ci
116
- npm run check
117
- npm test
118
- ```
29
+ The development npm package includes an ad hoc-signed Apple Silicon helper as `release/SlauntKillBar.zip`. The installer checks the archive SHA-256, extracts it to an owner-only temporary directory, verifies its code signature and pinned code-directory hash, and verifies the installed copy before registering a launch agent. The archive and manifest are in the same package: their hashes detect accidental or partial modification but do not authenticate the publisher if an attacker can replace the entire package. Distribution must use a trusted npm release and account.
119
30
 
120
- The Supabase schema is in [`supabase/migrations`](supabase/migrations). Public clients receive only approved, active rules through column-scoped `SELECT`, forced Row Level Security, and no public mutation policy. Never ship or expose a Supabase secret key.
31
+ The helper is not Developer ID signed or notarized. A local extraction and bridge-version test passed, but this does not establish Gatekeeper behavior for every download method, enrollment, Keychain access, or startup after login on another Mac. Intel Macs are unsupported by this artifact. The package remains private and has not been published.
121
32
 
122
- See [SECURITY.md](SECURITY.md) for the complete security boundary and vulnerability-reporting process.
33
+ The setup flow checks the authenticated workspace when enrolling the helper, stores a separate guard grant in the helper's Keychain item, and registers per-user launch agents for KillBar and the Claude broker. The broker maintains separate Central connections for Claude sessions and denies guarded calls when authorization is unavailable. Existing installations are not replaced. Live production enrollment, Keychain access, background health, restart, and command delivery have not been verified.
@@ -0,0 +1,96 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ const {spawn}=require('node:child_process');
4
+ const {ISSUER}=require('../src/setup-flow');
5
+ const {readJson}=require('../src/read-json');
6
+ const {createTerminal}=require('../src/tui');
7
+ const {installFlow}=require('../src/install-flow');
8
+
9
+ const MACOS_CODEX_APP = '/Applications/ChatGPT.app/Contents/Resources/codex-cli/CodexCLI.app/Contents/MacOS/codex';
10
+ function probeExecutable(command, {signal, env}) {
11
+ if (signal?.aborted) return Promise.resolve('aborted');
12
+ return new Promise(resolve=>{
13
+ let child;try {child=spawn(command,['--version'],{stdio:'ignore',env});}catch{return resolve('failed');}
14
+ let finished=false;const finish=value=>{if(finished)return;finished=true;clearTimeout(timer);signal?.removeEventListener('abort',abort);resolve(value);};
15
+ const abort=()=>{child.kill('SIGKILL');finish('aborted');};
16
+ const timer=setTimeout(abort,10000);
17
+ signal?.addEventListener('abort',abort,{once:true});
18
+ if(signal?.aborted)abort();
19
+ child.once('error',error=>finish(error.code==='ENOENT'?'missing':'failed'));
20
+ child.once('close',code=>finish(code===0?'found':'failed'));
21
+ });
22
+ }
23
+ async function resolveClient(command, {signal, env=process.env, platform=process.platform, appCodexPath=MACOS_CODEX_APP} = {}) {
24
+ const result=await probeExecutable(command,{signal,env});
25
+ if(result==='found') return command;
26
+ // The macOS app bundles Codex without adding it to other terminal sessions' PATH.
27
+ if(result==='missing' && command==='codex' && platform==='darwin' && !signal?.aborted)
28
+ return await probeExecutable(appCodexPath,{signal,env})==='found' ? appCodexPath : null;
29
+ return null;
30
+ }
31
+ async function checkClient(command, options) { return Boolean(await resolveClient(command, options)); }
32
+
33
+ async function main(args=process.argv.slice(2),dependencies={}) {
34
+ const terminal=dependencies.terminal||createTerminal();
35
+ const output=dependencies.output||process.stdout;
36
+ const env=dependencies.env||process.env;
37
+ const fetchImpl=dependencies.fetch||fetch;
38
+ const probe=dependencies.probe||resolveClient;
39
+ if(args.length===0||args[0]==='--help'){output.write('Usage: slaunt-hooks install [--client codex|claude|both]\nChecks setup availability for Codex and Claude Code.\n');return 0;}
40
+ if(args[0]!=='install'||(args.length!==1&&!(args.length===3&&args[1]==='--client'&&['codex','claude','both'].includes(args[2])))){terminal.fail('Invalid arguments.','Run slaunt-hooks --help.');return 2;}
41
+ const controller=new AbortController();
42
+ const probes=new Map();
43
+ const probeOnce=client=>{
44
+ if(!probes.has(client)) probes.set(client,Promise.resolve().then(()=>probe(client,{signal:controller.signal,env})));
45
+ return probes.get(client);
46
+ };
47
+ try {
48
+ await terminal.enter?.();
49
+ terminal.banner();
50
+ if(env.CODEX_HOME||env.CLAUDE_CONFIG_DIR){terminal.fail('Custom client directories are not supported.','No client settings were changed.');return 1;}
51
+ if(!args[2] && terminal.interactive && terminal.setClientAvailability){
52
+ for(const client of ['codex','claude']){
53
+ terminal.setClientAvailability(client,'checking');
54
+ void probeOnce(client).then(found=>{if(!controller.signal.aborted)terminal.setClientAvailability(client,found?'installed':'missing');},()=>{if(!controller.signal.aborted)terminal.setClientAvailability(client,'missing');});
55
+ }
56
+ }
57
+ const selection=args[2]||await terminal.selectClients();
58
+ if(!selection){terminal.fail('Setup cancelled.','No client settings were changed.');return 130;}
59
+ terminal.setSelection?.(selection);
60
+ const clients=selection==='both'?['codex','claude']:[selection];
61
+ const executablePaths={};
62
+ for(const client of clients){const label=client==='codex'?'Codex':'Claude Code';terminal.progress(`Checking ${label}`);const found=await probeOnce(client);if(!found){terminal.fail(`${label} could not be found or started.`,'Check that the client is installed and available in this terminal, then try again. No settings were changed.');return 1;}executablePaths[client]=typeof found==='string'?found:client;terminal.done(`${label} found`);}
63
+ terminal.progress('Checking Slaunt setup');
64
+ let metadata;try {const res=await fetchImpl(new URL('/.well-known/oauth-authorization-server',ISSUER),{redirect:'error',signal:AbortSignal.timeout(10000)});if(!res.ok)throw new Error();metadata=await readJson(res,32768);}catch{terminal.fail('Could not reach Slaunt setup.','Check your connection and try again. No settings were changed.');return 1;}
65
+ if(metadata.issuer!==ISSUER||metadata.slaunt_cli_setup_supported!==true){terminal.fail('Setup is not yet enabled on Slaunt.','No client settings were changed.');return 1;}
66
+ terminal.done('Slaunt setup available');
67
+ try {
68
+ const result=await (dependencies.installFlow||installFlow)({clients,executablePaths,terminal,fetchImpl});
69
+ if(result?.installed!==true)throw new Error('setup-incomplete');
70
+ terminal.success?.('Setup complete.','Selected clients and KillBar are connected.');
71
+ return 0;
72
+ } catch(error) {
73
+ const messages={'killbar-release-missing':'KillBar is missing from this installer.','killbar-release-unavailable':'KillBar release metadata is invalid.','killbar-release-untrusted':'KillBar could not be verified.','killbar-platform-unsupported':'This KillBar package requires an Apple Silicon Mac.','killbar-helper-unavailable':'KillBar could not be verified.','killbar-helper-untrusted':'KillBar could not be verified.','claude-guard-unavailable':'Claude authorization is not available in this installer.'};
74
+ const clientLabel=error?.setupClient==='codex'?'Codex':error?.setupClient==='claude'?'Claude Code':null;
75
+ if(clientLabel && error?.message==='client-configuration-conflict') messages[error.message]=`${clientLabel} already has a conflicting Slaunt configuration.`;
76
+ if(clientLabel && error?.message==='client-configuration-failed') messages[error.message]=`${clientLabel} configuration could not be updated.`;
77
+ if(error?.message==='cohort-failed') {
78
+ if(error.setupStage==='native-login' && clientLabel) messages[error.message]=`${clientLabel} could not start its Slaunt authorization.`;
79
+ else if(error.setupStage==='authorization') {
80
+ const details={'sign-in':'Slaunt sign-in could not be completed.','workspace':'Slaunt workspace identity could not be verified.','enrollment':'KillBar enrollment could not be completed.','client-grants':'Slaunt could not issue client authorization grants.'};
81
+ messages[error.message]=details[error.setupDetail] || 'Slaunt authorization or enrollment could not be completed.';
82
+ }
83
+ else if(error.setupStage==='timeout') messages[error.message]='Slaunt authorization timed out.';
84
+ }
85
+ terminal.fail(messages[error?.message]||'Setup could not be completed.',error?.setupMayHaveChanged?'Some settings may have changed; review them before retrying.':'No client settings were changed.');return 1;
86
+ }
87
+ } finally {controller.abort();terminal.stop?.();terminal.close?.();}
88
+ }
89
+ if(require.main===module) {
90
+ const terminal=createTerminal();
91
+ const onInterrupt=()=>{terminal.close();process.exit(130);};
92
+ const onTerminate=()=>{terminal.close();process.exit(143);};
93
+ process.once('SIGINT',onInterrupt);process.once('SIGTERM',onTerminate);
94
+ void main(process.argv.slice(2),{terminal}).then(code=>{process.exitCode=code;},()=>{terminal.close();console.error('Setup failed. Review client settings before retrying.');process.exitCode=1;}).finally(()=>{process.removeListener('SIGINT',onInterrupt);process.removeListener('SIGTERM',onTerminate);});
95
+ }
96
+ module.exports={main,checkClient,resolveClient};
package/package.json CHANGED
@@ -1,70 +1,20 @@
1
1
  {
2
2
  "name": "slaunt",
3
- "version": "0.1.16",
4
- "description": "Privacy-first MCP agent access auditor for Claude and Codex",
5
- "type": "module",
3
+ "version": "0.1.18",
6
4
  "bin": {
7
- "slaunt": "dist/cli.js"
5
+ "slaunt": "bin/slaunt-hooks.js",
6
+ "slaunt-hooks": "bin/slaunt-hooks.js"
8
7
  },
9
- "files": [
10
- "dist",
11
- "models/MODEL.json",
12
- "models/NOTICE.md",
13
- "models/APACHE-2.0.txt",
14
- "README.md",
15
- "LICENSE"
16
- ],
17
8
  "scripts": {
18
- "build": "tsc -p tsconfig.build.json",
19
- "check": "tsc --noEmit",
20
- "test": "npm run build && vitest run",
21
- "test:watch": "vitest",
22
- "audit:demo": "npm run build && node dist/cli.js audit --demo",
23
- "verify:model": "node scripts/verify-model.mjs",
24
- "fetch:model": "node scripts/fetch-model.mjs",
25
- "prepublishOnly": "npm run check && npm test && npm run verify:model"
9
+ "test": "node --test test/*.test.js",
10
+ "test:integration": "node test/native-integration.cjs"
26
11
  },
27
12
  "engines": {
28
- "node": ">=20.17.0"
13
+ "node": ">=20"
29
14
  },
30
- "keywords": [
31
- "mcp",
32
- "security",
33
- "audit",
34
- "claude",
35
- "codex",
36
- "agent"
37
- ],
38
- "repository": {
39
- "type": "git",
40
- "url": "git+https://github.com/kerodem/slaunt-audit.git"
41
- },
42
- "bugs": {
43
- "url": "https://github.com/kerodem/slaunt-audit/issues"
44
- },
45
- "homepage": "https://github.com/kerodem/slaunt-audit#readme",
46
- "license": "MIT",
47
15
  "dependencies": {
48
- "@inquirer/prompts": "8.5.2",
49
- "@modelcontextprotocol/sdk": "1.30.0",
50
- "chalk": "6.0.0",
51
- "node-llama-cpp": "3.21.1",
52
- "open": "11.0.0",
53
- "ora": "9.4.1",
54
- "slaunt-model-tinyllama-1b-part1": "0.1.0",
55
- "slaunt-model-tinyllama-1b-part2": "0.1.0",
56
- "smol-toml": "1.7.1",
57
- "zod": "4.4.3"
58
- },
59
- "devDependencies": {
60
- "@types/node": "24.10.1",
61
- "typescript": "7.0.2",
62
- "vitest": "4.1.10"
63
- },
64
- "overrides": {
65
- "fast-uri": "3.1.6",
66
- "hono": "4.13.5",
67
- "@hono/node-server": "2.1.1",
68
- "qs": "6.16.0"
16
+ "@electron/asar": "3.4.1",
17
+ "@modelcontextprotocol/sdk": "^1.32.2",
18
+ "smol-toml": "1.9.1"
69
19
  }
70
20
  }
Binary file
@@ -0,0 +1,7 @@
1
+ {
2
+ "format": 1,
3
+ "arch": "arm64",
4
+ "signing": "adhoc",
5
+ "sha256": "07890842d715bf1e322e13347b5b162774ccfb301b44595037fff97a2a2c147c",
6
+ "cdhash": "f2d067398109958d17e4c3b6bd6000eb02df0d99"
7
+ }
@@ -0,0 +1,67 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+ // Cross-machine acceptance probe. Never starts a persistent agent or enrolls a device.
4
+ const fs=require('node:fs');
5
+ const os=require('node:os');
6
+ const path=require('node:path');
7
+ const {spawnSync}=require('node:child_process');
8
+
9
+ function run(command,args,options={}) {
10
+ const result=spawnSync(command,args,{encoding:'utf8',timeout:30000,maxBuffer:1024*1024,...options});
11
+ if(result.error || result.status!==0) throw new Error('command failed');
12
+ return result.stdout.trim();
13
+ }
14
+ function parseArgs(args) {
15
+ if(args.length!==4 || args[0]!=='--app' || args[2]!=='--team' || !path.isAbsolute(args[1]) || !/^[A-Z0-9]{10}$/.test(args[3])) throw new Error('Usage: node scripts/killbar-portability-check.js --app /absolute/path/SlauntKillBar.app --team TRUSTED_TEAM_ID');
16
+ return {app:args[1],team:args[3]};
17
+ }
18
+ function test({app,team},{platform=process.platform,runCommand=run,tempRoot=os.tmpdir(),write=console.log}={}) {
19
+ const checks=[];
20
+ function check(name,fn){try{fn();checks.push({name,result:'PASS'});}catch{checks.push({name,result:'FAIL'});}}
21
+ function pending(name){checks.push({name,result:'NOT VERIFIED'});}
22
+ if(platform!=='darwin') {checks.push({name:'macOS platform',result:'FAIL'});return checks;}
23
+ let tmp;
24
+ try {
25
+ tmp=fs.mkdtempSync(path.join(tempRoot,'slaunt-other-mac-'));
26
+ const pkg=path.join(tmp,'package'),dest=path.join(tmp,'install'),extract=path.join(tmp,'extract');
27
+ fs.mkdirSync(pkg);fs.mkdirSync(dest);fs.mkdirSync(extract);
28
+ let packed=false,installed=false,unpacked=false,verified=false,bridge=false;
29
+ const archive=path.join(pkg,'killbar.zip');
30
+ const requirement=`=identifier "ai.slaunt.killbar" and anchor apple generic and certificate leaf[subject.OU] = "${team}"`;
31
+ check('Source signature matches trusted team',()=>runCommand('/usr/bin/codesign',['--verify','--deep','--strict','-R',requirement,app]));
32
+ if(checks.at(-1).result==='PASS') check('Create npm delivery fixture',()=>{
33
+ runCommand('/usr/bin/ditto',['-c','-k','--sequesterRsrc','--keepParent',app,archive]);
34
+ fs.writeFileSync(path.join(pkg,'package.json'),JSON.stringify({name:'slaunt-killbar-acceptance-fixture',version:'1.0.0',private:true,files:['killbar.zip']}));
35
+ runCommand('npm',['pack',pkg,'--pack-destination',tmp,'--offline','--ignore-scripts','--cache',path.join(tmp,'cache'),'--silent']);packed=true;
36
+ }); else pending('Create npm delivery fixture');
37
+ if(packed) check('Install npm fixture',()=>{
38
+ runCommand('npm',['install',path.join(tmp,'slaunt-killbar-acceptance-fixture-1.0.0.tgz'),'--prefix',dest,'--cache',path.join(tmp,'cache'),'--offline','--ignore-scripts','--no-audit','--no-fund','--silent']);installed=true;
39
+ }); else pending('Install npm fixture');
40
+ const copied=path.join(extract,'SlauntKillBar.app');
41
+ if(installed) check('Extract helper',()=>{runCommand('/usr/bin/ditto',['-x','-k',path.join(dest,'node_modules/slaunt-killbar-acceptance-fixture/killbar.zip'),extract]);unpacked=true;}); else pending('Extract helper');
42
+ if(unpacked) check('Extracted signature matches trusted team',()=>{runCommand('/usr/bin/codesign',['--verify','--deep','--strict','-R',requirement,copied]);verified=true;}); else pending('Extracted signature matches trusted team');
43
+ if(verified) check('Gatekeeper distribution assessment',()=>runCommand('/usr/sbin/spctl',['--assess','--type','execute',copied])); else pending('Gatekeeper distribution assessment');
44
+ const bin=path.join(copied,'Contents/MacOS/SlauntKillBar');
45
+ if(verified) check('Helper executes from npm fixture',()=>{const v=JSON.parse(runCommand(bin,['--desktop-bridge-version']));if(v.protocol_version!==1)throw new Error();bridge=true;}); else pending('Helper executes from npm fixture');
46
+ if(bridge) check('Local status protocol responds',()=>{const status=JSON.parse(runCommand(bin,['--desktop-control-status']));if(status.protocol_version!==1 || typeof status.enrolled!=='boolean' || status.requires_restart===true) throw new Error();}); else pending('Local status protocol responds');
47
+ pending('Clean-device enrollment and Keychain access');
48
+ // The installer and an observable service health protocol do not exist yet.
49
+ // A running process or a launchd entry alone cannot prove login restart or command processing.
50
+ pending('Clean macOS VM or independent machine');
51
+ pending('Actual published npm package');
52
+ pending('Background service starts and stays healthy');
53
+ pending('Service starts after logout/login or restart');
54
+ pending('Server command delivery and acknowledgement');
55
+ } catch {checks.push({name:'Test setup',result:'FAIL'});}
56
+ finally {if(tmp)fs.rmSync(tmp,{recursive:true,force:true});}
57
+ return checks;
58
+ }
59
+ function main(args=process.argv.slice(2)) {
60
+ let options;try{options=parseArgs(args);}catch(e){console.error(e.message);return 2;}
61
+ const checks=test(options);for(const c of checks) console.log(`${c.result.padEnd(12)} ${c.name}`);
62
+ const failed=checks.some(c=>c.result==='FAIL'),pending=checks.some(c=>c.result==='NOT VERIFIED');
63
+ console.log(failed?'Result: FAIL':pending?'Result: INCOMPLETE':'Result: PASS');
64
+ return failed?1:pending?2:0;
65
+ }
66
+ if(require.main===module)process.exitCode=main();
67
+ module.exports={test,parseArgs,main};
@@ -0,0 +1,38 @@
1
+ #!/bin/bash
2
+ # Stage an already signed and notarized KillBar app for the npm release.
3
+ set -euo pipefail
4
+ if [ "$#" -ne 3 ]; then
5
+ echo 'Usage: package-killbar-macos.sh <signed-app> <expected-team-id> <output-zip>' >&2
6
+ exit 2
7
+ fi
8
+ app=$1
9
+ team=$2
10
+ output=$3
11
+ if [[ ! "$team" =~ ^[A-Z0-9]{10}$ ]] || [[ "$app" != /* ]] || [[ "$output" != /* ]]; then
12
+ echo 'Use absolute paths and the expected 10-character Apple team ID.' >&2
13
+ exit 2
14
+ fi
15
+ if [ ! -d "$app" ] || [ -e "$output" ]; then
16
+ echo 'The source app must exist and the output must not already exist.' >&2
17
+ exit 1
18
+ fi
19
+ requirement="=identifier \"ai.slaunt.killbar\" and anchor apple generic and certificate leaf[subject.OU] = \"$team\""
20
+ /usr/bin/codesign --verify --deep --strict -R "$requirement" "$app"
21
+ /usr/sbin/spctl --assess --type execute "$app"
22
+ /usr/bin/xcrun stapler validate "$app"
23
+ tmp=$(/usr/bin/mktemp -d "${TMPDIR:-/tmp}/slaunt-killbar-release.XXXXXXXX")
24
+ trap 'rm -rf "$tmp"' EXIT
25
+ /usr/bin/codesign -d --entitlements :- "$app" > "$tmp/entitlements.plist" 2>/dev/null
26
+ expected="$team.ai.slaunt.killbar"
27
+ actual=$(/usr/libexec/PlistBuddy -c 'Print :com.apple.application-identifier' "$tmp/entitlements.plist")
28
+ group=$(/usr/libexec/PlistBuddy -c 'Print :keychain-access-groups:0' "$tmp/entitlements.plist")
29
+ if [ "$actual" != "$expected" ] || [ "$group" != "$expected" ]; then
30
+ echo 'The signed application identifier or Keychain access group does not match.' >&2
31
+ exit 1
32
+ fi
33
+ /usr/bin/ditto "$app" "$tmp/SlauntKillBar.app"
34
+ /usr/bin/codesign --verify --deep --strict -R "$requirement" "$tmp/SlauntKillBar.app"
35
+ /usr/bin/ditto -c -k --sequesterRsrc --keepParent "$tmp/SlauntKillBar.app" "$tmp/SlauntKillBar.zip"
36
+ # Exclusive creation prevents overwriting an existing release artifact.
37
+ ( set -o noclobber; cat "$tmp/SlauntKillBar.zip" > "$output" )
38
+ echo 'Verified KillBar archive created.'
@@ -0,0 +1,53 @@
1
+ 'use strict';
2
+ const crypto = require('node:crypto');
3
+ const { validateRequest, validateCallback } = require('./hidden-oauth');
4
+ const hash = value => crypto.createHash('sha256').update(value).digest();
5
+ const secret = () => crypto.randomBytes(32).toString('base64url');
6
+ const equal = (digest, value) => typeof value === 'string' && value.length <= 128 && crypto.timingSafeEqual(digest, hash(value));
7
+ // Prototype only: in-memory state is intentionally not suitable for deployment.
8
+ // validateClient must check the server's actual registered client and redirect.
9
+ // issueCallback must create independent refreshable grants for each request.
10
+ function createApprovalManager({ issuer, validateClient, issueCallback, now = Date.now, ttlMs = 120000, maxPending = 1000 }) {
11
+ if (typeof validateClient !== 'function' || typeof issueCallback !== 'function') throw new Error('approval-dependencies-required');
12
+ const entries = new Map();
13
+ function clearExpired() { for (const [id, entry] of entries) if (entry.expiresAt <= now()) entries.delete(id); }
14
+ async function create(requests) {
15
+ clearExpired();
16
+ if (!Array.isArray(requests) || requests.length < 1 || requests.length > 2 || entries.size >= maxPending) throw new Error('invalid-approval-request');
17
+ const clients = new Set();
18
+ const validated = [];
19
+ for (const item of requests) {
20
+ if (!['codex','claude'].includes(item?.client) || clients.has(item.client) || typeof item.authorizationUrl !== 'string' || item.authorizationUrl.length > 8192) throw new Error('invalid-approval-request');
21
+ clients.add(item.client);
22
+ const request = validateRequest(item.authorizationUrl, issuer);
23
+ if (!await validateClient({client:item.client, request:request.url})) throw new Error('unregistered-client');
24
+ validated.push({ client:item.client, request });
25
+ }
26
+ const id=secret(), approveSecret=secret(), redeemSecret=secret();
27
+ entries.set(id,{requests:validated, approveHash:hash(approveSecret), redeemHash:hash(redeemSecret), expiresAt:now()+ttlMs, state:'pending'});
28
+ return { id, approveSecret, redeemSecret, expiresAt:now()+ttlMs };
29
+ }
30
+ function approve({id, approveSecret, principal, accepted}) {
31
+ clearExpired();
32
+ const entry=entries.get(id);
33
+ if (!entry || entry.state !== 'pending' || !equal(entry.approveHash, approveSecret) || !principal || typeof principal !== 'string') throw new Error('invalid-approval');
34
+ if (accepted !== true) { entries.delete(id); return { approved:false }; }
35
+ entry.principal=principal; entry.state='approved'; entry.approveHash=null;
36
+ return { approved:true, clients:entry.requests.map(x=>x.client) };
37
+ }
38
+ async function redeem({id, redeemSecret}) {
39
+ clearExpired();
40
+ const entry=entries.get(id);
41
+ if (!entry || entry.state !== 'approved' || !equal(entry.redeemHash, redeemSecret)) throw new Error('invalid-redemption');
42
+ // Claim before awaiting external code; retries must start a new approval.
43
+ entry.state='consumed'; entries.delete(id);
44
+ const callbacks = {};
45
+ for (const item of entry.requests) {
46
+ const result = await issueCallback({ client:item.client, request:item.request.url, principal:entry.principal });
47
+ callbacks[item.client] = validateCallback(result, item.request);
48
+ }
49
+ return callbacks;
50
+ }
51
+ return {create,approve,redeem};
52
+ }
53
+ module.exports = {createApprovalManager};