@onyxsecurity/mcp-gateway 1.0.138 → 1.0.140

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 (26) hide show
  1. package/dist/bin/mcp-gateway.js +2 -2
  2. package/dist/{cli-DWpHxCK6.js → cli-Cj_W_Rxw.js} +2 -2
  3. package/dist/{cli-DWpHxCK6.js.map → cli-Cj_W_Rxw.js.map} +1 -1
  4. package/dist/{env-CkHa0WcM.js → env-CvPQLLkW.js} +1 -1
  5. package/dist/{env-DJNvBCH9.js → env-DKEqpop9.js} +2 -2
  6. package/dist/{env-DJNvBCH9.js.map → env-DKEqpop9.js.map} +1 -1
  7. package/dist/index.js +1 -1
  8. package/dist/{logger-BjLVc61x.js → logger-jKh1IKjp.js} +2 -2
  9. package/dist/{logger-BjLVc61x.js.map → logger-jKh1IKjp.js.map} +1 -1
  10. package/dist/{main-DrKclq2L.js → main-CsDKLUsa.js} +2 -2
  11. package/dist/{main-DrKclq2L.js.map → main-CsDKLUsa.js.map} +1 -1
  12. package/dist/oauthProvidersClient-DEQn3MwK.js +1 -0
  13. package/dist/{oauthProvidersClient-tKUY6rDu.js → oauthProvidersClient-T6ZhWZr8.js} +2 -2
  14. package/dist/{oauthProvidersClient-tKUY6rDu.js.map → oauthProvidersClient-T6ZhWZr8.js.map} +1 -1
  15. package/dist/{providers-BaFl5rAa.js → providers-CR3DqjRH.js} +2 -2
  16. package/dist/{providers-BaFl5rAa.js.map → providers-CR3DqjRH.js.map} +1 -1
  17. package/dist/secretstore-B7UBzLo4.js +3 -0
  18. package/dist/secretstore-B7UBzLo4.js.map +1 -0
  19. package/dist/{stdio-BD8z2niv.js → stdio-DDsYWNYz.js} +2 -2
  20. package/dist/{stdio-BD8z2niv.js.map → stdio-DDsYWNYz.js.map} +1 -1
  21. package/dist/{transportDetection-A7929-Uq.js → transportDetection-D45i4vIF.js} +2 -2
  22. package/dist/{transportDetection-A7929-Uq.js.map → transportDetection-D45i4vIF.js.map} +1 -1
  23. package/package.json +1 -1
  24. package/dist/oauthProvidersClient-MnnqkcZR.js +0 -1
  25. package/dist/secretstore-R8UMzO0o.js +0 -2
  26. package/dist/secretstore-R8UMzO0o.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"secretstore-R8UMzO0o.js","names":["timer: NodeJS.Timeout | undefined","blob: Buffer"],"sources":["../src/lib/secretstore.ts"],"sourcesContent":["// Cross-platform keychain reader for the MCP Gateway.\n//\n// The scanner picks the store by privilege at install time: a privileged\n// (root/SYSTEM/MDM) install writes a MACHINE-SCOPED, boot-available store (so\n// it resolves even when no user is logged in); a regular-user install writes\n// that user's own per-user store. The gateway tries the machine store first,\n// then the per-user store. The read paths:\n//\n// macOS: shells out to /usr/bin/security (async execFile). Tries the System\n// keychain (/Library/Keychains/System.keychain) explicitly first — where a\n// privileged install writes the allow-all (`-A`) entry, and passing the path\n// explicitly stops a customized search list from hiding it — then falls back\n// to the default search list, which covers the user's login keychain (where a\n// regular-user install writes). Same `security` caller on both ends ⇒ no\n// per-binary ACL prompt for the entry the scanner wrote.\n//\n// The `-A` ACL governs app access, not the keychain's locked state, so a\n// locked login keychain (or an ACL re-evaluated after the node binary\n// changed) can still pop a SecurityAgent dialog and BLOCK — and a coding-agent\n// subprocess can't dismiss it. The read is therefore hard-bounded with\n// killSignal \"SIGKILL\" (PRDCT-6680): the prior execFileSync timeout used the\n// default SIGTERM, which `security` ignores while blocked, so it never fired.\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// (No machine-scoped store on Linux — the scanner always uses the per-user\n// path.)\n//\n// Windows: reads a DPAPI blob (getMachineBlobSecret / getUserBlobSecret +\n// @primno/dpapi). Tries the machine blob under %ProgramData% (LocalMachine\n// scope — what a privileged/SYSTEM install writes, the path that works for\n// pre-login installs) first, then the per-user blob under %LOCALAPPDATA%\n// (CurrentUser scope — what a regular-user install writes). The scanner no\n// longer writes the per-user Credential Manager, so it isn't consulted.\n//\n// - macOS/Windows machine stores are unlocked at boot — no session, no prompt.\n// - the per-user stores (login keychain / CurrentUser blob) are readable by\n// the gateway running as that same user.\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 (Linux only) is\n// loaded via `await import(...)` so a missing prebuild on musl/alpine/headless\n// turns into a swallowed `catch` inside getSecretNative 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 { execFile } from \"node:child_process\";\nimport { join } from \"node:path\";\nimport { env, platform } from \"node:process\";\nimport { clearTimeout, setTimeout } from \"node:timers\";\n\nimport type { KeychainOutcomeSource } from \"./keychainOutcome.js\";\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/**\n * macOS machine-scoped keychain. The scanner writes the gateway key here (with\n * an allow-all ACL) so it is readable with no interactive-login dependency. We\n * pass this path explicitly rather than relying on the default search list, so\n * a customized search list can't hide the entry.\n */\nconst MAC_SYSTEM_KEYCHAIN = \"/Library/Keychains/System.keychain\";\n\n/** Hard cap on a single security/Entry call so a hung backend never wedges startup. */\nconst READ_TIMEOUT_MS = 2_000;\n\n/**\n * Overall deadline for resolveApiKeyFromStore — a backstop above the macOS worst\n * case (two sequential READ_TIMEOUT_MS reads + spawn overhead) so the probe is\n * non-blocking even if a platform's own per-call cap somehow doesn't fire.\n */\nexport const KEYCHAIN_PROBE_TIMEOUT_MS = 5_000;\n\n/**\n * getMachineBlobSecret reads the machine-scoped DPAPI blob a privileged install\n * writes under %ProgramData% (LocalMachine scope, readable by every user).\n */\nexport async function getMachineBlobSecret(account: string): Promise<null | string> {\n\treturn readDpapiBlob(programDataBlobPath(account), \"LocalMachine\");\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\t// macOS: machine-scoped System keychain (where the scanner writes).\n\t\treturn getSecretMac(account);\n\t}\n\tif (platform === \"win32\") {\n\t\t// The scanner writes a privileged install to the machine blob\n\t\t// (%ProgramData%, LocalMachine) and a regular-user install to the\n\t\t// per-user blob (%LOCALAPPDATA%, CurrentUser). Try the machine blob\n\t\t// first, then the per-user blob. The scanner no longer writes the\n\t\t// per-user Credential Manager, so it isn't consulted.\n\t\treturn (await getMachineBlobSecret(account)) ?? (await getUserBlobSecret(account));\n\t}\n\t// Linux: per-user libsecret vault. No machine-scoped store exists, so the\n\t// scanner keeps the per-user path and we read it via @napi-rs/keyring.\n\treturn getSecretNative(account);\n}\n\n/** Sentinel for the overall-deadline race in resolveApiKeyFromStore. */\nconst PROBE_TIMEOUT = Symbol(\"keychain-probe-timeout\");\n\n/**\n * Outcome of resolving the scanner api-key from the OS secret store, classified\n * for the keychain_outcome telemetry. `error` is defensive (getSecret is\n * best-effort and shouldn't throw); `timeout` is the backstop for a probe that\n * blocked past KEYCHAIN_PROBE_TIMEOUT_MS.\n */\nexport interface ApiKeyResolution {\n\tkey: null | string;\n\tsource: Extract<KeychainOutcomeSource, \"error\" | \"keychain\" | \"missing\" | \"timeout\">;\n}\n\n/**\n * getUserBlobSecret reads the per-user DPAPI blob a regular-user install writes\n * under %LOCALAPPDATA% (CurrentUser scope, readable only by that user).\n */\nexport async function getUserBlobSecret(account: string): Promise<null | string> {\n\treturn readDpapiBlob(localAppDataBlobPath(account), \"CurrentUser\");\n}\n\n/**\n * localAppDataBlobPath is the absolute path of the Windows per-user DPAPI blob a\n * regular-user install writes. MUST match scanner/internal/secretstore\n * userBlobPath: %LOCALAPPDATA%\\Onyx\\mcp-gateway\\<account>.bin.\n */\nexport function localAppDataBlobPath(account: string): string {\n\tconst base = env.LOCALAPPDATA ?? join(env.USERPROFILE ?? \"C:\\\\\", \"AppData\", \"Local\");\n\treturn join(base, \"Onyx\", \"mcp-gateway\", `${account}.bin`);\n}\n\n/**\n * programDataBlobPath is the absolute path of the Windows machine-scoped DPAPI\n * blob a privileged install writes. MUST match scanner/internal/secretstore\n * programDataBlobPath: %ProgramData%\\Onyx\\mcp-gateway\\<account>.bin.\n */\nexport function programDataBlobPath(account: string): string {\n\tconst base = env.ProgramData ?? \"C:\\\\ProgramData\";\n\treturn join(base, \"Onyx\", \"mcp-gateway\", `${account}.bin`);\n}\n\n/**\n * Resolve the scanner api-key from the OS secret store, bounded by an overall\n * hard deadline so the credential probe is GUARANTEED non-blocking — the gateway\n * must never hang on credential resolution before the MCP handshake (PRDCT-6680).\n *\n * Never throws. `opts` is for tests: `getSecretFn` injects a fake reader and\n * `timeoutMs` shrinks the deadline.\n */\nexport async function resolveApiKeyFromStore(\n\taccount: string,\n\topts: { getSecretFn?: (account: string) => Promise<null | string>; timeoutMs?: number } = {},\n): Promise<ApiKeyResolution> {\n\tconst getSecretFn = opts.getSecretFn ?? getSecret;\n\tconst timeoutMs = opts.timeoutMs ?? KEYCHAIN_PROBE_TIMEOUT_MS;\n\n\tlet timer: NodeJS.Timeout | undefined;\n\tconst deadline = new Promise<typeof PROBE_TIMEOUT>((resolve) => {\n\t\ttimer = setTimeout(() => resolve(PROBE_TIMEOUT), timeoutMs);\n\t\t// Don't keep the event loop alive just for this backstop timer.\n\t\ttimer.unref?.();\n\t});\n\n\ttry {\n\t\tconst result = await Promise.race([getSecretFn(account), deadline]);\n\t\tif (result === PROBE_TIMEOUT) {\n\t\t\treturn { key: null, source: \"timeout\" };\n\t\t}\n\t\tif (result) {\n\t\t\treturn { key: result, source: \"keychain\" };\n\t\t}\n\t\treturn { key: null, source: \"missing\" };\n\t} catch {\n\t\treturn { key: null, source: \"error\" };\n\t} finally {\n\t\tif (timer !== undefined) {\n\t\t\tclearTimeout(timer);\n\t\t}\n\t}\n}\n\n/**\n * getSecretMac reads the gateway key from the macOS keychain. A privileged\n * install lands it in the System keychain (machine-wide); a regular-user\n * install lands it in the user's login keychain. Try the System keychain\n * explicitly first (so a customized search list can't hide it), then fall back\n * to the default search list, which covers the login keychain.\n */\nasync function getSecretMac(account: string): Promise<null | string> {\n\ttry {\n\t\treturn (\n\t\t\t(await readSecurityKeychain(account, MAC_SYSTEM_KEYCHAIN)) ??\n\t\t\t(await readSecurityKeychain(account, null))\n\t\t);\n\t} catch {\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 for the Linux libsecret read. libsecret\n * matches by attribute set (service + user), so the default constructor finds\n * entries the scanner wrote via go-keyring. Linux is the only caller: darwin\n * shells to /usr/bin/security and Windows reads the machine DPAPI blob, so\n * neither loads the native binding. The dynamic `import` is lazy so a missing\n * prebuild on musl/alpine/headless becomes a swallowed exception inside\n * getSecretNative rather than a module-load crash. Using `await import(...)`\n * (rather than createRequire) makes the load interceptable by vitest's\n * `vi.mock` on the Linux test path.\n */\nasync function nativeEntry(account: string) {\n\tconst { Entry } = await import(\"@napi-rs/keyring\");\n\treturn new Entry(SERVICE, account);\n}\n\n/**\n * readDpapiBlob reads a Windows DPAPI blob from `path` and decrypts it with the\n * given scope. The @primno/dpapi import is lazy so a missing prebuild never\n * crashes startup (mirrors the @napi-rs/keyring lazy import).\n *\n * Two failure modes are deliberately NOT treated the same:\n * - No blob at `path`: a legitimate, expected miss (nothing was ever written\n * at this scope, or this install doesn't use it). Swallowed to null.\n * - Blob present but the @primno/dpapi native binding fails to load: this\n * means the scanner DID write a secret here but the gateway binary can't\n * read it back — e.g. a cross-compiled Windows binary missing the addon's\n * prebuilt .node (a real packaging defect, not an absent-secret case).\n * Left to propagate, so resolveApiKeyFromStore's outcome=\"error\" telemetry\n * surfaces it distinctly instead of blending into the generic \"missing\"\n * bucket every no-secret endpoint already reports.\n * A decrypt failure once the binding loaded (wrong scope, corrupt blob) is\n * still swallowed to null — that's an expected miss, not a packaging defect.\n */\nasync function readDpapiBlob(\n\tpath: string,\n\tscope: \"CurrentUser\" | \"LocalMachine\",\n): Promise<null | string> {\n\tconst { readFile } = await import(\"node:fs/promises\");\n\tlet blob: Buffer;\n\ttry {\n\t\tblob = await readFile(path);\n\t} catch {\n\t\treturn null;\n\t}\n\n\tconst { Dpapi } = await import(\"@primno/dpapi\");\n\ttry {\n\t\tconst decrypted = Dpapi.unprotectData(new Uint8Array(blob), null, scope);\n\t\tconst value = Buffer.from(decrypted).toString(\"utf8\");\n\t\treturn value === \"\" ? null : value;\n\t} catch {\n\t\treturn null;\n\t}\n}\n\n// readSecurityKeychain runs `security find-generic-password -w` asynchronously,\n// reading the explicit `keychain` path when given or the default search list\n// when null. Never rejects — resolves null on miss/error/timeout so the caller\n// can fall through. Hard-bounded with killSignal \"SIGKILL\" (see the header).\nfunction readSecurityKeychain(account: string, keychain: null | string): Promise<null | string> {\n\treturn new Promise((resolve) => {\n\t\tconst args = [\"find-generic-password\", \"-s\", SERVICE, \"-a\", account, \"-w\"];\n\t\tif (keychain !== null) {\n\t\t\targs.push(keychain);\n\t\t}\n\t\texecFile(\n\t\t\t\"/usr/bin/security\",\n\t\t\targs,\n\t\t\t{ encoding: \"utf8\", killSignal: \"SIGKILL\", timeout: READ_TIMEOUT_MS },\n\t\t\t(error, stdout) => {\n\t\t\t\tif (error) {\n\t\t\t\t\t// miss, security CLI error, or SIGKILL'd timeout — all a miss.\n\t\t\t\t\tresolve(null);\n\t\t\t\t\treturn;\n\t\t\t\t}\n\t\t\t\tconst trimmed = String(stdout).trim();\n\t\t\t\tresolve(trimmed === \"\" ? null : trimmed);\n\t\t\t},\n\t\t);\n\t});\n}\n"],"mappings":"2LAgEA,MAAa,EAAU,mBAGV,EAAkB,UAwB/B,eAAsB,EAAqB,EAAyC,CACnF,OAAO,EAAc,EAAoB,EAAQ,CAAE,eAAe,CAQnE,eAAsB,EAAU,EAAyC,CAexE,OAdI,IAAa,SAET,EAAa,EAAQ,CAEzB,IAAa,QAMR,MAAM,EAAqB,EAAQ,EAAM,MAAM,EAAkB,EAAQ,CAI3E,EAAgB,EAAQ,CAIhC,MAAM,EAAgB,OAAO,yBAAyB,CAiBtD,eAAsB,EAAkB,EAAyC,CAChF,OAAO,EAAc,EAAqB,EAAQ,CAAE,cAAc,CAQnE,SAAgB,EAAqB,EAAyB,CAC7D,IAAM,EAAO,EAAI,cAAgB,EAAK,EAAI,aAAe,OAAQ,UAAW,QAAQ,CACpF,OAAO,EAAK,EAAM,OAAQ,cAAe,GAAG,EAAQ,MAAM,CAQ3D,SAAgB,EAAoB,EAAyB,CAC5D,IAAM,EAAO,EAAI,aAAe,kBAChC,OAAO,EAAK,EAAM,OAAQ,cAAe,GAAG,EAAQ,MAAM,CAW3D,eAAsB,EACrB,EACA,EAA0F,EAAE,CAChE,CAC5B,IAAM,EAAc,EAAK,aAAe,EAClC,EAAY,EAAK,WAAa,IAEhCA,EACE,EAAW,IAAI,QAA+B,GAAY,CAC/D,EAAQ,MAAiB,EAAQ,EAAc,CAAE,EAAU,CAE3D,EAAM,SAAS,EACd,CAEF,GAAI,CACH,IAAM,EAAS,MAAM,QAAQ,KAAK,CAAC,EAAY,EAAQ,CAAE,EAAS,CAAC,CAOnE,OANI,IAAW,EACP,CAAE,IAAK,KAAM,OAAQ,UAAW,CAEpC,EACI,CAAE,IAAK,EAAQ,OAAQ,WAAY,CAEpC,CAAE,IAAK,KAAM,OAAQ,UAAW,MAChC,CACP,MAAO,CAAE,IAAK,KAAM,OAAQ,QAAS,QAC5B,CACL,IAAU,IAAA,IACb,EAAa,EAAM,EAYtB,eAAe,EAAa,EAAyC,CACpE,GAAI,CACH,OACE,MAAM,EAAqB,EAAS,qCAAoB,EACxD,MAAM,EAAqB,EAAS,KAAK,MAEpC,CACP,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,oBAC/B,OAAO,IAAI,EAAM,EAAS,EAAQ,CAqBnC,eAAe,EACd,EACA,EACyB,CACzB,GAAM,CAAE,YAAa,MAAM,OAAO,oBAC9BC,EACJ,GAAI,CACH,EAAO,MAAM,EAAS,EAAK,MACpB,CACP,OAAO,KAGR,GAAM,CAAE,SAAU,MAAM,OAAO,iBAC/B,GAAI,CACH,IAAM,EAAY,EAAM,cAAc,IAAI,WAAW,EAAK,CAAE,KAAM,EAAM,CAClE,EAAQ,OAAO,KAAK,EAAU,CAAC,SAAS,OAAO,CACrD,OAAO,IAAU,GAAK,KAAO,OACtB,CACP,OAAO,MAQT,SAAS,EAAqB,EAAiB,EAAiD,CAC/F,OAAO,IAAI,QAAS,GAAY,CAC/B,IAAM,EAAO,CAAC,wBAAyB,KAAM,EAAS,KAAM,EAAS,KAAK,CACtE,IAAa,MAChB,EAAK,KAAK,EAAS,CAEpB,EACC,oBACA,EACA,CAAE,SAAU,OAAQ,WAAY,UAAW,QAAS,IAAiB,EACpE,EAAO,IAAW,CAClB,GAAI,EAAO,CAEV,EAAQ,KAAK,CACb,OAED,IAAM,EAAU,OAAO,EAAO,CAAC,MAAM,CACrC,EAAQ,IAAY,GAAK,KAAO,EAAQ,EAEzC,EACA"}