@zivis/mcp 0.1.0-alpha.2 → 0.1.0-alpha.20
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/api-client.d.ts +11 -11
- package/dist/api-client.js +41 -36
- package/dist/api-client.js.map +1 -1
- package/dist/auth/index.d.ts +9 -5
- package/dist/auth/index.js +33 -17
- package/dist/auth/index.js.map +1 -1
- package/dist/auth/keychain.d.ts +6 -5
- package/dist/auth/keychain.js +82 -69
- package/dist/auth/keychain.js.map +1 -1
- package/dist/auth/oauth.d.ts +18 -0
- package/dist/auth/oauth.js +55 -6
- package/dist/auth/oauth.js.map +1 -1
- package/dist/auth/sessions.d.ts +5 -4
- package/dist/auth/sessions.js +20 -14
- package/dist/auth/sessions.js.map +1 -1
- package/dist/auth/token-refresh.d.ts +2 -2
- package/dist/auth/token-refresh.js +42 -9
- package/dist/auth/token-refresh.js.map +1 -1
- package/dist/cli.d.ts +7 -15
- package/dist/cli.js +36 -904
- package/dist/cli.js.map +1 -1
- package/dist/lib/next-steps.d.ts +57 -0
- package/dist/lib/next-steps.js +45 -0
- package/dist/lib/next-steps.js.map +1 -0
- package/dist/project-binding.d.ts +19 -2
- package/dist/project-binding.js +57 -3
- package/dist/project-binding.js.map +1 -1
- package/dist/prompts/audit-dependencies.d.ts +10 -0
- package/dist/prompts/audit-dependencies.js +56 -0
- package/dist/prompts/audit-dependencies.js.map +1 -0
- package/dist/prompts/getting-started.d.ts +12 -0
- package/dist/prompts/getting-started.js +48 -0
- package/dist/prompts/getting-started.js.map +1 -0
- package/dist/prompts/index.d.ts +14 -0
- package/dist/prompts/index.js +25 -0
- package/dist/prompts/index.js.map +1 -0
- package/dist/prompts/prep-for-launch.d.ts +9 -0
- package/dist/prompts/prep-for-launch.js +62 -0
- package/dist/prompts/prep-for-launch.js.map +1 -0
- package/dist/prompts/secure-my-app.d.ts +9 -0
- package/dist/prompts/secure-my-app.js +70 -0
- package/dist/prompts/secure-my-app.js.map +1 -0
- package/dist/prompts/weekly-check.d.ts +9 -0
- package/dist/prompts/weekly-check.js +48 -0
- package/dist/prompts/weekly-check.js.map +1 -0
- package/dist/server.d.ts +1 -1
- package/dist/server.js +114 -43
- package/dist/server.js.map +1 -1
- package/dist/tools/check-project.d.ts +7 -7
- package/dist/tools/check-project.js +127 -95
- package/dist/tools/check-project.js.map +1 -1
- package/dist/tools/check-repo-trust.d.ts +4 -10
- package/dist/tools/check-repo-trust.js +56 -27
- package/dist/tools/check-repo-trust.js.map +1 -1
- package/dist/tools/enterprise/get-campaign.d.ts +27 -0
- package/dist/tools/enterprise/get-campaign.js +31 -0
- package/dist/tools/enterprise/get-campaign.js.map +1 -0
- package/dist/tools/enterprise/list-agents.d.ts +1 -1
- package/dist/tools/enterprise/list-attack-scenarios.d.ts +29 -0
- package/dist/tools/enterprise/list-attack-scenarios.js +69 -0
- package/dist/tools/enterprise/list-attack-scenarios.js.map +1 -0
- package/dist/tools/enterprise/list-attack-step-library-tests.d.ts +29 -0
- package/dist/tools/enterprise/list-attack-step-library-tests.js +61 -0
- package/dist/tools/enterprise/list-attack-step-library-tests.js.map +1 -0
- package/dist/tools/enterprise/list-campaigns.d.ts +37 -0
- package/dist/tools/enterprise/list-campaigns.js +60 -0
- package/dist/tools/enterprise/list-campaigns.js.map +1 -0
- package/dist/tools/enterprise/list-threat-model-review-items.d.ts +35 -0
- package/dist/tools/enterprise/list-threat-model-review-items.js +60 -0
- package/dist/tools/enterprise/list-threat-model-review-items.js.map +1 -0
- package/dist/tools/enterprise/list-threat-model-versions.d.ts +29 -0
- package/dist/tools/enterprise/list-threat-model-versions.js +60 -0
- package/dist/tools/enterprise/list-threat-model-versions.js.map +1 -0
- package/dist/tools/enterprise/manage-campaign-agent-instance.d.ts +42 -0
- package/dist/tools/enterprise/manage-campaign-agent-instance.js +68 -0
- package/dist/tools/enterprise/manage-campaign-agent-instance.js.map +1 -0
- package/dist/tools/enterprise/manage-campaign-target.d.ts +44 -0
- package/dist/tools/enterprise/manage-campaign-target.js +70 -0
- package/dist/tools/enterprise/manage-campaign-target.js.map +1 -0
- package/dist/tools/enterprise/manage-campaign.d.ts +62 -0
- package/dist/tools/enterprise/manage-campaign.js +114 -0
- package/dist/tools/enterprise/manage-campaign.js.map +1 -0
- package/dist/tools/enterprise/manage-finding.d.ts +1 -1
- package/dist/tools/enterprise/manage-threat-model-analysis.d.ts +52 -0
- package/dist/tools/enterprise/manage-threat-model-analysis.js +92 -0
- package/dist/tools/enterprise/manage-threat-model-analysis.js.map +1 -0
- package/dist/tools/enterprise/manage-threat-model-review.d.ts +47 -0
- package/dist/tools/enterprise/manage-threat-model-review.js +82 -0
- package/dist/tools/enterprise/manage-threat-model-review.js.map +1 -0
- package/dist/tools/get-application-auth-config.d.ts +27 -0
- package/dist/tools/get-application-auth-config.js +44 -0
- package/dist/tools/get-application-auth-config.js.map +1 -0
- package/dist/tools/get-finding-details.d.ts +4 -10
- package/dist/tools/get-finding-details.js +28 -10
- package/dist/tools/get-finding-details.js.map +1 -1
- package/dist/tools/get-findings.d.ts +4 -10
- package/dist/tools/get-findings.js +44 -23
- package/dist/tools/get-findings.js.map +1 -1
- package/dist/tools/get-started.d.ts +28 -0
- package/dist/tools/get-started.js +364 -0
- package/dist/tools/get-started.js.map +1 -0
- package/dist/tools/list-applications.d.ts +1 -1
- package/dist/tools/list-scans.d.ts +4 -10
- package/dist/tools/list-scans.js +55 -21
- package/dist/tools/list-scans.js.map +1 -1
- package/dist/tools/list-test-scenarios.d.ts +4 -10
- package/dist/tools/list-test-scenarios.js +41 -13
- package/dist/tools/list-test-scenarios.js.map +1 -1
- package/dist/tools/local-scan.d.ts +1 -1
- package/dist/tools/manage-application.d.ts +72 -0
- package/dist/tools/manage-application.js +138 -0
- package/dist/tools/manage-application.js.map +1 -0
- package/dist/tools/manage-endpoint-lifecycle.d.ts +90 -0
- package/dist/tools/manage-endpoint-lifecycle.js +191 -0
- package/dist/tools/manage-endpoint-lifecycle.js.map +1 -0
- package/dist/tools/open-in-ide.d.ts +1 -1
- package/dist/tools/setup-red-team-target.d.ts +6 -12
- package/dist/tools/setup-red-team-target.js +51 -20
- package/dist/tools/setup-red-team-target.js.map +1 -1
- package/dist/tools/triage-finding.d.ts +4 -10
- package/dist/tools/triage-finding.js +30 -21
- package/dist/tools/triage-finding.js.map +1 -1
- package/dist/types.d.ts +42 -2
- package/dist/types.js +29 -23
- package/dist/types.js.map +1 -1
- package/package.json +1 -1
package/dist/api-client.d.ts
CHANGED
|
@@ -1,27 +1,27 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ZIVIS API Client
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - OAuth: Authorization: Bearer <token>
|
|
7
|
-
* - API Key: X-API-Key: <key>
|
|
8
|
-
*
|
|
9
|
-
* Handles 401 responses by attempting token refresh (OAuth)
|
|
10
|
-
* before returning an auth error.
|
|
4
|
+
* Uses API keys for all requests (X-API-Key). OAuth in the CLI exists to mint
|
|
5
|
+
* org-scoped keys via `zivis auth bind`; Bearer access tokens are not used here.
|
|
11
6
|
*/
|
|
12
7
|
import type { ZivisConfig } from "./types.js";
|
|
13
8
|
export declare class ApiClient {
|
|
14
9
|
private config;
|
|
15
10
|
constructor(config?: ZivisConfig);
|
|
11
|
+
/**
|
|
12
|
+
* Return a config with the session resolved from the project binding when
|
|
13
|
+
* no explicit session was set at server startup. Called on every request so
|
|
14
|
+
* that a globally-installed MCP server automatically uses the right session
|
|
15
|
+
* for whichever workspace it's called from, without needing a per-project
|
|
16
|
+
* `zivis mcp install`.
|
|
17
|
+
*/
|
|
18
|
+
resolveEffectiveConfig(cwd?: string): ZivisConfig;
|
|
16
19
|
/**
|
|
17
20
|
* Build auth headers from current credentials
|
|
18
21
|
*/
|
|
19
22
|
private buildAuthHeaders;
|
|
20
23
|
/**
|
|
21
|
-
* Make an authenticated request to the ZIVIS API.
|
|
22
|
-
*
|
|
23
|
-
* Automatically resolves credentials and attaches auth headers.
|
|
24
|
-
* On 401 with OAuth, attempts one token refresh and retries.
|
|
24
|
+
* Make an authenticated request to the ZIVIS API using an MCP API key (X-API-Key).
|
|
25
25
|
*/
|
|
26
26
|
request<T>(method: string, path: string, body?: unknown): Promise<T>;
|
|
27
27
|
private doRequest;
|
package/dist/api-client.js
CHANGED
|
@@ -1,21 +1,44 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* ZIVIS API Client
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* - OAuth: Authorization: Bearer <token>
|
|
7
|
-
* - API Key: X-API-Key: <key>
|
|
8
|
-
*
|
|
9
|
-
* Handles 401 responses by attempting token refresh (OAuth)
|
|
10
|
-
* before returning an auth error.
|
|
4
|
+
* Uses API keys for all requests (X-API-Key). OAuth in the CLI exists to mint
|
|
5
|
+
* org-scoped keys via `zivis auth bind`; Bearer access tokens are not used here.
|
|
11
6
|
*/
|
|
12
7
|
import { resolveAuth } from "./auth/index.js";
|
|
8
|
+
import { detectProjectBinding, resolveConfigFromBinding, resolveMcpSessionForWorkspace, } from "./project-binding.js";
|
|
13
9
|
import { DEFAULT_CONFIG } from "./types.js";
|
|
14
10
|
export class ApiClient {
|
|
15
11
|
config;
|
|
16
12
|
constructor(config = DEFAULT_CONFIG) {
|
|
17
13
|
this.config = config;
|
|
18
14
|
}
|
|
15
|
+
/**
|
|
16
|
+
* Return a config with the session resolved from the project binding when
|
|
17
|
+
* no explicit session was set at server startup. Called on every request so
|
|
18
|
+
* that a globally-installed MCP server automatically uses the right session
|
|
19
|
+
* for whichever workspace it's called from, without needing a per-project
|
|
20
|
+
* `zivis mcp install`.
|
|
21
|
+
*/
|
|
22
|
+
resolveEffectiveConfig(cwd) {
|
|
23
|
+
const ws = this.config.workspacePath ?? cwd ?? process.cwd();
|
|
24
|
+
const detected = detectProjectBinding(ws);
|
|
25
|
+
if (!detected) {
|
|
26
|
+
return this.config;
|
|
27
|
+
}
|
|
28
|
+
const { binding } = detected;
|
|
29
|
+
const fromBinding = resolveConfigFromBinding(ws);
|
|
30
|
+
// Same session precedence as server startup (see resolveMcpSessionForWorkspace).
|
|
31
|
+
// Always overlay binding for this workspace (OAuth URL, API host, keychain bucket).
|
|
32
|
+
return {
|
|
33
|
+
...this.config,
|
|
34
|
+
session: resolveMcpSessionForWorkspace(this.config, ws, detected, fromBinding),
|
|
35
|
+
...(binding.apiBaseUrl ? { apiBaseUrl: binding.apiBaseUrl } : {}),
|
|
36
|
+
...(binding.oauthApiUrl ? { oauthApiUrl: binding.oauthApiUrl } : {}),
|
|
37
|
+
...(binding.oauthClientId ? { oauthClientId: binding.oauthClientId } : {}),
|
|
38
|
+
...(binding.keychainPrefix ? { keychainPrefix: binding.keychainPrefix } : {}),
|
|
39
|
+
...(binding.configDir ? { configDir: binding.configDir } : {}),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
19
42
|
/**
|
|
20
43
|
* Build auth headers from current credentials
|
|
21
44
|
*/
|
|
@@ -30,41 +53,22 @@ export class ApiClient {
|
|
|
30
53
|
}
|
|
31
54
|
}
|
|
32
55
|
/**
|
|
33
|
-
* Make an authenticated request to the ZIVIS API.
|
|
34
|
-
*
|
|
35
|
-
* Automatically resolves credentials and attaches auth headers.
|
|
36
|
-
* On 401 with OAuth, attempts one token refresh and retries.
|
|
56
|
+
* Make an authenticated request to the ZIVIS API using an MCP API key (X-API-Key).
|
|
37
57
|
*/
|
|
38
58
|
async request(method, path, body) {
|
|
39
|
-
const
|
|
59
|
+
const effectiveConfig = this.resolveEffectiveConfig();
|
|
60
|
+
const creds = await resolveAuth(effectiveConfig, "apikey");
|
|
40
61
|
if (creds.method === "none") {
|
|
41
|
-
throw new ApiError("Not authenticated.
|
|
42
|
-
}
|
|
43
|
-
const result = await this.doRequest(method, path, body, creds);
|
|
44
|
-
// If 401 and using OAuth, try refreshing
|
|
45
|
-
if (result.status === 401 && creds.method === "oauth") {
|
|
46
|
-
const freshCreds = await resolveAuth(this.config);
|
|
47
|
-
if (freshCreds.method === "oauth" &&
|
|
48
|
-
freshCreds.accessToken !== creds.accessToken) {
|
|
49
|
-
// Token was refreshed, retry
|
|
50
|
-
const retryResult = await this.doRequest(method, path, body, freshCreds);
|
|
51
|
-
if (retryResult.status === 401) {
|
|
52
|
-
throw new ApiError("Session expired. Run: zivis auth login", 401, "SESSION_EXPIRED");
|
|
53
|
-
}
|
|
54
|
-
if (!retryResult.ok) {
|
|
55
|
-
throw new ApiError(retryResult.errorMessage || "API request failed", retryResult.status, "API_ERROR");
|
|
56
|
-
}
|
|
57
|
-
return retryResult.data;
|
|
58
|
-
}
|
|
59
|
-
throw new ApiError("Session expired. Run: zivis auth login", 401, "SESSION_EXPIRED");
|
|
62
|
+
throw new ApiError("Not authenticated. MCP uses API keys only — run: zivis auth bind (or zivis auth token sk_... --session <name>)", 401, "AUTH_REQUIRED");
|
|
60
63
|
}
|
|
64
|
+
const result = await this.doRequest(method, path, body, creds, effectiveConfig.apiBaseUrl);
|
|
61
65
|
if (!result.ok) {
|
|
62
66
|
throw new ApiError(result.errorMessage || `API request failed: ${method} ${path}`, result.status, "API_ERROR");
|
|
63
67
|
}
|
|
64
68
|
return result.data;
|
|
65
69
|
}
|
|
66
|
-
async doRequest(method, path, body, creds) {
|
|
67
|
-
const url = `${
|
|
70
|
+
async doRequest(method, path, body, creds, apiBaseUrl) {
|
|
71
|
+
const url = `${apiBaseUrl}${path}`;
|
|
68
72
|
const headers = {
|
|
69
73
|
"Content-Type": "application/json",
|
|
70
74
|
"User-Agent": "@zivis-ai/cli",
|
|
@@ -104,11 +108,12 @@ export class ApiClient {
|
|
|
104
108
|
* Used for artifact content streaming where the response is not JSON.
|
|
105
109
|
*/
|
|
106
110
|
async getText(path) {
|
|
107
|
-
const
|
|
111
|
+
const effectiveConfig = this.resolveEffectiveConfig();
|
|
112
|
+
const creds = await resolveAuth(effectiveConfig, "apikey");
|
|
108
113
|
if (creds.method === "none") {
|
|
109
|
-
throw new ApiError("Not authenticated.
|
|
114
|
+
throw new ApiError("Not authenticated. MCP uses API keys only — run: zivis auth bind", 401, "AUTH_REQUIRED");
|
|
110
115
|
}
|
|
111
|
-
const url = `${
|
|
116
|
+
const url = `${effectiveConfig.apiBaseUrl}${path}`;
|
|
112
117
|
const headers = {
|
|
113
118
|
"User-Agent": "@zivis-ai/cli",
|
|
114
119
|
...this.buildAuthHeaders(creds),
|
package/dist/api-client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"api-client.js","sourceRoot":"","sources":["../src/api-client.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/index.d.ts
CHANGED
|
@@ -2,15 +2,19 @@
|
|
|
2
2
|
* Auth Orchestrator
|
|
3
3
|
*
|
|
4
4
|
* Resolves credentials using the defined precedence:
|
|
5
|
-
* 1. OS Keychain —
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* 1. OS Keychain — Local Project Binding Token / API key (apikey.<session>)
|
|
6
|
+
* Minted by `zivis auth bind` — org-scoped, never expires, no refresh drift.
|
|
7
|
+
* The MCP server resolves with forceMethod=`"apikey"` only: OAuth is for
|
|
8
|
+
* browser login that *mints* this key, not for Bearer access to tenant APIs.
|
|
9
|
+
* 2. ZIVIS_API_KEY env var (CI environments)
|
|
10
|
+
* 3. OS Keychain — OAuth tokens (CLI `zivis auth status` / token refresh only — not MCP)
|
|
11
|
+
* 4. OS Keychain — default API key (no named session)
|
|
12
|
+
* 5. Not authenticated
|
|
9
13
|
*/
|
|
10
14
|
import type { AuthCredentials, AuthMethod, ZivisConfig } from "../types.js";
|
|
11
15
|
export { login } from "./oauth.js";
|
|
12
16
|
export { keychainClearAll, keychainClearSession, getStorageName, getSessionAccount } from "./keychain.js";
|
|
13
|
-
export { keychainStore, keychainRead } from "./keychain.js";
|
|
17
|
+
export { keychainStore, keychainRead, keychainDelete } from "./keychain.js";
|
|
14
18
|
export { loadTokens } from "./token-refresh.js";
|
|
15
19
|
export { loadSessionIndex, saveSessionEntry, removeSessionEntry, listSessions } from "./sessions.js";
|
|
16
20
|
/**
|
package/dist/auth/index.js
CHANGED
|
@@ -2,17 +2,21 @@
|
|
|
2
2
|
* Auth Orchestrator
|
|
3
3
|
*
|
|
4
4
|
* Resolves credentials using the defined precedence:
|
|
5
|
-
* 1. OS Keychain —
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
5
|
+
* 1. OS Keychain — Local Project Binding Token / API key (apikey.<session>)
|
|
6
|
+
* Minted by `zivis auth bind` — org-scoped, never expires, no refresh drift.
|
|
7
|
+
* The MCP server resolves with forceMethod=`"apikey"` only: OAuth is for
|
|
8
|
+
* browser login that *mints* this key, not for Bearer access to tenant APIs.
|
|
9
|
+
* 2. ZIVIS_API_KEY env var (CI environments)
|
|
10
|
+
* 3. OS Keychain — OAuth tokens (CLI `zivis auth status` / token refresh only — not MCP)
|
|
11
|
+
* 4. OS Keychain — default API key (no named session)
|
|
12
|
+
* 5. Not authenticated
|
|
9
13
|
*/
|
|
10
14
|
import { keychainRead } from "./keychain.js";
|
|
11
15
|
import { getValidAccessToken } from "./token-refresh.js";
|
|
12
|
-
import { DEFAULT_CONFIG } from "../types.js";
|
|
16
|
+
import { DEFAULT_CONFIG, credentialStorageFromConfig } from "../types.js";
|
|
13
17
|
export { login } from "./oauth.js";
|
|
14
18
|
export { keychainClearAll, keychainClearSession, getStorageName, getSessionAccount } from "./keychain.js";
|
|
15
|
-
export { keychainStore, keychainRead } from "./keychain.js";
|
|
19
|
+
export { keychainStore, keychainRead, keychainDelete } from "./keychain.js";
|
|
16
20
|
export { loadTokens } from "./token-refresh.js";
|
|
17
21
|
export { loadSessionIndex, saveSessionEntry, removeSessionEntry, listSessions } from "./sessions.js";
|
|
18
22
|
/**
|
|
@@ -26,34 +30,46 @@ export { loadSessionIndex, saveSessionEntry, removeSessionEntry, listSessions }
|
|
|
26
30
|
*/
|
|
27
31
|
export async function resolveAuth(config = DEFAULT_CONFIG, forceMethod) {
|
|
28
32
|
const session = config.session;
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
33
|
+
const st = credentialStorageFromConfig(config);
|
|
34
|
+
// 1. Per-session keychain API key — Local Project Binding Token (preferred).
|
|
35
|
+
// Minted by `zivis auth bind` and stored under `apikey.<session>`.
|
|
36
|
+
// Org-scoped at the WorkOS level, never refreshes, no drift risk.
|
|
37
|
+
// Also covers `zivis auth token sk_xxx --session <name>` for CI injection.
|
|
38
|
+
if (forceMethod !== "oauth") {
|
|
39
|
+
const apiKeyAccount = session ? `apikey.${session}` : null;
|
|
40
|
+
if (apiKeyAccount) {
|
|
41
|
+
const sessionApiKey = await keychainRead(apiKeyAccount, st);
|
|
42
|
+
if (sessionApiKey) {
|
|
43
|
+
return { method: "apikey", apiKey: sessionApiKey };
|
|
44
|
+
}
|
|
34
45
|
}
|
|
35
46
|
}
|
|
36
|
-
// 2. ZIVIS_API_KEY env var —
|
|
37
|
-
// This is an explicit user action (they set the env var in their MCP config),
|
|
38
|
-
// so it's safe to use regardless of session scope.
|
|
47
|
+
// 2. ZIVIS_API_KEY env var (CI environments — explicit, no session ambiguity).
|
|
39
48
|
const envKey = process.env.ZIVIS_API_KEY;
|
|
40
49
|
if (envKey) {
|
|
41
50
|
return { method: "apikey", apiKey: envKey };
|
|
42
51
|
}
|
|
52
|
+
// 3. OAuth tokens (fallback for `zivis auth login` users who haven't run bind).
|
|
53
|
+
if (forceMethod !== "apikey") {
|
|
54
|
+
const accessToken = await getValidAccessToken(config);
|
|
55
|
+
if (accessToken) {
|
|
56
|
+
return { method: "oauth", accessToken };
|
|
57
|
+
}
|
|
58
|
+
}
|
|
43
59
|
// CROSS-TENANT SAFETY: If a named session is active, do NOT fall through
|
|
44
60
|
// to default keychain credentials. This prevents accidentally using admin tokens
|
|
45
61
|
// when working in a customer's project.
|
|
46
62
|
if (session) {
|
|
47
63
|
return { method: "none" };
|
|
48
64
|
}
|
|
49
|
-
//
|
|
65
|
+
// 4. Default API key from keychain (no named session).
|
|
50
66
|
if (forceMethod !== "oauth") {
|
|
51
|
-
const apiKey = await keychainRead("apikey");
|
|
67
|
+
const apiKey = await keychainRead("apikey", st);
|
|
52
68
|
if (apiKey) {
|
|
53
69
|
return { method: "apikey", apiKey };
|
|
54
70
|
}
|
|
55
71
|
}
|
|
56
|
-
//
|
|
72
|
+
// 5. Not authenticated
|
|
57
73
|
return { method: "none" };
|
|
58
74
|
}
|
|
59
75
|
/**
|
package/dist/auth/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/auth/index.ts"],"names":[],"mappings":"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"}
|
package/dist/auth/keychain.d.ts
CHANGED
|
@@ -10,24 +10,25 @@
|
|
|
10
10
|
*
|
|
11
11
|
* Falls back to encrypted file if keychain CLI is unavailable.
|
|
12
12
|
*/
|
|
13
|
+
import { type CredentialStorage } from "../types.js";
|
|
13
14
|
/**
|
|
14
15
|
* Returns the keychain account key for a given session.
|
|
15
16
|
* - No session or undefined → "oauth" (backward compatible)
|
|
16
17
|
* - Named session → "oauth.{session}"
|
|
17
18
|
*/
|
|
18
19
|
export declare function getSessionAccount(session?: string): string;
|
|
19
|
-
export declare function keychainStore(account: string, value: string): Promise<void>;
|
|
20
|
-
export declare function keychainRead(account: string): Promise<string | null>;
|
|
21
|
-
export declare function keychainDelete(account: string): Promise<void>;
|
|
20
|
+
export declare function keychainStore(account: string, value: string, storage?: CredentialStorage): Promise<void>;
|
|
21
|
+
export declare function keychainRead(account: string, storage?: CredentialStorage): Promise<string | null>;
|
|
22
|
+
export declare function keychainDelete(account: string, storage?: CredentialStorage): Promise<void>;
|
|
22
23
|
/**
|
|
23
24
|
* Delete credentials for a specific session
|
|
24
25
|
*/
|
|
25
|
-
export declare function keychainClearSession(session?: string): Promise<void>;
|
|
26
|
+
export declare function keychainClearSession(session?: string, storage?: CredentialStorage): Promise<void>;
|
|
26
27
|
/**
|
|
27
28
|
* Delete all stored credentials (default OAuth + API key).
|
|
28
29
|
* Does NOT clear named sessions — use keychainClearSession() for those.
|
|
29
30
|
*/
|
|
30
|
-
export declare function keychainClearAll(): Promise<void>;
|
|
31
|
+
export declare function keychainClearAll(storage?: CredentialStorage): Promise<void>;
|
|
31
32
|
/**
|
|
32
33
|
* Returns the storage mechanism name for display purposes
|
|
33
34
|
*/
|
package/dist/auth/keychain.js
CHANGED
|
@@ -18,8 +18,12 @@ import * as os from "node:os";
|
|
|
18
18
|
import * as crypto from "node:crypto";
|
|
19
19
|
import { ENV_CONFIG } from "../types.js";
|
|
20
20
|
const execFileAsync = promisify(execFile);
|
|
21
|
-
|
|
22
|
-
|
|
21
|
+
function resolvedStorage(storage) {
|
|
22
|
+
return (storage ?? {
|
|
23
|
+
keychainPrefix: ENV_CONFIG.keychainPrefix,
|
|
24
|
+
configDir: ENV_CONFIG.configDir,
|
|
25
|
+
});
|
|
26
|
+
}
|
|
23
27
|
/**
|
|
24
28
|
* Returns the keychain account key for a given session.
|
|
25
29
|
* - No session or undefined → "oauth" (backward compatible)
|
|
@@ -28,38 +32,52 @@ const SERVICE_PREFIX = ENV_CONFIG.keychainPrefix;
|
|
|
28
32
|
export function getSessionAccount(session) {
|
|
29
33
|
return session ? `oauth.${session}` : "oauth";
|
|
30
34
|
}
|
|
31
|
-
function getServiceName(account) {
|
|
32
|
-
return `${
|
|
35
|
+
function getServiceName(account, keychainPrefix) {
|
|
36
|
+
return `${keychainPrefix}.${account}`;
|
|
33
37
|
}
|
|
34
|
-
function getFallbackDir() {
|
|
35
|
-
// Use environment-aware config dir so staging and production are isolated
|
|
36
|
-
const configDir = ENV_CONFIG.configDir;
|
|
38
|
+
function getFallbackDir(configDir) {
|
|
37
39
|
if (process.platform === "win32") {
|
|
38
40
|
return path.join(process.env.APPDATA || os.homedir(), configDir);
|
|
39
41
|
}
|
|
40
42
|
return path.join(os.homedir(), ".config", configDir);
|
|
41
43
|
}
|
|
42
|
-
function getFallbackPath(account) {
|
|
43
|
-
return path.join(getFallbackDir(), `${account}.enc`);
|
|
44
|
+
function getFallbackPath(account, storage) {
|
|
45
|
+
return path.join(getFallbackDir(storage.configDir), `${account}.enc`);
|
|
44
46
|
}
|
|
45
47
|
// ── macOS Keychain ──
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
48
|
+
/**
|
|
49
|
+
* Drains every entry that matches `-a zivis-mcp -s <service>`. The macOS
|
|
50
|
+
* keychain happily keeps duplicate entries for the same service+account pair
|
|
51
|
+
* (different creation timestamps), so a single delete leaves stragglers behind.
|
|
52
|
+
* Loop until the command errors (no entries left).
|
|
53
|
+
*/
|
|
54
|
+
async function macosDeleteAll(service) {
|
|
55
|
+
for (let i = 0; i < 100; i++) {
|
|
56
|
+
try {
|
|
57
|
+
await execFileAsync("security", [
|
|
58
|
+
"delete-generic-password",
|
|
59
|
+
"-a",
|
|
60
|
+
"zivis-mcp",
|
|
61
|
+
"-s",
|
|
62
|
+
service,
|
|
63
|
+
]);
|
|
64
|
+
}
|
|
65
|
+
catch {
|
|
66
|
+
return; // No more entries.
|
|
67
|
+
}
|
|
60
68
|
}
|
|
69
|
+
}
|
|
70
|
+
async function macosStore(account, value, keychainPrefix) {
|
|
71
|
+
const service = getServiceName(account, keychainPrefix);
|
|
72
|
+
// Drain stale duplicates before writing — guards against history left by
|
|
73
|
+
// older builds that didn't clean up after themselves.
|
|
74
|
+
await macosDeleteAll(service);
|
|
75
|
+
// -U updates in place if a matching entry exists, otherwise creates one.
|
|
76
|
+
// Belt-and-braces with the drain above; together they keep the keychain
|
|
77
|
+
// from accumulating duplicate generic-password entries.
|
|
61
78
|
await execFileAsync("security", [
|
|
62
79
|
"add-generic-password",
|
|
80
|
+
"-U",
|
|
63
81
|
"-a",
|
|
64
82
|
"zivis-mcp",
|
|
65
83
|
"-s",
|
|
@@ -68,8 +86,8 @@ async function macosStore(account, value) {
|
|
|
68
86
|
value,
|
|
69
87
|
]);
|
|
70
88
|
}
|
|
71
|
-
async function macosRead(account) {
|
|
72
|
-
const service = getServiceName(account);
|
|
89
|
+
async function macosRead(account, keychainPrefix) {
|
|
90
|
+
const service = getServiceName(account, keychainPrefix);
|
|
73
91
|
try {
|
|
74
92
|
const { stdout } = await execFileAsync("security", [
|
|
75
93
|
"find-generic-password",
|
|
@@ -85,29 +103,17 @@ async function macosRead(account) {
|
|
|
85
103
|
return null;
|
|
86
104
|
}
|
|
87
105
|
}
|
|
88
|
-
async function macosDelete(account) {
|
|
89
|
-
|
|
90
|
-
try {
|
|
91
|
-
await execFileAsync("security", [
|
|
92
|
-
"delete-generic-password",
|
|
93
|
-
"-a",
|
|
94
|
-
"zivis-mcp",
|
|
95
|
-
"-s",
|
|
96
|
-
service,
|
|
97
|
-
]);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// Entry didn't exist
|
|
101
|
-
}
|
|
106
|
+
async function macosDelete(account, keychainPrefix) {
|
|
107
|
+
await macosDeleteAll(getServiceName(account, keychainPrefix));
|
|
102
108
|
}
|
|
103
109
|
// ── Linux Secret Service ──
|
|
104
|
-
async function linuxStore(account, value) {
|
|
110
|
+
async function linuxStore(account, value, keychainPrefix) {
|
|
105
111
|
const child = execFileAsync("secret-tool", [
|
|
106
112
|
"store",
|
|
107
113
|
"--label",
|
|
108
114
|
`ZIVIS MCP ${account}`,
|
|
109
115
|
"service",
|
|
110
|
-
|
|
116
|
+
keychainPrefix,
|
|
111
117
|
"account",
|
|
112
118
|
account,
|
|
113
119
|
]);
|
|
@@ -116,12 +122,12 @@ async function linuxStore(account, value) {
|
|
|
116
122
|
child.child.stdin?.end();
|
|
117
123
|
await child;
|
|
118
124
|
}
|
|
119
|
-
async function linuxRead(account) {
|
|
125
|
+
async function linuxRead(account, keychainPrefix) {
|
|
120
126
|
try {
|
|
121
127
|
const { stdout } = await execFileAsync("secret-tool", [
|
|
122
128
|
"lookup",
|
|
123
129
|
"service",
|
|
124
|
-
|
|
130
|
+
keychainPrefix,
|
|
125
131
|
"account",
|
|
126
132
|
account,
|
|
127
133
|
]);
|
|
@@ -131,12 +137,12 @@ async function linuxRead(account) {
|
|
|
131
137
|
return null;
|
|
132
138
|
}
|
|
133
139
|
}
|
|
134
|
-
async function linuxDelete(account) {
|
|
140
|
+
async function linuxDelete(account, keychainPrefix) {
|
|
135
141
|
try {
|
|
136
142
|
await execFileAsync("secret-tool", [
|
|
137
143
|
"clear",
|
|
138
144
|
"service",
|
|
139
|
-
|
|
145
|
+
keychainPrefix,
|
|
140
146
|
"account",
|
|
141
147
|
account,
|
|
142
148
|
]);
|
|
@@ -151,7 +157,7 @@ function deriveKey() {
|
|
|
151
157
|
const seed = `zivis-mcp:${os.hostname()}:${os.userInfo().username}`;
|
|
152
158
|
return crypto.createHash("sha256").update(seed).digest();
|
|
153
159
|
}
|
|
154
|
-
async function fileStore(account, value) {
|
|
160
|
+
async function fileStore(account, value, storage) {
|
|
155
161
|
const key = deriveKey();
|
|
156
162
|
const iv = crypto.randomBytes(16);
|
|
157
163
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -159,13 +165,13 @@ async function fileStore(account, value) {
|
|
|
159
165
|
let encrypted = cipher.update(value, "utf8", "hex");
|
|
160
166
|
encrypted += cipher.final("hex");
|
|
161
167
|
const data = JSON.stringify({ iv: iv.toString("hex"), data: encrypted });
|
|
162
|
-
const dir = getFallbackDir();
|
|
168
|
+
const dir = getFallbackDir(storage.configDir);
|
|
163
169
|
await fs.mkdir(dir, { recursive: true });
|
|
164
|
-
await fs.writeFile(getFallbackPath(account), data, { mode: 0o600 });
|
|
170
|
+
await fs.writeFile(getFallbackPath(account, storage), data, { mode: 0o600 });
|
|
165
171
|
}
|
|
166
|
-
async function fileRead(account) {
|
|
172
|
+
async function fileRead(account, storage) {
|
|
167
173
|
try {
|
|
168
|
-
const raw = await fs.readFile(getFallbackPath(account), "utf8");
|
|
174
|
+
const raw = await fs.readFile(getFallbackPath(account, storage), "utf8");
|
|
169
175
|
const { iv, data } = JSON.parse(raw);
|
|
170
176
|
const key = deriveKey();
|
|
171
177
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -178,9 +184,9 @@ async function fileRead(account) {
|
|
|
178
184
|
return null;
|
|
179
185
|
}
|
|
180
186
|
}
|
|
181
|
-
async function fileDelete(account) {
|
|
187
|
+
async function fileDelete(account, storage) {
|
|
182
188
|
try {
|
|
183
|
-
await fs.unlink(getFallbackPath(account));
|
|
189
|
+
await fs.unlink(getFallbackPath(account, storage));
|
|
184
190
|
}
|
|
185
191
|
catch {
|
|
186
192
|
// File didn't exist
|
|
@@ -211,51 +217,58 @@ async function useKeychain() {
|
|
|
211
217
|
return keychainAvailable;
|
|
212
218
|
}
|
|
213
219
|
// ── Public API ──
|
|
214
|
-
export async function keychainStore(account, value) {
|
|
220
|
+
export async function keychainStore(account, value, storage) {
|
|
221
|
+
const st = resolvedStorage(storage);
|
|
215
222
|
// Always store to encrypted file as fallback (for environments where keychain CLI
|
|
216
223
|
// may not be available, e.g., MCP servers spawned by IDEs with different $PATH)
|
|
217
|
-
await fileStore(account, value);
|
|
224
|
+
await fileStore(account, value, st);
|
|
218
225
|
// Also store to native keychain if available
|
|
219
226
|
if (await useKeychain()) {
|
|
220
227
|
if (process.platform === "darwin") {
|
|
221
|
-
await macosStore(account, value);
|
|
228
|
+
await macosStore(account, value, st.keychainPrefix);
|
|
222
229
|
}
|
|
223
230
|
else if (process.platform === "linux") {
|
|
224
|
-
await linuxStore(account, value);
|
|
231
|
+
await linuxStore(account, value, st.keychainPrefix);
|
|
225
232
|
}
|
|
226
233
|
}
|
|
227
234
|
}
|
|
228
|
-
export async function keychainRead(account) {
|
|
235
|
+
export async function keychainRead(account, storage) {
|
|
236
|
+
const st = resolvedStorage(storage);
|
|
229
237
|
if (await useKeychain()) {
|
|
230
238
|
if (process.platform === "darwin")
|
|
231
|
-
return macosRead(account);
|
|
239
|
+
return macosRead(account, st.keychainPrefix);
|
|
232
240
|
if (process.platform === "linux")
|
|
233
|
-
return linuxRead(account);
|
|
241
|
+
return linuxRead(account, st.keychainPrefix);
|
|
234
242
|
}
|
|
235
|
-
return fileRead(account);
|
|
243
|
+
return fileRead(account, st);
|
|
236
244
|
}
|
|
237
|
-
export async function keychainDelete(account) {
|
|
245
|
+
export async function keychainDelete(account, storage) {
|
|
246
|
+
const st = resolvedStorage(storage);
|
|
247
|
+
// keychainStore writes to BOTH the native keychain AND the encrypted file
|
|
248
|
+
// fallback (so MCP servers spawned by IDEs can still read credentials when
|
|
249
|
+
// the `security` CLI is unreachable on $PATH). Delete must wipe both, or
|
|
250
|
+
// a stale OAuth/api-key file silently outlives a logout.
|
|
251
|
+
await fileDelete(account, st);
|
|
238
252
|
if (await useKeychain()) {
|
|
239
253
|
if (process.platform === "darwin")
|
|
240
|
-
return macosDelete(account);
|
|
254
|
+
return macosDelete(account, st.keychainPrefix);
|
|
241
255
|
if (process.platform === "linux")
|
|
242
|
-
return linuxDelete(account);
|
|
256
|
+
return linuxDelete(account, st.keychainPrefix);
|
|
243
257
|
}
|
|
244
|
-
return fileDelete(account);
|
|
245
258
|
}
|
|
246
259
|
/**
|
|
247
260
|
* Delete credentials for a specific session
|
|
248
261
|
*/
|
|
249
|
-
export async function keychainClearSession(session) {
|
|
250
|
-
await keychainDelete(getSessionAccount(session));
|
|
262
|
+
export async function keychainClearSession(session, storage) {
|
|
263
|
+
await keychainDelete(getSessionAccount(session), storage);
|
|
251
264
|
}
|
|
252
265
|
/**
|
|
253
266
|
* Delete all stored credentials (default OAuth + API key).
|
|
254
267
|
* Does NOT clear named sessions — use keychainClearSession() for those.
|
|
255
268
|
*/
|
|
256
|
-
export async function keychainClearAll() {
|
|
257
|
-
await keychainDelete("oauth");
|
|
258
|
-
await keychainDelete("apikey");
|
|
269
|
+
export async function keychainClearAll(storage) {
|
|
270
|
+
await keychainDelete("oauth", storage);
|
|
271
|
+
await keychainDelete("apikey", storage);
|
|
259
272
|
}
|
|
260
273
|
/**
|
|
261
274
|
* Returns the storage mechanism name for display purposes
|