@onyxsecurity/mcp-gateway 1.0.64 → 1.0.66
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/dist/bin/mcp-gateway.js +2 -1
- package/dist/bin/mcp-gateway.js.map +1 -1
- package/dist/{cli-dCYJQfgz.js → cli-DQlVGyV_.js} +2 -2
- package/dist/{cli-dCYJQfgz.js.map → cli-DQlVGyV_.js.map} +1 -1
- package/dist/{env-CrNjkYXT.js → env-BU-k04bk.js} +2 -2
- package/dist/{env-CrNjkYXT.js.map → env-BU-k04bk.js.map} +1 -1
- package/dist/{env-C-Ukdloh.js → env-CPU34WM0.js} +1 -1
- package/dist/index.js +1 -1
- package/dist/{logger-BQ4Ry3vv.js → logger-BwqGcO50.js} +2 -2
- package/dist/{logger-BQ4Ry3vv.js.map → logger-BwqGcO50.js.map} +1 -1
- package/dist/main-B46IHeJX.js +8 -0
- package/dist/main-B46IHeJX.js.map +1 -0
- package/dist/{normalizeUrl-u9rsNRmG.js → normalizeUrl-fkFv5Mfb.js} +2 -2
- package/dist/{normalizeUrl-u9rsNRmG.js.map → normalizeUrl-fkFv5Mfb.js.map} +1 -1
- package/dist/{oauthProvidersClient-CHQUDi12.js → oauthProvidersClient-CoyIrpuA.js} +2 -2
- package/dist/{oauthProvidersClient-CHQUDi12.js.map → oauthProvidersClient-CoyIrpuA.js.map} +1 -1
- package/dist/oauthProvidersClient-DqXf4kK7.js +1 -0
- package/dist/{providers-C1fyJhIB.js → providers-xwC_AxDC.js} +2 -2
- package/dist/{providers-C1fyJhIB.js.map → providers-xwC_AxDC.js.map} +1 -1
- package/dist/secretstore-Deoqfk8j.js +2 -0
- package/dist/secretstore-Deoqfk8j.js.map +1 -0
- package/dist/{stdio-CtGhfXjF.js → stdio-DLPm2NHB.js} +9 -9
- package/dist/stdio-DLPm2NHB.js.map +1 -0
- package/dist/transportDetection-XcdORTci.js +136 -0
- package/dist/transportDetection-XcdORTci.js.map +1 -0
- package/package.json +3 -2
- package/dist/main-Cnt1xxo5.js +0 -8
- package/dist/main-Cnt1xxo5.js.map +0 -1
- package/dist/oauthProvidersClient-dKwkd2bN.js +0 -1
- package/dist/stdio-CtGhfXjF.js.map +0 -1
- package/dist/transportDetection-Mo0c1sQH.js +0 -136
- package/dist/transportDetection-Mo0c1sQH.js.map +0 -1
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{logger as e}from"./logger-
|
|
2
|
-
//# sourceMappingURL=oauthProvidersClient-
|
|
1
|
+
import{logger as e}from"./logger-BwqGcO50.js";import{setOAuthProviders as t}from"./providers-xwC_AxDC.js";async function n(e){let t=new URL(e.url),n=t.hostname===`localhost`||t.hostname===`127.0.0.1`||t.hostname===`::1`||t.hostname===`[::1]`;if(t.protocol!==`https:`&&!n)throw Error(`OAuth providers URL must use HTTPS, got ${t.protocol}`);let r=new AbortController,i=setTimeout(()=>r.abort(),e.timeoutMs);try{let t=await fetch(e.url,{headers:{Accept:`application/json`,apikey:e.apiKey},method:`GET`,signal:r.signal});if(!t.ok)throw Error(`OAuth providers service returned ${t.status}: ${t.statusText}`);let n=await t.json();return Array.isArray(n.providers)?n.providers:[]}catch(t){throw t instanceof Error&&t.name===`AbortError`?Error(`OAuth providers fetch timed out after ${e.timeoutMs}ms`):t}finally{clearTimeout(i)}}async function r(r){if(!r.enabled||!r.url||!r.apiKey){t([]),e.debug(`OAuth providers fetch disabled (missing URL or API key); skipping`);return}try{let i=await n({apiKey:r.apiKey,timeoutMs:r.timeoutMs,url:r.url});t(i),e.info(`OAuth providers loaded`,{count:i.length,ids:i.map(e=>e.id)})}catch(n){t([]),e.warn(`OAuth providers fetch failed; falling back to DCR`,{error:String(n),url:r.url})}}export{n as fetchOAuthProviders,r as initializeOAuthProviders};
|
|
2
|
+
//# sourceMappingURL=oauthProvidersClient-CoyIrpuA.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauthProvidersClient-
|
|
1
|
+
{"version":3,"file":"oauthProvidersClient-CoyIrpuA.js","names":[],"sources":["../src/auth/oauthProvidersClient.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\nimport { setOAuthProviders } from \"./providers.js\";\n\nexport interface InitializeOAuthProvidersConfig {\n apiKey: string | undefined;\n enabled: boolean;\n timeoutMs: number;\n url: null | string;\n}\n\nexport interface OAuthProvidersClientConfig {\n apiKey: string;\n timeoutMs: number;\n url: string;\n}\n\ninterface OAuthProvidersResponse {\n providers?: OAuthProviderConfig[];\n}\n\n/**\n * Fetches the OAuth provider list from the web extension service.\n *\n * The endpoint is gated by Kong's key-auth plugin, which validates the\n * `apikey` header and forwards a per-tenant `x-consumer-username` header\n * (e.g. `mcp-gateway@<tenantUUID>`) to the backend.\n *\n * Throws on network error, timeout, or non-2xx response. Callers are\n * expected to handle errors and decide on a fallback (initializeOAuthProviders\n * fails open by emptying the in-memory provider list, which causes the OAuth\n * flow to fall through to Dynamic Client Registration).\n */\nexport async function fetchOAuthProviders(\n cfg: OAuthProvidersClientConfig\n): Promise<OAuthProviderConfig[]> {\n // Reject non-HTTPS URLs before sending the API key over the wire.\n // Loopback addresses are allowed for local development against a non-TLS\n // stub: \"localhost\", IPv4 (127.0.0.1), and IPv6 (::1). Node returns the\n // bracketed form (\"[::1]\") for IPv6 hosts in URL.hostname, so match both.\n const parsed = new URL(cfg.url);\n const isLocal =\n parsed.hostname === \"localhost\" ||\n parsed.hostname === \"127.0.0.1\" ||\n parsed.hostname === \"::1\" ||\n parsed.hostname === \"[::1]\";\n if (parsed.protocol !== \"https:\" && !isLocal) {\n throw new Error(`OAuth providers URL must use HTTPS, got ${parsed.protocol}`);\n }\n\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), cfg.timeoutMs);\n\n try {\n const response = await fetch(cfg.url, {\n // GET with no body — Accept signals the desired response format,\n // Content-Type would be misleading.\n headers: {\n Accept: \"application/json\",\n apikey: cfg.apiKey,\n },\n method: \"GET\",\n signal: controller.signal,\n });\n\n if (!response.ok) {\n throw new Error(`OAuth providers service returned ${response.status}: ${response.statusText}`);\n }\n\n const body = (await response.json()) as OAuthProvidersResponse;\n return Array.isArray(body.providers) ? body.providers : [];\n } catch (error) {\n if (error instanceof Error && error.name === \"AbortError\") {\n throw new Error(`OAuth providers fetch timed out after ${cfg.timeoutMs}ms`);\n }\n throw error;\n } finally {\n clearTimeout(timer);\n }\n}\n\n/**\n * Initializes the in-memory OAuth provider store at gateway startup.\n *\n * On a successful fetch, populates the store with the providers returned by\n * the web extension service. On any failure (disabled config, network error,\n * timeout, malformed body), empties the store and logs a warning. The OAuth\n * flow at oauthFlow.ts then falls through to Dynamic Client Registration,\n * matching the gateway's \"fail-safe by default\" convention.\n */\nexport async function initializeOAuthProviders(cfg: InitializeOAuthProvidersConfig): Promise<void> {\n if (!cfg.enabled || !cfg.url || !cfg.apiKey) {\n setOAuthProviders([]);\n logger.debug(\"OAuth providers fetch disabled (missing URL or API key); skipping\");\n return;\n }\n\n try {\n const providers = await fetchOAuthProviders({\n apiKey: cfg.apiKey,\n timeoutMs: cfg.timeoutMs,\n url: cfg.url,\n });\n setOAuthProviders(providers);\n logger.info(\"OAuth providers loaded\", {\n count: providers.length,\n ids: providers.map((p) => p.id),\n });\n } catch (error) {\n setOAuthProviders([]);\n logger.warn(\"OAuth providers fetch failed; falling back to DCR\", {\n error: String(error),\n url: cfg.url,\n });\n }\n}\n"],"mappings":"0GAkCA,eAAsB,EACpB,EACgC,CAKhC,IAAM,EAAS,IAAI,IAAI,EAAI,IAAI,CACzB,EACJ,EAAO,WAAa,aACpB,EAAO,WAAa,aACpB,EAAO,WAAa,OACpB,EAAO,WAAa,QACtB,GAAI,EAAO,WAAa,UAAY,CAAC,EACnC,MAAU,MAAM,2CAA2C,EAAO,WAAW,CAG/E,IAAM,EAAa,IAAI,gBACjB,EAAQ,eAAiB,EAAW,OAAO,CAAE,EAAI,UAAU,CAEjE,GAAI,CACF,IAAM,EAAW,MAAM,MAAM,EAAI,IAAK,CAGpC,QAAS,CACP,OAAQ,mBACR,OAAQ,EAAI,OACb,CACD,OAAQ,MACR,OAAQ,EAAW,OACpB,CAAC,CAEF,GAAI,CAAC,EAAS,GACZ,MAAU,MAAM,oCAAoC,EAAS,OAAO,IAAI,EAAS,aAAa,CAGhG,IAAM,EAAQ,MAAM,EAAS,MAAM,CACnC,OAAO,MAAM,QAAQ,EAAK,UAAU,CAAG,EAAK,UAAY,EAAE,OACnD,EAAO,CAId,MAHI,aAAiB,OAAS,EAAM,OAAS,aACjC,MAAM,yCAAyC,EAAI,UAAU,IAAI,CAEvE,SACE,CACR,aAAa,EAAM,EAavB,eAAsB,EAAyB,EAAoD,CACjG,GAAI,CAAC,EAAI,SAAW,CAAC,EAAI,KAAO,CAAC,EAAI,OAAQ,CAC3C,EAAkB,EAAE,CAAC,CACrB,EAAO,MAAM,oEAAoE,CACjF,OAGF,GAAI,CACF,IAAM,EAAY,MAAM,EAAoB,CAC1C,OAAQ,EAAI,OACZ,UAAW,EAAI,UACf,IAAK,EAAI,IACV,CAAC,CACF,EAAkB,EAAU,CAC5B,EAAO,KAAK,yBAA0B,CACpC,MAAO,EAAU,OACjB,IAAK,EAAU,IAAK,GAAM,EAAE,GAAG,CAChC,CAAC,OACK,EAAO,CACd,EAAkB,EAAE,CAAC,CACrB,EAAO,KAAK,oDAAqD,CAC/D,MAAO,OAAO,EAAM,CACpB,IAAK,EAAI,IACV,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
import"./env-BU-k04bk.js";import"./logger-BwqGcO50.js";import"./providers-xwC_AxDC.js";import{fetchOAuthProviders as e,initializeOAuthProviders as t}from"./oauthProvidersClient-CoyIrpuA.js";export{t as initializeOAuthProviders};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import{logger as e}from"./logger-
|
|
2
|
-
//# sourceMappingURL=providers-
|
|
1
|
+
import{logger as e}from"./logger-BwqGcO50.js";let t=[];function n(n){let r=typeof n==`string`?new URL(n):n,o=r.hostname.toLowerCase();for(let n of t)for(let t of n.urlPatterns){let r=t.toLowerCase();if(a(o,r))return e.debug(`Found matching OAuth provider for URL`,{hostname:o,pattern:t,providerId:n.id,providerName:n.name}),i(n)}e.debug(`No pre-configured OAuth provider found for URL`,{hostname:o,serverUrl:r.toString()})}function r(e){t=e.map(i)}function i(e){return{...e,scopes:e.scopes?[...e.scopes]:void 0,urlPatterns:[...e.urlPatterns]}}function a(e,t){return!!(e===t||e.endsWith(`.${t}`))}export{n as findProviderForUrl,r as setOAuthProviders};
|
|
2
|
+
//# sourceMappingURL=providers-xwC_AxDC.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"providers-
|
|
1
|
+
{"version":3,"file":"providers-xwC_AxDC.js","names":["oauthProviders: OAuthProviderConfig[]"],"sources":["../src/auth/providers.ts"],"sourcesContent":["import type { OAuthProviderConfig } from \"../config/config.js\";\n\nimport { logger } from \"../lib/logger.js\";\n\n// Mutable store. Populated by setOAuthProviders() during startup once the\n// gateway has fetched provider config from the web extension service.\n// Pre-startup or when the fetch fails, the array stays empty and OAuth flows\n// fall through to Dynamic Client Registration via oauthFlow.ts.\nlet oauthProviders: OAuthProviderConfig[] = [];\n\n/**\n * Finds a pre-configured OAuth provider that matches the given server URL.\n *\n * @param serverUrl The MCP server URL to match against provider URL patterns\n * @returns The matching provider config, or undefined if no match found\n */\nexport function findProviderForUrl(serverUrl: string | URL): OAuthProviderConfig | undefined {\n const serverUrlObj = typeof serverUrl === \"string\" ? new URL(serverUrl) : serverUrl;\n const hostname = serverUrlObj.hostname.toLowerCase();\n\n for (const provider of oauthProviders) {\n for (const pattern of provider.urlPatterns) {\n // Pattern can be a hostname or a hostname with path prefix\n const patternLower = pattern.toLowerCase();\n\n // Check if pattern matches hostname (with or without subdomains)\n if (matchesHostname(hostname, patternLower)) {\n logger.debug(\"Found matching OAuth provider for URL\", {\n hostname,\n pattern,\n providerId: provider.id,\n providerName: provider.name,\n });\n // Return a defensive clone — callers in oauthFlow.ts must not be\n // able to corrupt the in-memory store via the returned reference.\n return cloneProvider(provider);\n }\n }\n }\n\n logger.debug(\"No pre-configured OAuth provider found for URL\", {\n hostname,\n serverUrl: serverUrlObj.toString(),\n });\n return undefined;\n}\n\n/**\n * Gets a provider by its ID.\n *\n * @param providerId The provider ID (e.g., \"github\")\n * @returns The provider config, or undefined if not found\n */\nexport function getProviderById(providerId: string): OAuthProviderConfig | undefined {\n const found = oauthProviders.find((p) => p.id === providerId);\n return found ? cloneProvider(found) : undefined;\n}\n\n/**\n * Lists all configured OAuth providers.\n * Useful for debugging and admin interfaces.\n *\n * @returns Array of provider summaries (without secrets)\n */\nexport function listProviders(): Array<{\n hasClientSecret: boolean;\n id: string;\n name: string;\n urlPatterns: string[];\n}> {\n return oauthProviders.map((p) => ({\n hasClientSecret: !!p.clientSecret,\n id: p.id,\n name: p.name,\n urlPatterns: [...p.urlPatterns],\n }));\n}\n\n/**\n * Replaces the in-memory OAuth provider list. Called once at gateway startup\n * after fetching from the web extension service. Pass an empty array to clear.\n *\n * Stores defensive shallow copies of each provider so that callers cannot\n * mutate the in-memory store via the references they passed in. The\n * urlPatterns array is also cloned for the same reason.\n */\nexport function setOAuthProviders(providers: OAuthProviderConfig[]): void {\n oauthProviders = providers.map(cloneProvider);\n}\n\n// cloneProvider returns a defensive shallow copy with array fields cloned,\n// so callers can read and even mutate the result without corrupting the\n// in-memory store. Used on every read path (findProviderForUrl,\n// getProviderById) to match the write-side contract in setOAuthProviders.\nfunction cloneProvider(p: OAuthProviderConfig): OAuthProviderConfig {\n return {\n ...p,\n scopes: p.scopes ? [...p.scopes] : undefined,\n urlPatterns: [...p.urlPatterns],\n };\n}\n\n/**\n * Checks if a hostname matches a pattern.\n * Supports exact matches and subdomain matches.\n *\n * @param hostname The hostname to check (e.g., \"api.github.com\")\n * @param pattern The pattern to match against (e.g., \"github.com\")\n * @returns true if the hostname matches the pattern\n */\nfunction matchesHostname(hostname: string, pattern: string): boolean {\n // Exact match\n if (hostname === pattern) {\n return true;\n }\n\n // Subdomain match: hostname ends with \".pattern\"\n // e.g., \"api.github.com\" matches \"github.com\"\n if (hostname.endsWith(`.${pattern}`)) {\n return true;\n }\n\n return false;\n}\n"],"mappings":"8CAQA,IAAIA,EAAwC,EAAE,CAQ9C,SAAgB,EAAmB,EAA0D,CAC3F,IAAM,EAAe,OAAO,GAAc,SAAW,IAAI,IAAI,EAAU,CAAG,EACpE,EAAW,EAAa,SAAS,aAAa,CAEpD,IAAK,IAAM,KAAY,EACrB,IAAK,IAAM,KAAW,EAAS,YAAa,CAE1C,IAAM,EAAe,EAAQ,aAAa,CAG1C,GAAI,EAAgB,EAAU,EAAa,CASzC,OARA,EAAO,MAAM,wCAAyC,CACpD,WACA,UACA,WAAY,EAAS,GACrB,aAAc,EAAS,KACxB,CAAC,CAGK,EAAc,EAAS,CAKpC,EAAO,MAAM,iDAAkD,CAC7D,WACA,UAAW,EAAa,UAAU,CACnC,CAAC,CA2CJ,SAAgB,EAAkB,EAAwC,CACxE,EAAiB,EAAU,IAAI,EAAc,CAO/C,SAAS,EAAc,EAA6C,CAClE,MAAO,CACL,GAAG,EACH,OAAQ,EAAE,OAAS,CAAC,GAAG,EAAE,OAAO,CAAG,IAAA,GACnC,YAAa,CAAC,GAAG,EAAE,YAAY,CAChC,CAWH,SAAS,EAAgB,EAAkB,EAA0B,CAYnE,MAJA,GANI,IAAa,GAMb,EAAS,SAAS,IAAI,IAAU"}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{platform as e}from"node:process";import{execFileSync as t}from"node:child_process";const n=`onyx-mcp-gateway`,r=`default`;async function i(t){return e===`darwin`?a(t):o(t)}function a(e){try{let r=t(`/usr/bin/security`,[`find-generic-password`,`-s`,n,`-a`,e,`-w`],{encoding:`utf8`,stdio:[`ignore`,`pipe`,`ignore`],timeout:2e3}).trim();return r===``?null:r}catch{return null}}async function o(e){try{return(await s(e)).getPassword()}catch{return null}}async function s(t){let{Entry:r}=await import(`@napi-rs/keyring`);return e===`win32`?r.withTarget(`${n}:${t}`,n,t):new r(n,t)}export{r as ACCOUNT_DEFAULT,i as getSecret};
|
|
2
|
+
//# sourceMappingURL=secretstore-Deoqfk8j.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"secretstore-Deoqfk8j.js","names":[],"sources":["../src/lib/secretstore.ts"],"sourcesContent":["// Cross-platform keychain reader for the MCP Gateway.\n//\n// macOS: shells out to /usr/bin/security so the calling binary in the\n// keychain ACL is always `security` — matching what the scanner-side Go\n// writer (which uses github.com/zalando/go-keyring) also shells to.\n// Same caller binary on both ends ⇒ no Keychain Services prompt.\n// The @napi-rs/keyring native binding bypasses /usr/bin/security and\n// calls Security framework directly with the calling binary set to\n// `node`, which trips the per-entry ACL and prompts the user — that's\n// why we don't use it here on darwin.\n//\n// Linux: uses @napi-rs/keyring (libsecret) with the default (service, user)\n// constructor; libsecret stores by attribute set, not by TargetName, so\n// the default constructor finds entries the scanner wrote via go-keyring.\n//\n// Windows: uses @napi-rs/keyring with the EXPLICIT TargetName form via\n// Entry.withTarget(). The three writers/readers on this platform must\n// agree on TargetName:\n// - scanner user-context: go-keyring writes `<service>:<user>` (its\n// hardcoded format in keyring_windows.go::credName)\n// - scanner SYSTEM-context: cmdkey.exe /generic:<service>:<user>\n// - gateway: @napi-rs/keyring's default would write/read\n// `<user>.<service>` (per windows-native-keyring-store::store::Store\n// defaults `[\"\", \".\", \"\"]`) — incompatible with the scanner. Calling\n// Entry.withTarget(\"<service>:<user>\", ...) overrides that and lines\n// the gateway up with go-keyring's TargetName.\n//\n// - Windows CredRead is per-user-session, no per-binary ACL — silent.\n// - Linux libsecret with an unlocked login keyring (the normal\n// desktop state) — silent. Locked / headless boxes return an error\n// which we treat as miss and fall through to env.\n//\n// All public functions are best-effort. On any error — missing backend,\n// timeout, malformed binding — getSecret returns null and the gateway\n// falls back to process.env (legacy behavior).\n//\n// Async API rationale: the @napi-rs/keyring native binding is loaded via\n// `await import(...)` so a missing prebuild on musl/alpine/headless turns\n// into a swallowed `catch` inside getSecret/deleteSecret rather than a\n// module-load crash. Using `createRequire` for the same lazy-load worked\n// at runtime but was opaque to vitest's `vi.mock` (which only intercepts\n// ESM resolution), so the Linux test path resolved the real binding and\n// missed the mock. Dynamic ESM imports are interceptable on both runtime\n// and test paths.\n\nimport { execFileSync } from \"node:child_process\";\nimport { platform } from \"node:process\";\n\n/** Keychain service name. MUST match scanner/internal/secretstore Service. */\nexport const SERVICE = \"onyx-mcp-gateway\";\n\n/** Single-tenant account name; multi-tenant on the same host is future work. */\nexport const ACCOUNT_DEFAULT = \"default\";\n\n/** Hard cap on the security/Entry call so a hung backend never wedges startup. */\nconst READ_TIMEOUT_MS = 2_000;\n\n/**\n * Best-effort delete of the entry under (SERVICE, account). Returns true if\n * an entry was removed, false otherwise. Never throws.\n */\nexport async function deleteSecret(account: string): Promise<boolean> {\n\tif (platform === \"darwin\") {\n\t\ttry {\n\t\t\texecFileSync(\n\t\t\t\t\"/usr/bin/security\",\n\t\t\t\t[\"delete-generic-password\", \"-s\", SERVICE, \"-a\", account],\n\t\t\t\t{ stdio: \"ignore\", timeout: READ_TIMEOUT_MS },\n\t\t\t);\n\t\t\treturn true;\n\t\t} catch {\n\t\t\treturn false;\n\t\t}\n\t}\n\ttry {\n\t\tconst entry = await nativeEntry(account);\n\t\t// `Entry.deletePassword()` is declared `boolean` in @napi-rs/keyring's\n\t\t// .d.ts, but the underlying Rust API is `Result<(), Error>` which\n\t\t// napi-rs commonly maps to `void | throw`. Don't rely on the return\n\t\t// value: explicit `true` on no-throw matches the darwin\n\t\t// `/usr/bin/security delete-generic-password` branch semantics\n\t\t// (exit 0 → return true). Treats \"not present\" as \"removed\" because\n\t\t// our only signal that anything wasn't deleted is an exception, and\n\t\t// the caller's \"best-effort delete\" contract doesn't distinguish.\n\t\tentry.deletePassword();\n\t\treturn true;\n\t} catch {\n\t\treturn false;\n\t}\n}\n\n/**\n * Look up the password under (SERVICE, account). Returns null on miss, on\n * any backend error, or if the underlying keychain access fails. Never\n * throws.\n */\nexport async function getSecret(account: string): Promise<null | string> {\n\tif (platform === \"darwin\") {\n\t\treturn getSecretMac(account);\n\t}\n\treturn getSecretNative(account);\n}\n\nfunction getSecretMac(account: string): null | string {\n\ttry {\n\t\tconst out = execFileSync(\n\t\t\t\"/usr/bin/security\",\n\t\t\t[\"find-generic-password\", \"-s\", SERVICE, \"-a\", account, \"-w\"],\n\t\t\t{ encoding: \"utf8\", stdio: [\"ignore\", \"pipe\", \"ignore\"], timeout: READ_TIMEOUT_MS },\n\t\t);\n\t\tconst trimmed = out.trim();\n\t\treturn trimmed === \"\" ? null : trimmed;\n\t} catch {\n\t\t// \"could not be found\" or any other security CLI error → treated as miss.\n\t\treturn null;\n\t}\n}\n\nasync function getSecretNative(account: string): Promise<null | string> {\n\ttry {\n\t\tconst entry = await nativeEntry(account);\n\t\treturn entry.getPassword();\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n/**\n * Build an @napi-rs/keyring Entry that talks to the same TargetName the\n * scanner side writes. On Windows we pin the TargetName to `<service>:<user>`\n * via Entry.withTarget; on Linux we use the default Entry which libsecret\n * matches by attribute (service + user). The dynamic `import` is lazy so\n * darwin never loads the native binding (we shell to /usr/bin/security\n * instead) and so a missing prebuild on musl/alpine becomes a swallowed\n * exception inside getSecretNative/deleteSecret rather than a module-load\n * crash. Using `await import(...)` (rather than createRequire) makes the\n * load interceptable by vitest's `vi.mock` on the Linux/Windows test paths.\n */\nasync function nativeEntry(account: string) {\n\tconst { Entry } = await import(\"@napi-rs/keyring\");\n\tif (platform === \"win32\") {\n\t\treturn Entry.withTarget(`${SERVICE}:${account}`, SERVICE, account);\n\t}\n\treturn new Entry(SERVICE, account);\n}\n"],"mappings":"0FAiDA,MAAa,EAAU,mBAGV,EAAkB,UA4C/B,eAAsB,EAAU,EAAyC,CAIxE,OAHI,IAAa,SACT,EAAa,EAAQ,CAEtB,EAAgB,EAAQ,CAGhC,SAAS,EAAa,EAAgC,CACrD,GAAI,CAMH,IAAM,EALM,EACX,oBACA,CAAC,wBAAyB,KAAM,EAAS,KAAM,EAAS,KAAK,CAC7D,CAAE,SAAU,OAAQ,MAAO,CAAC,SAAU,OAAQ,SAAS,CAAE,QAAS,IAAiB,CACnF,CACmB,MAAM,CAC1B,OAAO,IAAY,GAAK,KAAO,OACxB,CAEP,OAAO,MAIT,eAAe,EAAgB,EAAyC,CACvE,GAAI,CAEH,OADc,MAAM,EAAY,EAAQ,EAC3B,aAAa,MACnB,CACP,OAAO,MAeT,eAAe,EAAY,EAAiB,CAC3C,GAAM,CAAE,SAAU,MAAM,OAAO,oBAI/B,OAHI,IAAa,QACT,EAAM,WAAW,GAAG,EAAQ,GAAG,IAAW,EAAS,EAAQ,CAE5D,IAAI,EAAM,EAAS,EAAQ"}
|