@juspay/neurolink 13.8.16 → 13.8.18
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +2 -2
- package/README.md +1 -1
- package/dist/action/actionExecutor.d.ts +33 -3
- package/dist/action/actionExecutor.js +71 -15
- package/dist/action/actionInputs.js +4 -1
- package/dist/action/cliVersion.d.ts +25 -0
- package/dist/action/cliVersion.js +26 -0
- package/dist/action/index.d.ts +2 -1
- package/dist/action/index.js +2 -1
- package/dist/adapters/video/directorPipeline.js +46 -36
- package/dist/adapters/video/ffmpegAdapter.d.ts +17 -0
- package/dist/adapters/video/ffmpegAdapter.js +32 -5
- package/dist/adapters/video/frameExtractor.js +1 -1
- package/dist/adapters/video/videoMerger.js +5 -2
- package/dist/agent/arithmeticEvaluator.d.ts +32 -0
- package/dist/agent/arithmeticEvaluator.js +349 -0
- package/dist/agent/directTools.d.ts +6 -18
- package/dist/agent/directTools.js +17 -56
- package/dist/artifacts/artifactStore.d.ts +22 -1
- package/dist/artifacts/artifactStore.js +66 -6
- package/dist/auth/anthropicOAuth.d.ts +26 -1
- package/dist/auth/anthropicOAuth.js +81 -5
- package/dist/auth/secureFiles.d.ts +37 -0
- package/dist/auth/secureFiles.js +95 -0
- package/dist/auth/tokenStore.d.ts +43 -6
- package/dist/auth/tokenStore.js +260 -79
- package/dist/avatar/providers/DIDAvatar.js +49 -45
- package/dist/avatar/providers/ReplicateAvatar.js +33 -40
- package/dist/browser/neurolink.min.js +485 -558
- package/dist/cli/commands/auth.js +112 -70
- package/dist/cli/commands/proxy.d.ts +4 -0
- package/dist/cli/commands/proxy.js +128 -13
- package/dist/cli/commands/serve.d.ts +10 -1
- package/dist/cli/commands/serve.js +98 -17
- package/dist/cli/commands/server.js +17 -5
- package/dist/cli/index.js +146 -5
- package/dist/cli/loop/optionsSchema.d.ts +1 -1
- package/dist/cli/utils/envManager.js +17 -3
- package/dist/cli/utils/serverUtils.d.ts +39 -3
- package/dist/cli/utils/serverUtils.js +63 -6
- package/dist/core/baseProvider.js +7 -1
- package/dist/core/modules/MessageBuilder.js +5 -0
- package/dist/core/modules/ToolsManager.d.ts +46 -1
- package/dist/core/modules/ToolsManager.js +190 -43
- package/dist/files/fileReferenceRegistry.d.ts +2 -23
- package/dist/files/fileReferenceRegistry.js +55 -5
- package/dist/hitl/hitlManager.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +6 -1
- package/dist/mcp/auth/tokenStorage.d.ts +7 -0
- package/dist/mcp/auth/tokenStorage.js +23 -4
- package/dist/mcp/externalServerManager.d.ts +13 -3
- package/dist/mcp/externalServerManager.js +154 -136
- package/dist/mcp/index.d.ts +2 -0
- package/dist/mcp/index.js +3 -0
- package/dist/mcp/mcpClientFactory.d.ts +14 -0
- package/dist/mcp/mcpClientFactory.js +70 -7
- package/dist/mcp/projectConfigTrust.d.ts +72 -0
- package/dist/mcp/projectConfigTrust.js +237 -0
- package/dist/mcp/transportUrlPolicy.d.ts +39 -0
- package/dist/mcp/transportUrlPolicy.js +172 -0
- package/dist/music/providers/BeatovenMusic.js +35 -46
- package/dist/music/providers/ReplicateMusic.js +33 -40
- package/dist/neurolink.d.ts +16 -1
- package/dist/neurolink.js +64 -11
- package/dist/processors/archive/ArchiveProcessor.js +15 -1
- package/dist/processors/archive/zipEntryIndex.d.ts +22 -0
- package/dist/processors/archive/zipEntryIndex.js +33 -0
- package/dist/processors/base/BaseFileProcessor.d.ts +9 -21
- package/dist/processors/base/BaseFileProcessor.js +40 -111
- package/dist/processors/data/YamlProcessor.d.ts +13 -1
- package/dist/processors/data/YamlProcessor.js +93 -2
- package/dist/processors/document/OpenDocumentProcessor.js +7 -2
- package/dist/processors/document/PptxProcessor.js +12 -7
- package/dist/processors/media/VideoProcessor.js +93 -15
- package/dist/proxy/inboundAuth.d.ts +89 -0
- package/dist/proxy/inboundAuth.js +219 -0
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +13 -1
- package/dist/proxy/updateState.js +4 -4
- package/dist/rag/document/loaders.d.ts +0 -6
- package/dist/rag/document/loaders.js +16 -10
- package/dist/rag/ragIntegration.d.ts +2 -2
- package/dist/rag/ragIntegration.js +35 -10
- package/dist/rag/retrieval/vectorQueryTool.d.ts +7 -0
- package/dist/rag/retrieval/vectorQueryTool.js +54 -17
- package/dist/server/abstract/baseServerAdapter.d.ts +43 -0
- package/dist/server/abstract/baseServerAdapter.js +119 -6
- package/dist/server/adapters/expressAdapter.d.ts +3 -0
- package/dist/server/adapters/expressAdapter.js +83 -8
- package/dist/server/adapters/fastifyAdapter.d.ts +3 -0
- package/dist/server/adapters/fastifyAdapter.js +165 -70
- package/dist/server/adapters/honoAdapter.d.ts +9 -1
- package/dist/server/adapters/honoAdapter.js +99 -21
- package/dist/server/adapters/koaAdapter.d.ts +1 -0
- package/dist/server/adapters/koaAdapter.js +153 -74
- package/dist/server/index.d.ts +2 -0
- package/dist/server/index.js +4 -0
- package/dist/server/middleware/auth.js +6 -2
- package/dist/server/middleware/cache.js +6 -1
- package/dist/server/middleware/rateLimit.js +29 -4
- package/dist/server/routes/agentRoutes.js +24 -11
- package/dist/server/routes/claudeProxyRoutes.d.ts +1 -0
- package/dist/server/routes/claudeProxyRoutes.js +273 -84
- package/dist/server/routes/codexProxyRoutes.d.ts +2 -2
- package/dist/server/routes/codexProxyRoutes.js +125 -40
- package/dist/server/routes/index.d.ts +4 -0
- package/dist/server/routes/index.js +35 -4
- package/dist/server/routes/mcpRoutes.d.ts +6 -2
- package/dist/server/routes/mcpRoutes.js +21 -7
- package/dist/server/routes/memoryRoutes.js +7 -6
- package/dist/server/routes/toolRoutes.d.ts +12 -2
- package/dist/server/routes/toolRoutes.js +33 -3
- package/dist/server/routes/transcribeRoutes.js +60 -13
- package/dist/server/utils/clientAddress.d.ts +23 -0
- package/dist/server/utils/clientAddress.js +65 -0
- package/dist/server/utils/clientError.d.ts +35 -0
- package/dist/server/utils/clientError.js +129 -0
- package/dist/server/utils/expressBody.d.ts +33 -0
- package/dist/server/utils/expressBody.js +353 -0
- package/dist/server/utils/pathMatch.d.ts +21 -0
- package/dist/server/utils/pathMatch.js +41 -0
- package/dist/server/utils/validation.d.ts +9 -0
- package/dist/server/utils/validation.js +9 -0
- package/dist/server/websocket/WebSocketHandler.js +22 -3
- package/dist/server/websocket/frameQueue.d.ts +19 -0
- package/dist/server/websocket/frameQueue.js +41 -3
- package/dist/server/websocket/transcribeWebSocket.d.ts +8 -1
- package/dist/server/websocket/transcribeWebSocket.js +223 -34
- package/dist/skills/skillStores.js +5 -0
- package/dist/tasks/store/fileTaskStore.d.ts +2 -0
- package/dist/tasks/store/fileTaskStore.js +11 -4
- package/dist/types/attachments.d.ts +117 -0
- package/dist/types/attachments.js +15 -0
- package/dist/types/auth.d.ts +6 -0
- package/dist/types/cli.d.ts +4 -0
- package/dist/types/config.d.ts +56 -0
- package/dist/types/file.d.ts +8 -0
- package/dist/types/hitl.d.ts +8 -0
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.js +1 -0
- package/dist/types/mcp.d.ts +98 -0
- package/dist/types/multimodal.js +0 -41
- package/dist/types/processor.d.ts +14 -1
- package/dist/types/proxy.d.ts +62 -3
- package/dist/types/rag.d.ts +9 -0
- package/dist/types/safeFetch.d.ts +40 -0
- package/dist/types/server.d.ts +255 -10
- package/dist/types/tools.d.ts +57 -0
- package/dist/types/transcribe.d.ts +6 -0
- package/dist/utils/attachmentPolicy.d.ts +89 -0
- package/dist/utils/attachmentPolicy.js +327 -0
- package/dist/utils/dotenvBootstrap.d.ts +48 -19
- package/dist/utils/dotenvBootstrap.js +172 -31
- package/dist/utils/fileDetector.js +115 -65
- package/dist/utils/json/coerce.js +11 -1
- package/dist/utils/logSanitize.d.ts +5 -0
- package/dist/utils/logSanitize.js +8 -0
- package/dist/utils/logger.js +73 -1
- package/dist/utils/messageBuilder.d.ts +2 -2
- package/dist/utils/messageBuilder.js +118 -105
- package/dist/utils/multimodalOptionsBuilder.d.ts +1 -0
- package/dist/utils/multimodalOptionsBuilder.js +2 -0
- package/dist/utils/redis.d.ts +12 -0
- package/dist/utils/redis.js +72 -0
- package/dist/utils/safeFetch.d.ts +22 -1
- package/dist/utils/safeFetch.js +132 -38
- package/dist/utils/safeStoreId.d.ts +19 -0
- package/dist/utils/safeStoreId.js +29 -0
- package/dist/utils/schemaConversion.d.ts +30 -3
- package/dist/utils/schemaConversion.js +457 -84
- package/dist/utils/sizeGuard.js +2 -2
- package/dist/utils/ssrfGuard.d.ts +3 -3
- package/dist/utils/ssrfGuard.js +71 -39
- package/dist/voice/eval/sttEvalStore.d.ts +26 -3
- package/dist/voice/eval/sttEvalStore.js +77 -12
- package/dist/voice/livekit/agentDefinition.d.ts +18 -0
- package/dist/voice/livekit/agentDefinition.js +22 -0
- package/dist/voice/livekit/realtimeMcpTools.d.ts +1 -1
- package/dist/voice/livekit/realtimeMcpTools.js +8 -7
- package/dist/voice/livekit/realtimeVoiceAgent.d.ts +2 -4
- package/dist/voice/livekit/realtimeVoiceAgent.js +18 -7
- package/dist/voice/livekit/voiceAgent.d.ts +2 -2
- package/dist/voice/livekit/voiceAgent.js +2 -1
- package/dist/voice/livekit/xaiSessionWatch.d.ts +0 -5
- package/dist/voice/livekit/xaiSessionWatch.js +30 -0
- package/dist/voice/providers/GeminiLive.js +21 -6
- package/dist/voice/providers/GoogleSTT.js +8 -5
- package/dist/voice/transcribe.d.ts +2 -2
- package/dist/voice/transcribe.js +34 -18
- package/dist/voice/whistle/audio.js +11 -5
- package/docs-site/static/search-index.json +75 -52
- package/package.json +171 -156
- package/scripts/observability/import-openobserve-dashboard.mjs +11 -3
|
@@ -26,7 +26,9 @@ import chalk from "chalk";
|
|
|
26
26
|
import ora from "ora";
|
|
27
27
|
import { logger } from "../../utils/logger.js";
|
|
28
28
|
import { defaultTokenStore } from "../../auth/tokenStore.js";
|
|
29
|
-
import { CLAUDE_CODE_CLIENT_ID, ANTHROPIC_AUTH_URL, ANTHROPIC_TOKEN_URL, ANTHROPIC_REDIRECT_URI, CLAUDE_CLI_USER_AGENT, OAUTH_BETA_HEADERS, } from "../../auth/anthropicOAuth.js";
|
|
29
|
+
import { CLAUDE_CODE_CLIENT_ID, ANTHROPIC_AUTH_URL, ANTHROPIC_TOKEN_URL, ANTHROPIC_REDIRECT_URI, CLAUDE_CLI_USER_AGENT, OAUTH_BETA_HEADERS, buildSubscriptionAuthUrl, exchangePastedSubscriptionCode, parsePastedAuthorizationCode, } from "../../auth/anthropicOAuth.js";
|
|
30
|
+
import { tightenProcessUmask, writePrivateFileAtomicallySync, } from "../../auth/secureFiles.js";
|
|
31
|
+
import { normalizeAnthropicAccountKey } from "../../proxy/accountSelection.js";
|
|
30
32
|
import { flushAccountQuotas, loadAccountQuotas, saveAccountQuota, } from "../../proxy/accountQuota.js";
|
|
31
33
|
import { fetchAccountUsage, listAnthropicAccountsForUsage, usageToQuota, } from "../../proxy/accountUsage.js";
|
|
32
34
|
import { importCodexAuthFile } from "../../auth/codexOAuth.js";
|
|
@@ -78,6 +80,7 @@ const AUTH_LOGIN_PROVIDERS = ["anthropic", "codex"];
|
|
|
78
80
|
* (e.g., "anthropic:alice") to support multi-account pools.
|
|
79
81
|
*/
|
|
80
82
|
export async function handleLogin(argv) {
|
|
83
|
+
tightenProcessUmask();
|
|
81
84
|
try {
|
|
82
85
|
const provider = argv.provider?.toLowerCase();
|
|
83
86
|
// Validate provider
|
|
@@ -142,7 +145,7 @@ export async function handleCodexLogin(argv) {
|
|
|
142
145
|
credential.email ??
|
|
143
146
|
credential.accountId?.slice(0, 8) ??
|
|
144
147
|
Date.now().toString(36).slice(-6);
|
|
145
|
-
const label = rawLabel
|
|
148
|
+
const label = validateAccountLabel(rawLabel);
|
|
146
149
|
const compoundKey = `codex:${label}`;
|
|
147
150
|
const scopeParts = ["codex"];
|
|
148
151
|
if (credential.planType) {
|
|
@@ -1020,6 +1023,7 @@ async function handleCodexRefresh(argv) {
|
|
|
1020
1023
|
* `neurolink auth refresh <provider>`
|
|
1021
1024
|
*/
|
|
1022
1025
|
export async function handleRefresh(argv) {
|
|
1026
|
+
tightenProcessUmask();
|
|
1023
1027
|
try {
|
|
1024
1028
|
const provider = argv.provider?.toLowerCase();
|
|
1025
1029
|
// Validate provider
|
|
@@ -1093,15 +1097,25 @@ export async function handleRefresh(argv) {
|
|
|
1093
1097
|
createdAt: credentials.createdAt,
|
|
1094
1098
|
updatedAt: Date.now(),
|
|
1095
1099
|
});
|
|
1096
|
-
// Also update the TokenStore pool entries for this provider
|
|
1097
|
-
//
|
|
1098
|
-
//
|
|
1100
|
+
// Also update the TokenStore pool entries for this provider — but only
|
|
1101
|
+
// the entries that held the credential just refreshed. Writing the new
|
|
1102
|
+
// token to every `provider:*` key collapsed a multi-account pool into
|
|
1103
|
+
// one identity and threw the other accounts' refresh tokens away.
|
|
1104
|
+
const previousAccessToken = credentials.oauth.accessToken;
|
|
1105
|
+
const previousRefreshToken = credentials.oauth.refreshToken;
|
|
1099
1106
|
try {
|
|
1100
1107
|
const allKeys = await defaultTokenStore.listProviders();
|
|
1101
1108
|
for (const key of allKeys) {
|
|
1102
1109
|
if (key === provider || key.startsWith(`${provider}:`)) {
|
|
1103
1110
|
const existingTokens = await defaultTokenStore.loadTokens(key);
|
|
1104
|
-
|
|
1111
|
+
const heldThisCredential = existingTokens !== null &&
|
|
1112
|
+
(existingTokens.accessToken === previousAccessToken ||
|
|
1113
|
+
(previousRefreshToken !== undefined &&
|
|
1114
|
+
existingTokens.refreshToken === previousRefreshToken));
|
|
1115
|
+
if (existingTokens && !heldThisCredential) {
|
|
1116
|
+
logger.debug(`Skipping TokenStore entry ${key}: it holds a different account's credential`);
|
|
1117
|
+
}
|
|
1118
|
+
if (existingTokens && heldThisCredential) {
|
|
1105
1119
|
await defaultTokenStore.saveTokens(key, {
|
|
1106
1120
|
accessToken: newTokens.accessToken,
|
|
1107
1121
|
refreshToken: newTokens.refreshToken,
|
|
@@ -1945,11 +1959,13 @@ async function handleCreateApiKeyOAuth(provider) {
|
|
|
1945
1959
|
logger.always(chalk.yellow("Copy that code and paste it back here.\n"));
|
|
1946
1960
|
}
|
|
1947
1961
|
const spinner = ora("Starting OAuth flow...").start();
|
|
1948
|
-
//
|
|
1962
|
+
// PKCE verifier and an unrelated random state. The state is what the pasted
|
|
1963
|
+
// code must come back with; the verifier never leaves this process.
|
|
1949
1964
|
const codeVerifier = randomBytes(32).toString("base64url");
|
|
1950
1965
|
const codeChallenge = createHash("sha256")
|
|
1951
1966
|
.update(codeVerifier)
|
|
1952
1967
|
.digest("base64url");
|
|
1968
|
+
const oauthState = randomBytes(32).toString("base64url");
|
|
1953
1969
|
// Build authorization URL using CONSOLE config (for API key creation scope)
|
|
1954
1970
|
// Based on opencode-anthropic-auth implementation
|
|
1955
1971
|
const authUrl = new URL(ANTHROPIC_CONSOLE_OAUTH_CONFIG.authorizationUrl);
|
|
@@ -1960,8 +1976,7 @@ async function handleCreateApiKeyOAuth(provider) {
|
|
|
1960
1976
|
authUrl.searchParams.set("scope", ANTHROPIC_CONSOLE_OAUTH_CONFIG.scope);
|
|
1961
1977
|
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
1962
1978
|
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
1963
|
-
|
|
1964
|
-
authUrl.searchParams.set("state", codeVerifier);
|
|
1979
|
+
authUrl.searchParams.set("state", oauthState);
|
|
1965
1980
|
spinner.text = "Opening browser for authentication...";
|
|
1966
1981
|
// Open browser
|
|
1967
1982
|
try {
|
|
@@ -2005,15 +2020,17 @@ async function handleCreateApiKeyOAuth(provider) {
|
|
|
2005
2020
|
},
|
|
2006
2021
|
]);
|
|
2007
2022
|
const exchangeSpinner = ora("Exchanging authorization code for tokens...").start();
|
|
2008
|
-
//
|
|
2009
|
-
//
|
|
2010
|
-
//
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2023
|
+
// The pasted value is `code#state`. The state must be the one this login
|
|
2024
|
+
// generated, and the exchange always uses this process's own verifier — a
|
|
2025
|
+
// code from someone else's login is refused before anything is sent.
|
|
2026
|
+
let actualCode;
|
|
2027
|
+
try {
|
|
2028
|
+
actualCode = parsePastedAuthorizationCode(authCode, oauthState).code;
|
|
2029
|
+
}
|
|
2030
|
+
catch (error) {
|
|
2031
|
+
exchangeSpinner.fail("Authorization code rejected");
|
|
2032
|
+
throw error;
|
|
2033
|
+
}
|
|
2017
2034
|
// Exchange code for tokens using JSON body (per opencode-anthropic-auth)
|
|
2018
2035
|
const tokenResponse = await fetch(ANTHROPIC_CONSOLE_OAUTH_CONFIG.tokenUrl, {
|
|
2019
2036
|
method: "POST",
|
|
@@ -2022,11 +2039,11 @@ async function handleCreateApiKeyOAuth(provider) {
|
|
|
2022
2039
|
},
|
|
2023
2040
|
body: JSON.stringify({
|
|
2024
2041
|
code: actualCode,
|
|
2025
|
-
state:
|
|
2042
|
+
state: oauthState,
|
|
2026
2043
|
grant_type: "authorization_code",
|
|
2027
2044
|
client_id: ANTHROPIC_CONSOLE_OAUTH_CONFIG.clientId,
|
|
2028
2045
|
redirect_uri: ANTHROPIC_CONSOLE_OAUTH_CONFIG.redirectUri,
|
|
2029
|
-
code_verifier:
|
|
2046
|
+
code_verifier: codeVerifier,
|
|
2030
2047
|
}),
|
|
2031
2048
|
});
|
|
2032
2049
|
if (!tokenResponse.ok) {
|
|
@@ -2110,23 +2127,21 @@ async function handleOAuthAuth(provider) {
|
|
|
2110
2127
|
logger.always(chalk.yellow("Copy that code and paste it back here.\n"));
|
|
2111
2128
|
}
|
|
2112
2129
|
const spinner = ora("Starting OAuth flow...").start();
|
|
2113
|
-
//
|
|
2130
|
+
// PKCE verifier and an unrelated random state, through the same builder the
|
|
2131
|
+
// split-PKCE provisioning flow uses so the two cannot drift. The verifier
|
|
2132
|
+
// never leaves this process; the state is what the pasted code must carry.
|
|
2114
2133
|
const codeVerifier = randomBytes(32).toString("base64url");
|
|
2115
2134
|
const codeChallenge = createHash("sha256")
|
|
2116
2135
|
.update(codeVerifier)
|
|
2117
2136
|
.digest("base64url");
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
2122
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
authUrl.searchParams.set("code_challenge", codeChallenge);
|
|
2127
|
-
authUrl.searchParams.set("code_challenge_method", "S256");
|
|
2128
|
-
// OpenCode sets state = verifier for simplicity
|
|
2129
|
-
authUrl.searchParams.set("state", codeVerifier);
|
|
2137
|
+
const oauthState = randomBytes(32).toString("base64url");
|
|
2138
|
+
const authUrl = new URL(buildSubscriptionAuthUrl({
|
|
2139
|
+
codeChallenge,
|
|
2140
|
+
state: oauthState,
|
|
2141
|
+
clientId: ANTHROPIC_OAUTH_CONFIG.clientId,
|
|
2142
|
+
redirectUri: ANTHROPIC_OAUTH_CONFIG.redirectUri,
|
|
2143
|
+
scope: ANTHROPIC_OAUTH_CONFIG.scope,
|
|
2144
|
+
}));
|
|
2130
2145
|
spinner.text = "Opening browser for authentication...";
|
|
2131
2146
|
// Open browser
|
|
2132
2147
|
try {
|
|
@@ -2171,36 +2186,35 @@ async function handleOAuthAuth(provider) {
|
|
|
2171
2186
|
]);
|
|
2172
2187
|
const trimmedCode = authCode.trim();
|
|
2173
2188
|
const exchangeSpinner = ora("Exchanging authorization code for tokens...").start();
|
|
2174
|
-
//
|
|
2175
|
-
//
|
|
2176
|
-
//
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
},
|
|
2189
|
-
body: JSON.stringify({
|
|
2190
|
-
code: actualCode,
|
|
2191
|
-
state: codeState,
|
|
2192
|
-
grant_type: "authorization_code",
|
|
2193
|
-
client_id: ANTHROPIC_OAUTH_CONFIG.clientId,
|
|
2194
|
-
redirect_uri: ANTHROPIC_OAUTH_CONFIG.redirectUri,
|
|
2195
|
-
code_verifier: actualVerifier,
|
|
2196
|
-
}),
|
|
2197
|
-
});
|
|
2198
|
-
if (!tokenResponse.ok) {
|
|
2199
|
-
const errorText = await tokenResponse.text();
|
|
2189
|
+
// The pasted value is `code#state`: the state must match this login and the
|
|
2190
|
+
// exchange uses this process's verifier. A code from someone else's login is
|
|
2191
|
+
// refused before anything is sent.
|
|
2192
|
+
let exchanged;
|
|
2193
|
+
try {
|
|
2194
|
+
exchanged = await exchangePastedSubscriptionCode({
|
|
2195
|
+
pasted: trimmedCode,
|
|
2196
|
+
expectedState: oauthState,
|
|
2197
|
+
codeVerifier,
|
|
2198
|
+
clientId: ANTHROPIC_OAUTH_CONFIG.clientId,
|
|
2199
|
+
redirectUri: ANTHROPIC_OAUTH_CONFIG.redirectUri,
|
|
2200
|
+
});
|
|
2201
|
+
}
|
|
2202
|
+
catch (error) {
|
|
2200
2203
|
exchangeSpinner.fail("Token exchange failed");
|
|
2201
|
-
throw
|
|
2204
|
+
throw error;
|
|
2202
2205
|
}
|
|
2203
|
-
const tokenData =
|
|
2206
|
+
const tokenData = {
|
|
2207
|
+
access_token: exchanged.accessToken,
|
|
2208
|
+
refresh_token: exchanged.refreshToken,
|
|
2209
|
+
expires_in: exchanged.expiresAt !== undefined
|
|
2210
|
+
? Math.max(1, Math.round((exchanged.expiresAt - Date.now()) / 1000))
|
|
2211
|
+
: undefined,
|
|
2212
|
+
token_type: exchanged.tokenType,
|
|
2213
|
+
scope: exchanged.scope,
|
|
2214
|
+
account: exchanged.accountEmail
|
|
2215
|
+
? { email_address: exchanged.accountEmail }
|
|
2216
|
+
: undefined,
|
|
2217
|
+
};
|
|
2204
2218
|
// Save tokens
|
|
2205
2219
|
const tokens = {
|
|
2206
2220
|
accessToken: tokenData.access_token,
|
|
@@ -2290,11 +2304,17 @@ async function saveAccountToPool(provider, label) {
|
|
|
2290
2304
|
const credEmail = credentials.email;
|
|
2291
2305
|
let compoundKey;
|
|
2292
2306
|
if (label) {
|
|
2293
|
-
compoundKey = `${provider}:${label}`;
|
|
2307
|
+
compoundKey = `${provider}:${validateAccountLabel(label)}`;
|
|
2294
2308
|
}
|
|
2295
2309
|
else {
|
|
2296
2310
|
const autoLabel = credEmail ?? Date.now().toString(36).slice(-6);
|
|
2297
|
-
compoundKey = `${provider}:${autoLabel}`;
|
|
2311
|
+
compoundKey = `${provider}:${validateAccountLabel(autoLabel)}`;
|
|
2312
|
+
}
|
|
2313
|
+
if (provider === "anthropic") {
|
|
2314
|
+
// The proxy compares Anthropic keys case-insensitively
|
|
2315
|
+
// (normalizeAnthropicAccountKey); saving them as typed let `Alice` and
|
|
2316
|
+
// `alice` coexist as two pool entries that every lookup treated as one.
|
|
2317
|
+
compoundKey = normalizeAnthropicAccountKey(compoundKey);
|
|
2298
2318
|
}
|
|
2299
2319
|
// Decision 10A: Auto-prune stale token-prefix entries when email is known.
|
|
2300
2320
|
// When we have a real email, remove old entries whose stored token value is
|
|
@@ -2542,14 +2562,36 @@ async function getStoredCredentials(provider) {
|
|
|
2542
2562
|
* Save credentials to file
|
|
2543
2563
|
*/
|
|
2544
2564
|
async function saveStoredCredentials(provider, credentials) {
|
|
2545
|
-
// Ensure config directory exists
|
|
2546
|
-
if (!fs.existsSync(NEUROLINK_CONFIG_DIR)) {
|
|
2547
|
-
fs.mkdirSync(NEUROLINK_CONFIG_DIR, { recursive: true });
|
|
2548
|
-
}
|
|
2549
2565
|
const credentialsFile = path.join(NEUROLINK_CONFIG_DIR, `${provider}-credentials.json`);
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2566
|
+
// Owner-only directory and an atomic owner-only write: the previous
|
|
2567
|
+
// truncate-in-place write kept whatever mode the file already had and
|
|
2568
|
+
// followed a symlink planted at the path.
|
|
2569
|
+
//
|
|
2570
|
+
// The refresh token stays in this file deliberately. It is the credential
|
|
2571
|
+
// source for the SDK's Anthropic provider (`providers/anthropic/client.ts`)
|
|
2572
|
+
// and for the proxy's legacy single-account path, both of which refresh from
|
|
2573
|
+
// it; removing it would silently break every caller that never ran
|
|
2574
|
+
// `auth login` against the pooled store.
|
|
2575
|
+
writePrivateFileAtomicallySync(credentialsFile, JSON.stringify(credentials, null, 2));
|
|
2576
|
+
}
|
|
2577
|
+
/**
|
|
2578
|
+
* An account label becomes part of a token-store key, a log line and a
|
|
2579
|
+
* snapshot file name. Control characters and newlines have no place in any of
|
|
2580
|
+
* those (a newline forges a log line), so they are refused rather than stored.
|
|
2581
|
+
*/
|
|
2582
|
+
function validateAccountLabel(label) {
|
|
2583
|
+
const trimmed = label.trim();
|
|
2584
|
+
if (trimmed === "") {
|
|
2585
|
+
throw new Error("Account label must not be empty.");
|
|
2586
|
+
}
|
|
2587
|
+
// eslint-disable-next-line no-control-regex -- the point is to refuse them
|
|
2588
|
+
if (/[\u0000-\u001f\u007f]/.test(trimmed)) {
|
|
2589
|
+
throw new Error("Account label must not contain control characters or line breaks.");
|
|
2590
|
+
}
|
|
2591
|
+
if (trimmed.length > 200) {
|
|
2592
|
+
throw new Error("Account label must be 200 characters or fewer.");
|
|
2593
|
+
}
|
|
2594
|
+
return trimmed;
|
|
2553
2595
|
}
|
|
2554
2596
|
/**
|
|
2555
2597
|
* Check existing authentication status
|
|
@@ -78,6 +78,10 @@ export declare function createProxyStartApp(params: {
|
|
|
78
78
|
updateControlToken?: string;
|
|
79
79
|
/** Port of the gate-only share listener, when one is configured. */
|
|
80
80
|
sharePort?: number;
|
|
81
|
+
/** Bearer every inbound caller must present; omitted means none is required. */
|
|
82
|
+
inboundToken?: string;
|
|
83
|
+
/** The bind policy already ran and accepted an unauthenticated listener. */
|
|
84
|
+
inboundAuthAcknowledged?: boolean;
|
|
81
85
|
}): Promise<{
|
|
82
86
|
app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
|
|
83
87
|
readiness: ProxyReadinessState;
|
|
@@ -20,6 +20,9 @@ import { raceWithAbort, withTimeout, } from "../../utils/async/withTimeout.js";
|
|
|
20
20
|
import { startProxyHttpTrace } from "../../proxy/proxyTracer.js";
|
|
21
21
|
import { formatUptime, isProcessRunning, StateFileManager, } from "../utils/serverUtils.js";
|
|
22
22
|
import { configureProxyKeepAliveDispatcher } from "../../proxy/proxyDispatcher.js";
|
|
23
|
+
import { evaluateProxyBindPolicy, isUnauthenticatedBindAllowedByEnv, resolveInboundToken, } from "../../proxy/inboundAuth.js";
|
|
24
|
+
import { isGrantRequiredByEnv } from "../../proxy/shareGate.js";
|
|
25
|
+
import { tightenProcessUmask } from "../../auth/secureFiles.js";
|
|
23
26
|
import { ProxyRuntimeConfigStore } from "../../proxy/runtimeConfig.js";
|
|
24
27
|
import { sessionAffinity } from "../../proxy/sessionAffinity.js";
|
|
25
28
|
import { startProxyLogCleanupScheduler } from "../../proxy/logCleanupScheduler.js";
|
|
@@ -1439,8 +1442,16 @@ export async function createProxyStartApp(params) {
|
|
|
1439
1442
|
const { recordFinalError } = await import("../../proxy/usageStats.js");
|
|
1440
1443
|
const { admitInboundShareRequest, isGrantRequiredByEnv } = await import("../../proxy/shareGate.js");
|
|
1441
1444
|
const { runWithShareContext } = await import("../../proxy/shareContext.js");
|
|
1445
|
+
const { authorizeInboundRequest, hasValidInboundToken, isInboundAuthExemptPath, secretsMatch, } = await import("../../proxy/inboundAuth.js");
|
|
1442
1446
|
const { Hono } = await import("hono");
|
|
1443
1447
|
const app = new Hono();
|
|
1448
|
+
const inboundToken = params.inboundToken;
|
|
1449
|
+
const inboundAuth = {
|
|
1450
|
+
...(inboundToken ? { token: inboundToken } : {}),
|
|
1451
|
+
// The CLI ran the bind policy; a loopback or acknowledged listener must
|
|
1452
|
+
// not re-trigger the route factories' unauthenticated-mount warning.
|
|
1453
|
+
allowUnauthenticated: params.inboundAuthAcknowledged ?? true,
|
|
1454
|
+
};
|
|
1444
1455
|
/**
|
|
1445
1456
|
* Is this request arriving on the gate-only share listener?
|
|
1446
1457
|
*
|
|
@@ -1582,7 +1593,7 @@ export async function createProxyStartApp(params) {
|
|
|
1582
1593
|
app.post("/internal/update-control", async (c) => {
|
|
1583
1594
|
const suppliedToken = c.req.header("x-neurolink-update-token");
|
|
1584
1595
|
const expectedToken = params.updateControlToken ?? PROXY_UPDATE_CONTROL_TOKEN;
|
|
1585
|
-
if (!suppliedToken || suppliedToken
|
|
1596
|
+
if (!suppliedToken || !secretsMatch(expectedToken, suppliedToken)) {
|
|
1586
1597
|
return c.json({ error: "not_found" }, 404);
|
|
1587
1598
|
}
|
|
1588
1599
|
const payload = await c.req
|
|
@@ -1665,12 +1676,11 @@ export async function createProxyStartApp(params) {
|
|
|
1665
1676
|
const runtimeConfigProvider = runtimeConfigStore
|
|
1666
1677
|
? () => runtimeConfigStore.getSnapshot()
|
|
1667
1678
|
: undefined;
|
|
1668
|
-
const routeGroup = createClaudeProxyRoutes(params.modelRouter, "", params.strategy, params.passthrough, params.primaryAccountKey,
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
: params.accountAllowlist);
|
|
1679
|
+
const routeGroup = createClaudeProxyRoutes(params.modelRouter, "", params.strategy, params.passthrough, params.primaryAccountKey, {
|
|
1680
|
+
accountAllowlist: params.accountAllowlist,
|
|
1681
|
+
...(runtimeConfigProvider ? { runtimeConfigProvider } : {}),
|
|
1682
|
+
inboundAuth,
|
|
1683
|
+
});
|
|
1674
1684
|
const openaiRouteGroup = createOpenAIProxyRoutes(params.modelRouter, "", params.port, runtimeConfigProvider,
|
|
1675
1685
|
// Keep bridge capabilities, accounting and cancellation in this worker
|
|
1676
1686
|
// across supervisor generation switches; never re-enter the public listener.
|
|
@@ -1679,7 +1689,7 @@ export async function createProxyStartApp(params) {
|
|
|
1679
1689
|
// Same in-process loopback discipline as the OpenAI->Anthropic bridge:
|
|
1680
1690
|
// stay in this worker across supervisor generation switches, never
|
|
1681
1691
|
// re-enter the public listener.
|
|
1682
|
-
(request) => app.fetch(request));
|
|
1692
|
+
(request) => app.fetch(request), inboundAuth);
|
|
1683
1693
|
const geminiRouteGroup = createGeminiProxyRoutes(params.modelRouter, "", params.port, runtimeConfigProvider);
|
|
1684
1694
|
const allProxyRoutes = [
|
|
1685
1695
|
...routeGroup.routes,
|
|
@@ -1708,6 +1718,27 @@ export async function createProxyStartApp(params) {
|
|
|
1708
1718
|
},
|
|
1709
1719
|
}, 503);
|
|
1710
1720
|
}
|
|
1721
|
+
// Inbound token, before the body is even read. A request carrying a
|
|
1722
|
+
// share token is left to the share gate below; everything else on a
|
|
1723
|
+
// non-exempt path must present the operator's token.
|
|
1724
|
+
if (inboundToken &&
|
|
1725
|
+
metadata?.accountingScope !== "internal" &&
|
|
1726
|
+
!isInboundAuthExemptPath(c.req.path)) {
|
|
1727
|
+
const verdict = authorizeInboundRequest(Object.fromEntries(c.req.raw.headers.entries()), inboundToken);
|
|
1728
|
+
if (!verdict.ok) {
|
|
1729
|
+
for (const [key, value] of Object.entries(verdict.response.headers)) {
|
|
1730
|
+
c.header(key, value);
|
|
1731
|
+
}
|
|
1732
|
+
if (metadata) {
|
|
1733
|
+
metadata.terminalErrorType = "inbound_auth_required";
|
|
1734
|
+
await recordRuntimeError(metadata, 401, "inbound_auth_required", "Request did not present the inbound token", {
|
|
1735
|
+
clientMessage: verdict.response.body.error.message,
|
|
1736
|
+
clientErrorType: verdict.response.body.error.type,
|
|
1737
|
+
});
|
|
1738
|
+
}
|
|
1739
|
+
return c.json(verdict.response.body, 401);
|
|
1740
|
+
}
|
|
1741
|
+
}
|
|
1711
1742
|
const emptyBody = {};
|
|
1712
1743
|
let body;
|
|
1713
1744
|
let rawBody;
|
|
@@ -1946,6 +1977,18 @@ export async function createProxyStartApp(params) {
|
|
|
1946
1977
|
}));
|
|
1947
1978
|
});
|
|
1948
1979
|
app.get("/status", async (c) => {
|
|
1980
|
+
// `/status` is an operator surface. With an inbound token configured it
|
|
1981
|
+
// is refused outright to anyone without it, not merely redacted.
|
|
1982
|
+
if (inboundToken &&
|
|
1983
|
+
!hasValidInboundToken(Object.fromEntries(c.req.raw.headers.entries()), inboundToken)) {
|
|
1984
|
+
return c.json({
|
|
1985
|
+
type: "error",
|
|
1986
|
+
error: {
|
|
1987
|
+
type: "authentication_error",
|
|
1988
|
+
message: "This proxy requires an inbound token.",
|
|
1989
|
+
},
|
|
1990
|
+
}, 401);
|
|
1991
|
+
}
|
|
1949
1992
|
// A gated proxy is, by definition, one that may be exposed. `/status`
|
|
1950
1993
|
// enumerates account labels — which for an OAuth account is an email — plus
|
|
1951
1994
|
// quota and cooldown state, and it is not behind the request gate, so
|
|
@@ -1957,8 +2000,7 @@ export async function createProxyStartApp(params) {
|
|
|
1957
2000
|
// token, and redacted for everyone else. Counters and health stay visible so
|
|
1958
2001
|
// liveness tooling keeps working.
|
|
1959
2002
|
const statusIdentityAllowed = !isGatedListener(c) ||
|
|
1960
|
-
c.req.header("x-neurolink-update-token")
|
|
1961
|
-
(params.updateControlToken ?? PROXY_UPDATE_CONTROL_TOKEN);
|
|
2003
|
+
secretsMatch(params.updateControlToken ?? PROXY_UPDATE_CONTROL_TOKEN, c.req.header("x-neurolink-update-token") ?? "");
|
|
1962
2004
|
const runtimeConfig = params.runtimeConfigStore?.getSnapshot();
|
|
1963
2005
|
const runtimeConfigStatus = params.runtimeConfigStore?.getStatus();
|
|
1964
2006
|
const activeStrategy = runtimeConfig
|
|
@@ -3104,6 +3146,7 @@ async function runLaunchdProxySupervisor(argv, spinner) {
|
|
|
3104
3146
|
}
|
|
3105
3147
|
const host = argv.host ?? "127.0.0.1";
|
|
3106
3148
|
const port = argv.port ?? 55669;
|
|
3149
|
+
enforceProxyBindPolicy(host, argv, spinner);
|
|
3107
3150
|
const workerArgs = process.argv.slice(2);
|
|
3108
3151
|
const supervisorStartedAt = new Date().toISOString();
|
|
3109
3152
|
configureProxyLifecycleLogger({
|
|
@@ -3278,7 +3321,36 @@ export function registerProxySupervisorReload(activeWorkerPid) {
|
|
|
3278
3321
|
process.on("SIGHUP", reload);
|
|
3279
3322
|
return () => process.off("SIGHUP", reload);
|
|
3280
3323
|
}
|
|
3324
|
+
/**
|
|
3325
|
+
* Decide whether this listener may bind where it was asked to, after the env
|
|
3326
|
+
* file has been loaded (the inbound token may live there). Refusal exits the
|
|
3327
|
+
* process: an open proxy on a routable address is not a configuration to
|
|
3328
|
+
* start and warn about.
|
|
3329
|
+
*/
|
|
3330
|
+
function enforceProxyBindPolicy(host, argv, spinner) {
|
|
3331
|
+
const inboundToken = resolveInboundToken(argv.inboundToken);
|
|
3332
|
+
const allowUnauthenticated = argv.allowUnauthenticated === true || isUnauthenticatedBindAllowedByEnv();
|
|
3333
|
+
const decision = evaluateProxyBindPolicy({
|
|
3334
|
+
host,
|
|
3335
|
+
inboundToken,
|
|
3336
|
+
requireGrant: isGrantRequiredByEnv(),
|
|
3337
|
+
allowUnauthenticated,
|
|
3338
|
+
});
|
|
3339
|
+
if (!decision.allowed) {
|
|
3340
|
+
if (spinner) {
|
|
3341
|
+
spinner.fail(chalk.red("Refusing to start an unauthenticated proxy"));
|
|
3342
|
+
}
|
|
3343
|
+
logger.error(chalk.red(decision.reason));
|
|
3344
|
+
process.exit(1);
|
|
3345
|
+
}
|
|
3346
|
+
if (decision.warning) {
|
|
3347
|
+
logger.always(chalk.yellow(`[proxy] WARNING: ${decision.warning}`));
|
|
3348
|
+
}
|
|
3349
|
+
return { inboundToken, acknowledged: true };
|
|
3350
|
+
}
|
|
3281
3351
|
async function startProxyCommandHandler(argv) {
|
|
3352
|
+
// Everything this process creates under ~/.neurolink is owner-only.
|
|
3353
|
+
tightenProcessUmask();
|
|
3282
3354
|
const spinner = argv.quiet ? null : ora("Starting Claude proxy...").start();
|
|
3283
3355
|
const isDev = argv.dev ?? false;
|
|
3284
3356
|
const socketWorker = isProxySocketWorkerProcess();
|
|
@@ -3376,6 +3448,7 @@ async function startProxyCommandHandler(argv) {
|
|
|
3376
3448
|
mainPort: port,
|
|
3377
3449
|
});
|
|
3378
3450
|
const host = argv.host ?? "127.0.0.1";
|
|
3451
|
+
const bindPolicy = enforceProxyBindPolicy(host, argv, spinner);
|
|
3379
3452
|
const { app, readiness } = await createProxyStartApp({
|
|
3380
3453
|
neurolink,
|
|
3381
3454
|
modelRouter,
|
|
@@ -3388,6 +3461,8 @@ async function startProxyCommandHandler(argv) {
|
|
|
3388
3461
|
accountAllowlist,
|
|
3389
3462
|
runtimeConfigStore,
|
|
3390
3463
|
sharePort,
|
|
3464
|
+
inboundToken: bindPolicy.inboundToken,
|
|
3465
|
+
inboundAuthAcknowledged: bindPolicy.acknowledged,
|
|
3391
3466
|
});
|
|
3392
3467
|
await initializeProxyOpenTelemetry();
|
|
3393
3468
|
if (spinner) {
|
|
@@ -3446,7 +3521,21 @@ export const proxyStartCommand = {
|
|
|
3446
3521
|
type: "string",
|
|
3447
3522
|
alias: "H",
|
|
3448
3523
|
default: "127.0.0.1",
|
|
3449
|
-
description: "Host to bind to"
|
|
3524
|
+
description: "Host to bind to. A non-loopback host needs --inbound-token, " +
|
|
3525
|
+
"NEUROLINK_PROXY_REQUIRE_GRANT=1 or --allow-unauthenticated",
|
|
3526
|
+
})
|
|
3527
|
+
.option("inbound-token", {
|
|
3528
|
+
type: "string",
|
|
3529
|
+
alias: "inboundToken",
|
|
3530
|
+
description: "Bearer every caller must present (x-neurolink-inbound-token or " +
|
|
3531
|
+
"Authorization: Bearer). Also NEUROLINK_PROXY_INBOUND_TOKEN",
|
|
3532
|
+
})
|
|
3533
|
+
.option("allow-unauthenticated", {
|
|
3534
|
+
type: "boolean",
|
|
3535
|
+
alias: "allowUnauthenticated",
|
|
3536
|
+
default: false,
|
|
3537
|
+
description: "Bind a non-loopback host with no inbound authentication (an " +
|
|
3538
|
+
"authenticating reverse proxy must sit in front)",
|
|
3450
3539
|
})
|
|
3451
3540
|
.option("strategy", {
|
|
3452
3541
|
type: "string",
|
|
@@ -5189,7 +5278,20 @@ export const proxyInstallCommand = {
|
|
|
5189
5278
|
})
|
|
5190
5279
|
.option("host", {
|
|
5191
5280
|
type: "string",
|
|
5192
|
-
description: "Proxy host"
|
|
5281
|
+
description: "Proxy host. A non-loopback host needs --inbound-token, " +
|
|
5282
|
+
"NEUROLINK_PROXY_REQUIRE_GRANT=1 or --allow-unauthenticated",
|
|
5283
|
+
})
|
|
5284
|
+
.option("inbound-token", {
|
|
5285
|
+
type: "string",
|
|
5286
|
+
alias: "inboundToken",
|
|
5287
|
+
description: "Bearer every caller must present; persisted into the service " +
|
|
5288
|
+
"environment as NEUROLINK_PROXY_INBOUND_TOKEN",
|
|
5289
|
+
})
|
|
5290
|
+
.option("allow-unauthenticated", {
|
|
5291
|
+
type: "boolean",
|
|
5292
|
+
alias: "allowUnauthenticated",
|
|
5293
|
+
default: false,
|
|
5294
|
+
description: "Install a non-loopback service with no inbound authentication",
|
|
5193
5295
|
})
|
|
5194
5296
|
.option("env-file", {
|
|
5195
5297
|
type: "string",
|
|
@@ -5265,9 +5367,22 @@ export const proxyInstallCommand = {
|
|
|
5265
5367
|
process.exit(1);
|
|
5266
5368
|
}
|
|
5267
5369
|
await loadProxyEnvFile({ explicitEnvFile: envFile });
|
|
5370
|
+
tightenProcessUmask();
|
|
5371
|
+
// The service inherits NEUROLINK_PROXY_* from this environment (see
|
|
5372
|
+
// isProxyServiceEnvironmentKey), so a flag given here is persisted into
|
|
5373
|
+
// the 0600 plist rather than echoed on the command line.
|
|
5374
|
+
const installArgs = argv;
|
|
5375
|
+
if (installArgs.inboundToken?.trim()) {
|
|
5376
|
+
process.env.NEUROLINK_PROXY_INBOUND_TOKEN =
|
|
5377
|
+
installArgs.inboundToken.trim();
|
|
5378
|
+
}
|
|
5379
|
+
if (installArgs.allowUnauthenticated === true) {
|
|
5380
|
+
process.env.NEUROLINK_PROXY_ALLOW_UNAUTHENTICATED = "1";
|
|
5381
|
+
}
|
|
5382
|
+
enforceProxyBindPolicy(host, installArgs, null);
|
|
5268
5383
|
const logsDir = join(homedir(), ".neurolink", "logs");
|
|
5269
5384
|
if (!isProxyOtelOnly() && !existsSync(logsDir)) {
|
|
5270
|
-
mkdirSync(logsDir, { recursive: true });
|
|
5385
|
+
mkdirSync(logsDir, { recursive: true, mode: 0o700 });
|
|
5271
5386
|
}
|
|
5272
5387
|
if (!existsSync(PLIST_DIR)) {
|
|
5273
5388
|
mkdirSync(PLIST_DIR, { recursive: true });
|
|
@@ -34,7 +34,16 @@ export declare class ServeCommandFactory {
|
|
|
34
34
|
*/
|
|
35
35
|
private static createAndStartServer;
|
|
36
36
|
/**
|
|
37
|
-
*
|
|
37
|
+
* The NeuroLink instance the server runs. Its registry holds no built-in
|
|
38
|
+
* tool unless `--enable-builtin-tools`: those tools read and write files
|
|
39
|
+
* under the server's working directory, and `/tools/execute` and the agent
|
|
40
|
+
* routes would hand them to every caller. The switch is the SDK's own
|
|
41
|
+
* (NEUROLINK_DISABLE_BUILTIN_TOOLS), set before the instance and its
|
|
42
|
+
* providers read it, so generate() in this process does not see them either.
|
|
43
|
+
*/
|
|
44
|
+
private static createServerNeuroLink;
|
|
45
|
+
/**
|
|
46
|
+
* Put every route except the health probes behind the API key(s) in
|
|
38
47
|
* NEUROLINK_SERVER_API_KEY. Must run before the routes are registered.
|
|
39
48
|
*/
|
|
40
49
|
private static applyApiKeyAuth;
|