@sonoma-security/mcp-gateway 0.1.12 → 0.1.14

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 (53) hide show
  1. package/dist/__tests__/config.test.js +140 -2
  2. package/dist/__tests__/config.test.js.map +1 -1
  3. package/dist/__tests__/plugin-discovery.test.d.ts +12 -0
  4. package/dist/__tests__/plugin-discovery.test.d.ts.map +1 -0
  5. package/dist/__tests__/plugin-discovery.test.js +367 -0
  6. package/dist/__tests__/plugin-discovery.test.js.map +1 -0
  7. package/dist/__tests__/tool-blocking.test.d.ts +2 -0
  8. package/dist/__tests__/tool-blocking.test.d.ts.map +1 -0
  9. package/dist/__tests__/tool-blocking.test.js +256 -0
  10. package/dist/__tests__/tool-blocking.test.js.map +1 -0
  11. package/dist/auth/keychain.d.ts +34 -0
  12. package/dist/auth/keychain.d.ts.map +1 -0
  13. package/dist/auth/keychain.js +305 -0
  14. package/dist/auth/keychain.js.map +1 -0
  15. package/dist/auth/storage.d.ts +5 -6
  16. package/dist/auth/storage.d.ts.map +1 -1
  17. package/dist/auth/storage.js +72 -21
  18. package/dist/auth/storage.js.map +1 -1
  19. package/dist/auth/upstream-oauth.d.ts.map +1 -1
  20. package/dist/auth/upstream-oauth.js +8 -5
  21. package/dist/auth/upstream-oauth.js.map +1 -1
  22. package/dist/auth/upstream-token-store.d.ts +18 -6
  23. package/dist/auth/upstream-token-store.d.ts.map +1 -1
  24. package/dist/auth/upstream-token-store.js +127 -35
  25. package/dist/auth/upstream-token-store.js.map +1 -1
  26. package/dist/cli.js +48 -2
  27. package/dist/cli.js.map +1 -1
  28. package/dist/config.d.ts.map +1 -1
  29. package/dist/config.js +122 -27
  30. package/dist/config.js.map +1 -1
  31. package/dist/gateway.d.ts +15 -0
  32. package/dist/gateway.d.ts.map +1 -1
  33. package/dist/gateway.js +302 -68
  34. package/dist/gateway.js.map +1 -1
  35. package/dist/http-proxy.d.ts +126 -0
  36. package/dist/http-proxy.d.ts.map +1 -0
  37. package/dist/http-proxy.js +875 -0
  38. package/dist/http-proxy.js.map +1 -0
  39. package/dist/index.d.ts +3 -0
  40. package/dist/index.d.ts.map +1 -1
  41. package/dist/index.js +2 -0
  42. package/dist/index.js.map +1 -1
  43. package/dist/pattern-matcher.d.ts +25 -0
  44. package/dist/pattern-matcher.d.ts.map +1 -1
  45. package/dist/pattern-matcher.js +65 -0
  46. package/dist/pattern-matcher.js.map +1 -1
  47. package/dist/sonoma-client.d.ts +17 -1
  48. package/dist/sonoma-client.d.ts.map +1 -1
  49. package/dist/sonoma-client.js +67 -43
  50. package/dist/sonoma-client.js.map +1 -1
  51. package/dist/types.d.ts +14 -0
  52. package/dist/types.d.ts.map +1 -1
  53. package/package.json +1 -1
@@ -0,0 +1,256 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { isToolBlockedByPolicy, matchesToolPattern, getPatternSpecificity, } from "../pattern-matcher.js";
3
+ // ---------------------------------------------------------------------------
4
+ // matchesToolPattern
5
+ // ---------------------------------------------------------------------------
6
+ describe("matchesToolPattern", () => {
7
+ it("matches exact tool name", () => {
8
+ expect(matchesToolPattern("read_file", "read_file")).toBe(true);
9
+ });
10
+ it("rejects non-matching exact name", () => {
11
+ expect(matchesToolPattern("write_file", "read_file")).toBe(false);
12
+ });
13
+ it("matches prefix wildcard", () => {
14
+ expect(matchesToolPattern("read_file", "read_*")).toBe(true);
15
+ expect(matchesToolPattern("read_directory", "read_*")).toBe(true);
16
+ });
17
+ it("rejects non-matching prefix wildcard", () => {
18
+ expect(matchesToolPattern("write_file", "read_*")).toBe(false);
19
+ });
20
+ it("matches suffix wildcard", () => {
21
+ expect(matchesToolPattern("user_delete", "*_delete")).toBe(true);
22
+ expect(matchesToolPattern("file_delete", "*_delete")).toBe(true);
23
+ });
24
+ it("matches infix wildcard", () => {
25
+ expect(matchesToolPattern("get_user_info", "get_*_info")).toBe(true);
26
+ expect(matchesToolPattern("get_file_info", "get_*_info")).toBe(true);
27
+ });
28
+ it("matches universal wildcard", () => {
29
+ expect(matchesToolPattern("anything", "*")).toBe(true);
30
+ });
31
+ });
32
+ // ---------------------------------------------------------------------------
33
+ // getPatternSpecificity
34
+ // ---------------------------------------------------------------------------
35
+ describe("getPatternSpecificity", () => {
36
+ it("returns 0 for server-level (null)", () => {
37
+ expect(getPatternSpecificity(null)).toBe(0);
38
+ });
39
+ it("returns 1 for universal wildcard", () => {
40
+ expect(getPatternSpecificity("*")).toBe(1);
41
+ });
42
+ it("returns 100 for exact name", () => {
43
+ expect(getPatternSpecificity("read_file")).toBe(100);
44
+ });
45
+ it("orders wildcard patterns by non-wildcard character count", () => {
46
+ // "r*" has 1 non-wildcard char, "read_*" has 5
47
+ expect(getPatternSpecificity("r*")).toBeLessThan(getPatternSpecificity("read_*"));
48
+ });
49
+ });
50
+ // ---------------------------------------------------------------------------
51
+ // isToolBlockedByPolicy
52
+ // ---------------------------------------------------------------------------
53
+ describe("isToolBlockedByPolicy", () => {
54
+ // -----------------------------------------------------------------------
55
+ // Basics
56
+ // -----------------------------------------------------------------------
57
+ it("returns false when policy list is empty", () => {
58
+ expect(isToolBlockedByPolicy([], "slack", "search", null)).toBe(false);
59
+ });
60
+ it("returns false when no rules match the server", () => {
61
+ const rules = [
62
+ { identifier: "github", status: "blocked", toolPattern: null, priority: 0 },
63
+ ];
64
+ expect(isToolBlockedByPolicy(rules, "slack", "search", null)).toBe(false);
65
+ });
66
+ // -----------------------------------------------------------------------
67
+ // Server-level blocking
68
+ // -----------------------------------------------------------------------
69
+ it("blocks all tools when server-level rule is blocked", () => {
70
+ const rules = [
71
+ { identifier: "slack", status: "blocked", toolPattern: null, priority: 0 },
72
+ ];
73
+ expect(isToolBlockedByPolicy(rules, "slack", "search", null)).toBe(true);
74
+ expect(isToolBlockedByPolicy(rules, "slack", "send_message", null)).toBe(true);
75
+ });
76
+ it("allows all tools when server-level rule is allowed", () => {
77
+ const rules = [
78
+ { identifier: "slack", status: "allowed", toolPattern: null, priority: 0 },
79
+ ];
80
+ expect(isToolBlockedByPolicy(rules, "slack", "search", null)).toBe(false);
81
+ });
82
+ // -----------------------------------------------------------------------
83
+ // Tool-level blocking (exact name)
84
+ // -----------------------------------------------------------------------
85
+ it("blocks a specific tool by exact name", () => {
86
+ const rules = [
87
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
88
+ ];
89
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null)).toBe(true);
90
+ });
91
+ it("does not block other tools when only one tool is blocked", () => {
92
+ const rules = [
93
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
94
+ ];
95
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null)).toBe(false);
96
+ });
97
+ // -----------------------------------------------------------------------
98
+ // Tool-level blocking (wildcard patterns)
99
+ // -----------------------------------------------------------------------
100
+ it("blocks tools matching a wildcard pattern", () => {
101
+ const rules = [
102
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_*", priority: 10 },
103
+ ];
104
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", null)).toBe(true);
105
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null)).toBe(true);
106
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null)).toBe(false);
107
+ });
108
+ // -----------------------------------------------------------------------
109
+ // Package name matching
110
+ // -----------------------------------------------------------------------
111
+ it("matches rules by packageName when provided", () => {
112
+ const rules = [
113
+ { identifier: "@anthropic/slack-mcp", status: "blocked", toolPattern: "slack_search_*", priority: 10 },
114
+ ];
115
+ // Server name doesn't match, but packageName does
116
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", "@anthropic/slack-mcp")).toBe(true);
117
+ });
118
+ it("falls back to server name when packageName doesn't match", () => {
119
+ const rules = [
120
+ { identifier: "slack", status: "blocked", toolPattern: null, priority: 0 },
121
+ ];
122
+ expect(isToolBlockedByPolicy(rules, "slack", "search", "@some/other-package")).toBe(true);
123
+ });
124
+ // -----------------------------------------------------------------------
125
+ // Global wildcard identifier
126
+ // -----------------------------------------------------------------------
127
+ it("blocks tools from any server when identifier is *", () => {
128
+ const rules = [
129
+ { identifier: "*", status: "blocked", toolPattern: "dangerous_*", priority: 10 },
130
+ ];
131
+ expect(isToolBlockedByPolicy(rules, "slack", "dangerous_delete", null)).toBe(true);
132
+ expect(isToolBlockedByPolicy(rules, "github", "dangerous_reset", null)).toBe(true);
133
+ expect(isToolBlockedByPolicy(rules, "slack", "safe_read", null)).toBe(false);
134
+ });
135
+ // -----------------------------------------------------------------------
136
+ // Priority resolution
137
+ // -----------------------------------------------------------------------
138
+ it("higher priority rule wins over lower priority", () => {
139
+ const rules = [
140
+ // Server-level: allowed at priority 0
141
+ { identifier: "slack", status: "allowed", toolPattern: null, priority: 0 },
142
+ // Tool-level: blocked at priority 20 (higher, wins)
143
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
144
+ ];
145
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null)).toBe(true);
146
+ // Other tools still allowed
147
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null)).toBe(false);
148
+ });
149
+ it("tool-level allow overrides server-level block at higher priority", () => {
150
+ const rules = [
151
+ // Server-level: blocked at priority 0
152
+ { identifier: "slack", status: "blocked", toolPattern: null, priority: 0 },
153
+ // Tool-level: allowed at priority 20 (higher, wins)
154
+ { identifier: "slack", status: "allowed", toolPattern: "slack_send_message", priority: 20 },
155
+ ];
156
+ // The allowed tool is not blocked
157
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null)).toBe(false);
158
+ // Other tools still blocked by server-level rule
159
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", null)).toBe(true);
160
+ });
161
+ // -----------------------------------------------------------------------
162
+ // Deny wins at same priority
163
+ // -----------------------------------------------------------------------
164
+ it("deny wins when two rules have the same priority and specificity", () => {
165
+ const rules = [
166
+ { identifier: "slack", status: "allowed", toolPattern: "slack_search_public", priority: 20 },
167
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_public", priority: 20 },
168
+ ];
169
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", null)).toBe(true);
170
+ });
171
+ // -----------------------------------------------------------------------
172
+ // Specificity resolution
173
+ // -----------------------------------------------------------------------
174
+ it("more specific pattern wins at same priority", () => {
175
+ const rules = [
176
+ // Wildcard pattern: block all search tools (less specific)
177
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_*", priority: 10 },
178
+ // Exact name: allow this specific search tool (more specific)
179
+ { identifier: "slack", status: "allowed", toolPattern: "slack_search_public", priority: 10 },
180
+ ];
181
+ // Exact name (specificity 100) wins over wildcard (specificity ~22)
182
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", null)).toBe(false);
183
+ // Other search tools still blocked
184
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_private", null)).toBe(true);
185
+ });
186
+ // -----------------------------------------------------------------------
187
+ // Multiple tool blocks on same server
188
+ // -----------------------------------------------------------------------
189
+ it("blocks multiple specific tools on the same server", () => {
190
+ const rules = [
191
+ { identifier: "slack", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
192
+ { identifier: "slack", status: "blocked", toolPattern: "slack_send_message", priority: 20 },
193
+ ];
194
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null)).toBe(true);
195
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null)).toBe(true);
196
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_read_channel", null)).toBe(false);
197
+ });
198
+ // -----------------------------------------------------------------------
199
+ // URL-based identifier matching
200
+ // -----------------------------------------------------------------------
201
+ it("matches rules by URL when server name and packageName don't match", () => {
202
+ const rules = [
203
+ { identifier: "https://mcp.slack.com/mcp", status: "blocked", toolPattern: "slack_search_*", priority: 10 },
204
+ ];
205
+ // Server name "slack" and packageName don't match, but URL does
206
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public", null, "https://mcp.slack.com/mcp")).toBe(true);
207
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null, "https://mcp.slack.com/mcp")).toBe(false);
208
+ });
209
+ // -----------------------------------------------------------------------
210
+ // Real-world Slack scenario: policy uses packageName, gateway has URL
211
+ // -----------------------------------------------------------------------
212
+ it("blocks Slack tools matching the real-world policy shape", () => {
213
+ // Mirrors the actual policy returned by the Sonoma API
214
+ const rules = [
215
+ { identifier: "https://mcp.slack.com/mcp", status: "gateway", toolPattern: null, priority: 0 },
216
+ { identifier: "slack-mcp-server", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
217
+ { identifier: "slack-mcp-server", status: "blocked", toolPattern: "slack_send_message", priority: 20 },
218
+ ];
219
+ // Gateway config: server name "slack", no packageName, URL "https://mcp.slack.com/mcp"
220
+ // The tool-level rules use "slack-mcp-server" which doesn't match server name or URL,
221
+ // but if packageName is resolved to "slack-mcp-server" it will match
222
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", "slack-mcp-server", "https://mcp.slack.com/mcp")).toBe(true);
223
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", "slack-mcp-server", "https://mcp.slack.com/mcp")).toBe(true);
224
+ // Unblocked tools are still allowed
225
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_read_channel", "slack-mcp-server", "https://mcp.slack.com/mcp")).toBe(false);
226
+ });
227
+ // -----------------------------------------------------------------------
228
+ // Extra identifiers (alias resolution from policy)
229
+ // -----------------------------------------------------------------------
230
+ it("matches rules via extraIdentifiers (alias resolution)", () => {
231
+ // Tool-level rules use the canonical package name
232
+ const rules = [
233
+ { identifier: "slack-mcp-server", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
234
+ ];
235
+ // Gateway only knows server name "slack" and URL, but aliases resolve
236
+ // "slack-mcp-server" from the identifierAliases map
237
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null, "https://mcp.slack.com/mcp", ["slack-mcp-server"])).toBe(true);
238
+ // Non-blocked tools still allowed
239
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_read_channel", null, "https://mcp.slack.com/mcp", ["slack-mcp-server"])).toBe(false);
240
+ });
241
+ it("matches the exact real-world scenario: gateway has URL, policy has packageName", () => {
242
+ // Real policy from the Sonoma API
243
+ const rules = [
244
+ { identifier: "https://mcp.slack.com/mcp", status: "gateway", toolPattern: null, priority: 0 },
245
+ { identifier: "slack-mcp-server", status: "blocked", toolPattern: "slack_search_public_and_private", priority: 20 },
246
+ { identifier: "slack-mcp-server", status: "blocked", toolPattern: "slack_send_message", priority: 20 },
247
+ ];
248
+ // Gateway: name="slack", no packageName, url="https://mcp.slack.com/mcp"
249
+ // identifierAliases from policy: {"https://mcp.slack.com/mcp": ["slack-mcp-server"]}
250
+ const aliases = ["slack-mcp-server"];
251
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_search_public_and_private", null, "https://mcp.slack.com/mcp", aliases)).toBe(true);
252
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_send_message", null, "https://mcp.slack.com/mcp", aliases)).toBe(true);
253
+ expect(isToolBlockedByPolicy(rules, "slack", "slack_read_channel", null, "https://mcp.slack.com/mcp", aliases)).toBe(false);
254
+ });
255
+ });
256
+ //# sourceMappingURL=tool-blocking.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-blocking.test.js","sourceRoot":"","sources":["../../src/__tests__/tool-blocking.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,34 @@
1
+ /**
2
+ * OS Keychain abstraction for secure credential storage.
3
+ *
4
+ * Uses native OS credential stores via CLI tools (no native modules needed):
5
+ * - macOS: Keychain Access via `security` CLI
6
+ * - Windows: Credential Manager via PowerShell
7
+ * - Linux/other: Falls back to encrypted file storage (existing behavior)
8
+ *
9
+ * All credentials are stored as JSON strings, keyed by (service, account).
10
+ */
11
+ /**
12
+ * Store a credential in the OS keychain.
13
+ * @param account - Unique key within the service (e.g., "sonoma-auth", "upstream:https://mcp.slack.com")
14
+ * @param data - Credential data (will be JSON-stringified)
15
+ */
16
+ export declare function keychainSet(account: string, data: unknown): void;
17
+ /**
18
+ * Retrieve a credential from the OS keychain.
19
+ * @returns Parsed JSON data, or null if not found
20
+ */
21
+ export declare function keychainGet<T = unknown>(account: string): T | null;
22
+ /**
23
+ * Delete a credential from the OS keychain.
24
+ */
25
+ export declare function keychainDelete(account: string): void;
26
+ /**
27
+ * Delete all credentials for the service from the OS keychain.
28
+ */
29
+ export declare function keychainDeleteAll(): void;
30
+ /**
31
+ * Check whether OS keychain is available on this platform.
32
+ */
33
+ export declare function keychainAvailable(): boolean;
34
+ //# sourceMappingURL=keychain.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.d.ts","sourceRoot":"","sources":["../../src/auth/keychain.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AAQH;;;;GAIG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,GAAG,IAAI,CAWhE;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,CAAC,GAAG,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAmBlE;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAUpD;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,IAAI,CAUxC;AAED;;GAEG;AACH,wBAAgB,iBAAiB,IAAI,OAAO,CAM3C"}
@@ -0,0 +1,305 @@
1
+ /**
2
+ * OS Keychain abstraction for secure credential storage.
3
+ *
4
+ * Uses native OS credential stores via CLI tools (no native modules needed):
5
+ * - macOS: Keychain Access via `security` CLI
6
+ * - Windows: Credential Manager via PowerShell
7
+ * - Linux/other: Falls back to encrypted file storage (existing behavior)
8
+ *
9
+ * All credentials are stored as JSON strings, keyed by (service, account).
10
+ */
11
+ import { execSync } from "node:child_process";
12
+ const SERVICE_NAME = "sonoma-mcp-gateway";
13
+ /**
14
+ * Store a credential in the OS keychain.
15
+ * @param account - Unique key within the service (e.g., "sonoma-auth", "upstream:https://mcp.slack.com")
16
+ * @param data - Credential data (will be JSON-stringified)
17
+ */
18
+ export function keychainSet(account, data) {
19
+ const json = JSON.stringify(data);
20
+ const platform = process.platform;
21
+ if (platform === "darwin") {
22
+ macosSet(account, json);
23
+ }
24
+ else if (platform === "win32") {
25
+ windowsSet(account, json);
26
+ }
27
+ else {
28
+ throw new Error(`Keychain not supported on ${platform}`);
29
+ }
30
+ }
31
+ /**
32
+ * Retrieve a credential from the OS keychain.
33
+ * @returns Parsed JSON data, or null if not found
34
+ */
35
+ export function keychainGet(account) {
36
+ const platform = process.platform;
37
+ let json;
38
+ if (platform === "darwin") {
39
+ json = macosGet(account);
40
+ }
41
+ else if (platform === "win32") {
42
+ json = windowsGet(account);
43
+ }
44
+ else {
45
+ throw new Error(`Keychain not supported on ${platform}`);
46
+ }
47
+ if (json === null)
48
+ return null;
49
+ try {
50
+ return JSON.parse(json);
51
+ }
52
+ catch {
53
+ return null;
54
+ }
55
+ }
56
+ /**
57
+ * Delete a credential from the OS keychain.
58
+ */
59
+ export function keychainDelete(account) {
60
+ const platform = process.platform;
61
+ if (platform === "darwin") {
62
+ macosDelete(account);
63
+ }
64
+ else if (platform === "win32") {
65
+ windowsDelete(account);
66
+ }
67
+ else {
68
+ throw new Error(`Keychain not supported on ${platform}`);
69
+ }
70
+ }
71
+ /**
72
+ * Delete all credentials for the service from the OS keychain.
73
+ */
74
+ export function keychainDeleteAll() {
75
+ const platform = process.platform;
76
+ if (platform === "darwin") {
77
+ macosDeleteAll();
78
+ }
79
+ else if (platform === "win32") {
80
+ windowsDeleteAll();
81
+ }
82
+ else {
83
+ throw new Error(`Keychain not supported on ${platform}`);
84
+ }
85
+ }
86
+ /**
87
+ * Check whether OS keychain is available on this platform.
88
+ */
89
+ export function keychainAvailable() {
90
+ const platform = process.platform;
91
+ if (platform === "darwin" || platform === "win32") {
92
+ return true;
93
+ }
94
+ return false;
95
+ }
96
+ // ---------------------------------------------------------------------------
97
+ // macOS Keychain via `security` CLI
98
+ // ---------------------------------------------------------------------------
99
+ function macosSet(account, password) {
100
+ // -U = update if exists, -s = service, -a = account, -w = password
101
+ // Accepted risk: macOS `security` CLI requires -w <password> as a CLI argument,
102
+ // which briefly exposes it in the process list. There is no stdin alternative for
103
+ // add-generic-password. The exposure window is sub-second and local-only.
104
+ execSync(`/usr/bin/security add-generic-password -s ${shellEscape(SERVICE_NAME)} -a ${shellEscape(account)} -U -w ${shellEscape(password)}`, { stdio: "pipe" });
105
+ }
106
+ function macosGet(account) {
107
+ try {
108
+ const result = execSync(`/usr/bin/security find-generic-password -s ${shellEscape(SERVICE_NAME)} -a ${shellEscape(account)} -w`, { stdio: "pipe", encoding: "utf-8" });
109
+ return result.trim();
110
+ }
111
+ catch {
112
+ // Not found (exit code 44) or other error
113
+ return null;
114
+ }
115
+ }
116
+ function macosDelete(account) {
117
+ try {
118
+ execSync(`/usr/bin/security delete-generic-password -s ${shellEscape(SERVICE_NAME)} -a ${shellEscape(account)}`, { stdio: "pipe" });
119
+ }
120
+ catch {
121
+ // Ignore if not found
122
+ }
123
+ }
124
+ function macosDeleteAll() {
125
+ // Delete entries one at a time until none remain
126
+ for (let i = 0; i < 100; i++) {
127
+ try {
128
+ execSync(`/usr/bin/security delete-generic-password -s ${shellEscape(SERVICE_NAME)}`, { stdio: "pipe" });
129
+ }
130
+ catch {
131
+ break; // No more entries
132
+ }
133
+ }
134
+ }
135
+ // ---------------------------------------------------------------------------
136
+ // Windows Credential Manager via PowerShell
137
+ // ---------------------------------------------------------------------------
138
+ function windowsTargetName(account) {
139
+ return `${SERVICE_NAME}:${account}`;
140
+ }
141
+ function windowsSet(account, password) {
142
+ const target = windowsTargetName(account);
143
+ // Use CredWrite via .NET interop for arbitrary credential storage.
144
+ // Base64-encode the password to avoid escaping issues in PowerShell.
145
+ const b64 = Buffer.from(password, "utf-8").toString("base64");
146
+ const ps = `
147
+ $ErrorActionPreference='Stop'
148
+ Add-Type -TypeDefinition @"
149
+ using System;
150
+ using System.Runtime.InteropServices;
151
+ using System.Text;
152
+
153
+ public class CredManager {
154
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
155
+ public static extern bool CredWriteW(ref CREDENTIAL cred, uint flags);
156
+ [DllImport("advapi32.dll", SetLastError=true)]
157
+ public static extern bool CredFree(IntPtr buffer);
158
+
159
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
160
+ public struct CREDENTIAL {
161
+ public uint Flags;
162
+ public uint Type;
163
+ public string TargetName;
164
+ public string Comment;
165
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
166
+ public uint CredentialBlobSize;
167
+ public IntPtr CredentialBlob;
168
+ public uint Persist;
169
+ public uint AttributeCount;
170
+ public IntPtr Attributes;
171
+ public string TargetAlias;
172
+ public string UserName;
173
+ }
174
+
175
+ public static void Write(string target, string secret) {
176
+ byte[] bytes = Encoding.UTF8.GetBytes(secret);
177
+ IntPtr blob = Marshal.AllocHGlobal(bytes.Length);
178
+ Marshal.Copy(bytes, 0, blob, bytes.Length);
179
+ CREDENTIAL cred = new CREDENTIAL();
180
+ cred.Type = 1; // CRED_TYPE_GENERIC
181
+ cred.TargetName = target;
182
+ cred.CredentialBlobSize = (uint)bytes.Length;
183
+ cred.CredentialBlob = blob;
184
+ cred.Persist = 2; // CRED_PERSIST_LOCAL_MACHINE
185
+ cred.UserName = "sonoma-gateway";
186
+ bool ok = CredWriteW(ref cred, 0);
187
+ Marshal.FreeHGlobal(blob);
188
+ if (!ok) throw new Exception("CredWrite failed: " + Marshal.GetLastWin32Error());
189
+ }
190
+ }
191
+ "@
192
+ $targetB64 = '${Buffer.from(target, "utf-8").toString("base64")}'
193
+ $target = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($targetB64))
194
+ $secret = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String('${b64}'))
195
+ [CredManager]::Write($target, $secret)
196
+ `;
197
+ execSync(`powershell -NoProfile -NonInteractive -Command "${ps.replace(/"/g, '\\"')}"`, {
198
+ stdio: "pipe",
199
+ windowsHide: true,
200
+ });
201
+ }
202
+ function windowsGet(account) {
203
+ const target = windowsTargetName(account);
204
+ const ps = `
205
+ $ErrorActionPreference='Stop'
206
+ Add-Type -TypeDefinition @"
207
+ using System;
208
+ using System.Runtime.InteropServices;
209
+ using System.Text;
210
+
211
+ public class CredReader {
212
+ [DllImport("advapi32.dll", SetLastError=true, CharSet=CharSet.Unicode)]
213
+ public static extern bool CredReadW(string target, uint type, uint flags, out IntPtr cred);
214
+ [DllImport("advapi32.dll", SetLastError=true)]
215
+ public static extern bool CredFree(IntPtr buffer);
216
+
217
+ [StructLayout(LayoutKind.Sequential, CharSet=CharSet.Unicode)]
218
+ public struct CREDENTIAL {
219
+ public uint Flags;
220
+ public uint Type;
221
+ public string TargetName;
222
+ public string Comment;
223
+ public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten;
224
+ public uint CredentialBlobSize;
225
+ public IntPtr CredentialBlob;
226
+ public uint Persist;
227
+ public uint AttributeCount;
228
+ public IntPtr Attributes;
229
+ public string TargetAlias;
230
+ public string UserName;
231
+ }
232
+
233
+ public static string Read(string target) {
234
+ IntPtr credPtr;
235
+ bool ok = CredReadW(target, 1, 0, out credPtr);
236
+ if (!ok) return null;
237
+ CREDENTIAL cred = (CREDENTIAL)Marshal.PtrToStructure(credPtr, typeof(CREDENTIAL));
238
+ byte[] bytes = new byte[cred.CredentialBlobSize];
239
+ Marshal.Copy(cred.CredentialBlob, bytes, 0, bytes.Length);
240
+ CredFree(credPtr);
241
+ return Encoding.UTF8.GetString(bytes);
242
+ }
243
+ }
244
+ "@
245
+ $targetB64 = '${Buffer.from(target, "utf-8").toString("base64")}'
246
+ $t = [System.Text.Encoding]::UTF8.GetString([System.Convert]::FromBase64String($targetB64))
247
+ $result = [CredReader]::Read($t)
248
+ if ($result -eq $null) { exit 1 }
249
+ [Console]::Write($result)
250
+ `;
251
+ try {
252
+ return execSync(`powershell -NoProfile -NonInteractive -Command "${ps.replace(/"/g, '\\"')}"`, { stdio: "pipe", encoding: "utf-8", windowsHide: true });
253
+ }
254
+ catch {
255
+ return null;
256
+ }
257
+ }
258
+ function windowsDelete(account) {
259
+ const target = windowsTargetName(account);
260
+ try {
261
+ execSync(`cmdkey /delete:${target}`, { stdio: "pipe", windowsHide: true });
262
+ }
263
+ catch {
264
+ // Ignore if not found
265
+ }
266
+ }
267
+ function windowsDeleteAll() {
268
+ // List all credentials matching our service prefix and delete them
269
+ try {
270
+ const output = execSync("cmdkey /list", {
271
+ stdio: "pipe",
272
+ encoding: "utf-8",
273
+ windowsHide: true,
274
+ });
275
+ const prefix = `${SERVICE_NAME}:`;
276
+ for (const line of output.split("\n")) {
277
+ const match = line.match(/Target:\s*(.+)/i);
278
+ if (match) {
279
+ const target = match[1].trim();
280
+ if (target.startsWith(prefix)) {
281
+ try {
282
+ execSync(`cmdkey /delete:${target}`, { stdio: "pipe", windowsHide: true });
283
+ }
284
+ catch {
285
+ // Ignore individual delete failures
286
+ }
287
+ }
288
+ }
289
+ }
290
+ }
291
+ catch {
292
+ // Ignore errors
293
+ }
294
+ }
295
+ // ---------------------------------------------------------------------------
296
+ // Helpers
297
+ // ---------------------------------------------------------------------------
298
+ /**
299
+ * Shell-escape a string for use in a command argument.
300
+ * Wraps in single quotes and escapes embedded single quotes.
301
+ */
302
+ function shellEscape(s) {
303
+ return `'${s.replace(/'/g, "'\\''")}'`;
304
+ }
305
+ //# sourceMappingURL=keychain.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain.js","sourceRoot":"","sources":["../../src/auth/keychain.ts"],"names":[],"mappings":"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"}