gitnexus 1.6.10-rc.32 → 1.6.10-rc.34
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/README.md +16 -14
- package/dist/cli/help-i18n.js +1 -0
- package/dist/cli/i18n/en.d.ts +1 -0
- package/dist/cli/i18n/en.js +1 -0
- package/dist/cli/i18n/resources.d.ts +2 -0
- package/dist/cli/i18n/zh-CN.d.ts +1 -0
- package/dist/cli/i18n/zh-CN.js +1 -0
- package/dist/cli/index.js +1 -0
- package/dist/cli/wiki.d.ts +1 -0
- package/dist/cli/wiki.js +7 -1
- package/dist/core/wiki/llm-client.d.ts +8 -2
- package/dist/core/wiki/llm-client.js +41 -5
- package/dist/mcp/tools.d.ts +0 -8
- package/dist/mcp/tools.js +0 -3
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -249,6 +249,8 @@ gitnexus clean # Delete index for current repo
|
|
|
249
249
|
gitnexus clean --all --force # Delete all indexes
|
|
250
250
|
gitnexus wiki [path] # Generate LLM-powered docs from knowledge graph
|
|
251
251
|
gitnexus wiki --model <model> # Wiki with custom LLM model (default: minimax/minimax-m2.5)
|
|
252
|
+
gitnexus wiki --base-url http://llama-box.local:8080/v1 --allow-insecure-connection llama-box.local
|
|
253
|
+
# Allow an exact LAN/self-hosted HTTP LLM host; env: GITNEXUS_ALLOW_INSECURE_CONNECTION
|
|
252
254
|
gitnexus doctor # Show runtime platform capabilities and embedding configuration
|
|
253
255
|
|
|
254
256
|
# Direct graph queries — the same tools the MCP server exposes, no MCP daemon needed
|
|
@@ -461,14 +463,14 @@ GitNexus uses optional DuckDB extensions for BM25 and vector search. The `gitnex
|
|
|
461
463
|
|
|
462
464
|
Configure the behavior with these environment variables:
|
|
463
465
|
|
|
464
|
-
| Variable | Values
|
|
465
|
-
| -------------------------------------------- |
|
|
466
|
-
| `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never`
|
|
467
|
-
| `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer
|
|
468
|
-
| `GITNEXUS_FTS_STEMMER` | supported LadybugDB stemmer
|
|
469
|
-
| `GITNEXUS_FTS_CJK_SEGMENTATION` | `none`, `bigram`
|
|
470
|
-
| `GITNEXUS_COMMUNITY_ENGINE` | `graphology`, `icebug`, `auto` | `graphology` | Community-detection engine used during analyze. `graphology` uses the bundled default path. `icebug` and `auto` currently behave identically: both try the experimental Icebug CSR path and fall back to Graphology if the optional native module is unavailable or incompatible.
|
|
471
|
-
| `GITNEXUS_WAL_CHECKPOINT_THRESHOLD` | integer `>= -1`
|
|
466
|
+
| Variable | Values | Default | Effect |
|
|
467
|
+
| -------------------------------------------- | ------------------------------ | ------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
468
|
+
| `GITNEXUS_LBUG_EXTENSION_INSTALL` | `auto`, `load-only`, `never` | `auto` | `auto` runs one bounded install if LOAD fails — a plain `INSTALL`, escalating to `FORCE INSTALL` only when the LOAD error shows the present extension file is broken. `load-only` only uses already-installed extensions (recommended for offline / firewalled environments). `never` skips optional extensions entirely. |
|
|
469
|
+
| `GITNEXUS_LBUG_EXTENSION_INSTALL_TIMEOUT_MS` | positive integer | `15000` | Wall-clock budget for the out-of-process extension-install child before it is killed. |
|
|
470
|
+
| `GITNEXUS_FTS_STEMMER` | supported LadybugDB stemmer | `porter` | Stemmer used when rebuilding BM25/FTS indexes. Use `none` for CJK-heavy repositories, or a language stemmer such as `german`, `french`, or `spanish` when that better matches repository comments and identifiers. Re-run `gitnexus analyze --repair-fts` after changing it. |
|
|
471
|
+
| `GITNEXUS_FTS_CJK_SEGMENTATION` | `none`, `bigram` | `none` | `bigram` inserts overlapping character-bigram boundaries into Chinese/Japanese Han-ideograph spans in `content`/`description` before FTS indexing, so LadybugDB's space-only tokenizer can see sub-phrase word boundaries. Scoped to CJK Unified Ideographs only — Japanese Hiragana/Katakana and Korean Hangul are not currently segmented. Unlike `GITNEXUS_FTS_STEMMER`, this rewrites stored text — enabling it on an already-indexed repo requires a full `gitnexus analyze --force`; neither `--repair-fts` nor a plain incremental `analyze` applies it to previously-indexed files. Set the same value wherever `analyze` and search-serving processes (CLI query, MCP server, web server) run. |
|
|
472
|
+
| `GITNEXUS_COMMUNITY_ENGINE` | `graphology`, `icebug`, `auto` | `graphology` | Community-detection engine used during analyze. `graphology` uses the bundled default path. `icebug` and `auto` currently behave identically: both try the experimental Icebug CSR path and fall back to Graphology if the optional native module is unavailable or incompatible. |
|
|
473
|
+
| `GITNEXUS_WAL_CHECKPOINT_THRESHOLD` | integer `>= -1` | `67108864` (64 MiB) | LadybugDB WAL auto-checkpoint threshold during analyze (bytes). Auto-checkpoint remains enabled; `-1` keeps Ladybug's stock ~16 MiB. Larger thresholds reduce checkpoint frequency but increase the WAL size at rotation time — choose a smaller value on disk-constrained environments. |
|
|
472
474
|
|
|
473
475
|
```bash
|
|
474
476
|
# Offline/airgapped: never reach the network for extensions
|
|
@@ -533,13 +535,13 @@ For repositories with very large source files, `GITNEXUS_WORKER_SUB_BATCH_MAX_BY
|
|
|
533
535
|
|
|
534
536
|
Three env vars expose the pool's resilience layers (respawn budget, cumulative-timeout cap, circuit breaker). Defaults are tuned for typical repos; bump them when an analyze legitimately needs more retries, or lower them to fail-fast on a known-bad shape.
|
|
535
537
|
|
|
536
|
-
| Variable | Default | Effect
|
|
537
|
-
| ----------------------------------------------- | ----------------------- |
|
|
538
|
-
| `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3` | Max replacement spawns per slot before the slot is dropped from the active rotation.
|
|
539
|
-
| `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits.
|
|
540
|
-
| `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool.
|
|
538
|
+
| Variable | Default | Effect |
|
|
539
|
+
| ----------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
540
|
+
| `GITNEXUS_WORKER_MAX_RESPAWNS_PER_SLOT` | `3` | Max replacement spawns per slot before the slot is dropped from the active rotation. |
|
|
541
|
+
| `GITNEXUS_WORKER_MAX_CUMULATIVE_TIMEOUT_MS` | `5 × subBatchTimeoutMs` | Total retry wall-time budget per job before quarantining. Bounds exponentially-growing retry waits. |
|
|
542
|
+
| `GITNEXUS_WORKER_CONSECUTIVE_FAILURE_THRESHOLD` | `max(3, poolSize)` | Per-slot consecutive deaths before the pool's circuit breaker trips. After tripping, dispatches require a fresh pool. |
|
|
541
543
|
| `GITNEXUS_WORKER_SHUTDOWN_DRAIN_MS` | `30000` | Max wait at pool shutdown for a retired worker still inside native code — terminated at its next JS-safe point instead of mid-native-call, which would abort the process (`Napi::Error`, #2432). |
|
|
542
|
-
| `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000` | Per-file wall-clock budget for C++ capture extraction; on breach the file keeps partial captures with a warning (#2432). `0` expires immediately.
|
|
544
|
+
| `GITNEXUS_CPP_CAPTURE_BUDGET_MS` | `20000` | Per-file wall-clock budget for C++ capture extraction; on breach the file keeps partial captures with a warning (#2432). `0` expires immediately. |
|
|
543
545
|
|
|
544
546
|
### Graph cleanup tuning
|
|
545
547
|
|
package/dist/cli/help-i18n.js
CHANGED
|
@@ -92,6 +92,7 @@ const OPTION_DESCRIPTION_KEYS = {
|
|
|
92
92
|
'wiki|--concurrency <n>': 'help.option.wiki.concurrency',
|
|
93
93
|
'wiki|--timeout <seconds>': 'help.option.wiki.timeout',
|
|
94
94
|
'wiki|--retries <n>': 'help.option.wiki.retries',
|
|
95
|
+
'wiki|--allow-insecure-connection <host>': 'help.option.wiki.allowInsecureConnection',
|
|
95
96
|
'wiki|--gist': 'help.option.wiki.gist',
|
|
96
97
|
'wiki|-v, --verbose': 'help.option.verbose',
|
|
97
98
|
'wiki|--review': 'help.option.wiki.review',
|
package/dist/cli/i18n/en.d.ts
CHANGED
|
@@ -188,6 +188,7 @@ export declare const en: {
|
|
|
188
188
|
readonly 'help.option.wiki.concurrency': "Parallel LLM calls (default: 3)";
|
|
189
189
|
readonly 'help.option.wiki.timeout': "LLM request timeout in seconds (default: disabled)";
|
|
190
190
|
readonly 'help.option.wiki.retries': "Max LLM retry attempts per request (default: 3)";
|
|
191
|
+
readonly 'help.option.wiki.allowInsecureConnection': "Allow exact host(s) for http:// LLM base URLs (comma-separated; HTTPS is preferred)";
|
|
191
192
|
readonly 'help.option.wiki.gist': "Publish wiki as a public GitHub Gist after generation";
|
|
192
193
|
readonly 'help.option.wiki.review': "Stop after grouping to review module structure before generating pages";
|
|
193
194
|
readonly 'help.option.wiki.lang': "Output language for generated documentation (e.g. english, chinese, spanish, japanese)";
|
package/dist/cli/i18n/en.js
CHANGED
|
@@ -188,6 +188,7 @@ export const en = {
|
|
|
188
188
|
'help.option.wiki.concurrency': 'Parallel LLM calls (default: 3)',
|
|
189
189
|
'help.option.wiki.timeout': 'LLM request timeout in seconds (default: disabled)',
|
|
190
190
|
'help.option.wiki.retries': 'Max LLM retry attempts per request (default: 3)',
|
|
191
|
+
'help.option.wiki.allowInsecureConnection': 'Allow exact host(s) for http:// LLM base URLs (comma-separated; HTTPS is preferred)',
|
|
191
192
|
'help.option.wiki.gist': 'Publish wiki as a public GitHub Gist after generation',
|
|
192
193
|
'help.option.wiki.review': 'Stop after grouping to review module structure before generating pages',
|
|
193
194
|
'help.option.wiki.lang': 'Output language for generated documentation (e.g. english, chinese, spanish, japanese)',
|
|
@@ -189,6 +189,7 @@ export declare const cliResources: {
|
|
|
189
189
|
readonly 'help.option.wiki.concurrency': "Parallel LLM calls (default: 3)";
|
|
190
190
|
readonly 'help.option.wiki.timeout': "LLM request timeout in seconds (default: disabled)";
|
|
191
191
|
readonly 'help.option.wiki.retries': "Max LLM retry attempts per request (default: 3)";
|
|
192
|
+
readonly 'help.option.wiki.allowInsecureConnection': "Allow exact host(s) for http:// LLM base URLs (comma-separated; HTTPS is preferred)";
|
|
192
193
|
readonly 'help.option.wiki.gist': "Publish wiki as a public GitHub Gist after generation";
|
|
193
194
|
readonly 'help.option.wiki.review': "Stop after grouping to review module structure before generating pages";
|
|
194
195
|
readonly 'help.option.wiki.lang': "Output language for generated documentation (e.g. english, chinese, spanish, japanese)";
|
|
@@ -436,6 +437,7 @@ export declare const cliResources: {
|
|
|
436
437
|
'help.option.wiki.concurrency': string;
|
|
437
438
|
'help.option.wiki.timeout': string;
|
|
438
439
|
'help.option.wiki.retries': string;
|
|
440
|
+
'help.option.wiki.allowInsecureConnection': string;
|
|
439
441
|
'help.option.wiki.gist': string;
|
|
440
442
|
'help.option.wiki.review': string;
|
|
441
443
|
'help.option.wiki.lang': string;
|
package/dist/cli/i18n/zh-CN.d.ts
CHANGED
|
@@ -188,6 +188,7 @@ export declare const zhCN: {
|
|
|
188
188
|
'help.option.wiki.concurrency': string;
|
|
189
189
|
'help.option.wiki.timeout': string;
|
|
190
190
|
'help.option.wiki.retries': string;
|
|
191
|
+
'help.option.wiki.allowInsecureConnection': string;
|
|
191
192
|
'help.option.wiki.gist': string;
|
|
192
193
|
'help.option.wiki.review': string;
|
|
193
194
|
'help.option.wiki.lang': string;
|
package/dist/cli/i18n/zh-CN.js
CHANGED
|
@@ -188,6 +188,7 @@ export const zhCN = {
|
|
|
188
188
|
'help.option.wiki.concurrency': '并行 LLM 调用数(默认:3)',
|
|
189
189
|
'help.option.wiki.timeout': 'LLM 请求超时时间(秒,默认:禁用)',
|
|
190
190
|
'help.option.wiki.retries': '每个请求的最大 LLM 重试次数(默认:3)',
|
|
191
|
+
'help.option.wiki.allowInsecureConnection': '允许 http:// LLM base URL 使用的精确主机(逗号分隔;推荐使用 HTTPS)',
|
|
191
192
|
'help.option.wiki.gist': '生成后发布 Wiki 为公开 GitHub Gist',
|
|
192
193
|
'help.option.wiki.review': '分组后停止,以便在生成页面前审查模块结构',
|
|
193
194
|
'help.option.wiki.lang': '生成文档的输出语言(如 english、chinese、spanish、japanese)',
|
package/dist/cli/index.js
CHANGED
|
@@ -192,6 +192,7 @@ program
|
|
|
192
192
|
.option('--concurrency <n>', 'Parallel LLM calls (default: 3)', '3')
|
|
193
193
|
.option('--timeout <seconds>', 'LLM request timeout in seconds (default: disabled)')
|
|
194
194
|
.option('--retries <n>', 'Max LLM retry attempts per request (default: 3)')
|
|
195
|
+
.option('--allow-insecure-connection <host>', 'Allow exact host(s) for http:// LLM base URLs (comma-separated; HTTPS is preferred)')
|
|
195
196
|
.option('--gist', 'Publish wiki as a public GitHub Gist after generation')
|
|
196
197
|
.option('-v, --verbose', 'Enable verbose output (show LLM commands and responses)')
|
|
197
198
|
.option('--review', 'Stop after grouping to review module structure before generating pages')
|
package/dist/cli/wiki.d.ts
CHANGED
package/dist/cli/wiki.js
CHANGED
|
@@ -11,7 +11,7 @@ import cliProgress from 'cli-progress';
|
|
|
11
11
|
import { getGitRoot, isGitRepo } from '../storage/git.js';
|
|
12
12
|
import { getStoragePaths, loadMeta, loadCLIConfig, saveCLIConfig, } from '../storage/repo-manager.js';
|
|
13
13
|
import { WikiGenerator } from '../core/wiki/generator.js';
|
|
14
|
-
import { resolveLLMConfig } from '../core/wiki/llm-client.js';
|
|
14
|
+
import { parseLLMAllowedInsecureHttpHosts, resolveLLMConfig, } from '../core/wiki/llm-client.js';
|
|
15
15
|
import { detectCursorCLI } from '../core/wiki/cursor-client.js';
|
|
16
16
|
import { detectLocalCLI } from '../core/wiki/local-cli-client.js';
|
|
17
17
|
import { logger } from '../core/logger.js';
|
|
@@ -152,9 +152,14 @@ const wikiCommandImpl = async (inputPath, options) => {
|
|
|
152
152
|
}
|
|
153
153
|
let timeoutSeconds;
|
|
154
154
|
let retries;
|
|
155
|
+
let allowedInsecureHttpHosts;
|
|
155
156
|
try {
|
|
156
157
|
timeoutSeconds = parsePositiveIntegerOption(options?.timeout, '--timeout', 1000);
|
|
157
158
|
retries = parsePositiveIntegerOption(options?.retries, '--retries');
|
|
159
|
+
allowedInsecureHttpHosts =
|
|
160
|
+
options?.allowInsecureConnection === undefined
|
|
161
|
+
? undefined
|
|
162
|
+
: parseLLMAllowedInsecureHttpHosts(options.allowInsecureConnection);
|
|
158
163
|
}
|
|
159
164
|
catch (error) {
|
|
160
165
|
console.log(` Error: ${error.message}\n`);
|
|
@@ -210,6 +215,7 @@ const wikiCommandImpl = async (inputPath, options) => {
|
|
|
210
215
|
provider: options?.provider,
|
|
211
216
|
apiVersion: options?.apiVersion,
|
|
212
217
|
isReasoningModel: options?.reasoningModel,
|
|
218
|
+
allowedInsecureHttpHosts,
|
|
213
219
|
});
|
|
214
220
|
// Run interactive setup if no saved config and no CLI flags provided
|
|
215
221
|
// (even if env vars exist — let user explicitly choose their provider)
|
|
@@ -23,6 +23,8 @@ export interface LLMConfig {
|
|
|
23
23
|
requestTimeoutMs?: number;
|
|
24
24
|
/** Max fetch attempts before giving up (default: 3). */
|
|
25
25
|
maxAttempts?: number;
|
|
26
|
+
/** Exact hostnames allowed for explicit http:// LLM endpoints. */
|
|
27
|
+
allowedInsecureHttpHosts?: readonly string[];
|
|
26
28
|
}
|
|
27
29
|
export interface LLMResponse {
|
|
28
30
|
content: string;
|
|
@@ -40,6 +42,8 @@ export declare function resolveLLMConfig(overrides?: Partial<LLMConfig>): Promis
|
|
|
40
42
|
* Estimate token count from text (rough heuristic: ~4 chars per token).
|
|
41
43
|
*/
|
|
42
44
|
export declare function estimateTokens(text: string): number;
|
|
45
|
+
export declare const LLM_ALLOW_INSECURE_CONNECTION_ENV = "GITNEXUS_ALLOW_INSECURE_CONNECTION";
|
|
46
|
+
export declare function parseLLMAllowedInsecureHttpHosts(value: string | undefined): string[];
|
|
43
47
|
/**
|
|
44
48
|
* Validate that a base URL supplied for LLM API calls is a safe HTTP/HTTPS
|
|
45
49
|
* endpoint (CWE-918 / CodeQL js/http-to-file-access).
|
|
@@ -47,15 +51,17 @@ export declare function estimateTokens(text: string): number;
|
|
|
47
51
|
* Allowed:
|
|
48
52
|
* - https:// with any hostname (public LLM APIs, Azure, OpenRouter, …)
|
|
49
53
|
* - http:// restricted to localhost / 127.0.0.1 (local servers: Ollama, LiteLLM, …)
|
|
54
|
+
* - http:// to exact hosts explicitly allowlisted for LAN/self-hosted LLMs
|
|
50
55
|
*
|
|
51
56
|
* Rejected:
|
|
52
57
|
* - file://, data:, javascript:, and any other non-HTTP scheme
|
|
53
|
-
* - http:// aimed at non-loopback hosts
|
|
58
|
+
* - http:// aimed at non-loopback hosts unless explicitly allowlisted
|
|
59
|
+
* (avoids SSRF against internal networks by default)
|
|
54
60
|
*
|
|
55
61
|
* Throws with a descriptive message on validation failure so callers surface a
|
|
56
62
|
* clear error rather than an opaque network error.
|
|
57
63
|
*/
|
|
58
|
-
export declare function validateLLMBaseUrl(baseUrl: string): void;
|
|
64
|
+
export declare function validateLLMBaseUrl(baseUrl: string, allowedInsecureHttpHosts?: readonly string[]): void;
|
|
59
65
|
/**
|
|
60
66
|
* Returns true if the given base URL is an Azure OpenAI endpoint.
|
|
61
67
|
* Uses proper hostname matching to avoid spoofed URLs like
|
|
@@ -43,6 +43,8 @@ export async function resolveLLMConfig(overrides) {
|
|
|
43
43
|
provider: savedProvider ?? 'openai',
|
|
44
44
|
apiVersion: overrides?.apiVersion || process.env.GITNEXUS_AZURE_API_VERSION || savedConfig.apiVersion,
|
|
45
45
|
isReasoningModel: overrides?.isReasoningModel ?? savedConfig.isReasoningModel,
|
|
46
|
+
allowedInsecureHttpHosts: overrides?.allowedInsecureHttpHosts ??
|
|
47
|
+
parseLLMAllowedInsecureHttpHosts(process.env[LLM_ALLOW_INSECURE_CONNECTION_ENV]),
|
|
46
48
|
};
|
|
47
49
|
}
|
|
48
50
|
/**
|
|
@@ -64,6 +66,36 @@ function isTimeoutLikeError(err) {
|
|
|
64
66
|
return true;
|
|
65
67
|
return /time(d)?\s*out|timeout/i.test(err.message);
|
|
66
68
|
}
|
|
69
|
+
export const LLM_ALLOW_INSECURE_CONNECTION_ENV = 'GITNEXUS_ALLOW_INSECURE_CONNECTION';
|
|
70
|
+
function normalizeAllowedInsecureHttpHost(host) {
|
|
71
|
+
const trimmed = host.trim().toLowerCase();
|
|
72
|
+
const fail = () => {
|
|
73
|
+
throw new Error(`--allow-insecure-connection / ${LLM_ALLOW_INSECURE_CONNECTION_ENV} entries must be exact hostnames or IP addresses`);
|
|
74
|
+
};
|
|
75
|
+
if (!trimmed || /[/@?#]/.test(trimmed))
|
|
76
|
+
fail();
|
|
77
|
+
if (trimmed.startsWith('[')) {
|
|
78
|
+
if (!trimmed.endsWith(']'))
|
|
79
|
+
fail();
|
|
80
|
+
const normalized = trimmed.slice(1, -1);
|
|
81
|
+
if (!normalized || /[\[\]]/.test(normalized))
|
|
82
|
+
fail();
|
|
83
|
+
return normalized;
|
|
84
|
+
}
|
|
85
|
+
if (/[\[\]]/.test(trimmed))
|
|
86
|
+
fail();
|
|
87
|
+
if ((trimmed.match(/:/g)?.length ?? 0) === 1) {
|
|
88
|
+
// URL.hostname never includes the port, so accepting "host:port" would
|
|
89
|
+
// create a confusing no-op allowlist entry.
|
|
90
|
+
fail();
|
|
91
|
+
}
|
|
92
|
+
return trimmed;
|
|
93
|
+
}
|
|
94
|
+
export function parseLLMAllowedInsecureHttpHosts(value) {
|
|
95
|
+
if (value === undefined || value.trim() === '')
|
|
96
|
+
return [];
|
|
97
|
+
return [...new Set(value.split(',').map(normalizeAllowedInsecureHttpHost))];
|
|
98
|
+
}
|
|
67
99
|
/**
|
|
68
100
|
* Validate that a base URL supplied for LLM API calls is a safe HTTP/HTTPS
|
|
69
101
|
* endpoint (CWE-918 / CodeQL js/http-to-file-access).
|
|
@@ -71,15 +103,17 @@ function isTimeoutLikeError(err) {
|
|
|
71
103
|
* Allowed:
|
|
72
104
|
* - https:// with any hostname (public LLM APIs, Azure, OpenRouter, …)
|
|
73
105
|
* - http:// restricted to localhost / 127.0.0.1 (local servers: Ollama, LiteLLM, …)
|
|
106
|
+
* - http:// to exact hosts explicitly allowlisted for LAN/self-hosted LLMs
|
|
74
107
|
*
|
|
75
108
|
* Rejected:
|
|
76
109
|
* - file://, data:, javascript:, and any other non-HTTP scheme
|
|
77
|
-
* - http:// aimed at non-loopback hosts
|
|
110
|
+
* - http:// aimed at non-loopback hosts unless explicitly allowlisted
|
|
111
|
+
* (avoids SSRF against internal networks by default)
|
|
78
112
|
*
|
|
79
113
|
* Throws with a descriptive message on validation failure so callers surface a
|
|
80
114
|
* clear error rather than an opaque network error.
|
|
81
115
|
*/
|
|
82
|
-
export function validateLLMBaseUrl(baseUrl) {
|
|
116
|
+
export function validateLLMBaseUrl(baseUrl, allowedInsecureHttpHosts = parseLLMAllowedInsecureHttpHosts(process.env[LLM_ALLOW_INSECURE_CONNECTION_ENV])) {
|
|
83
117
|
let parsed;
|
|
84
118
|
try {
|
|
85
119
|
parsed = new URL(baseUrl);
|
|
@@ -96,9 +130,11 @@ export function validateLLMBaseUrl(baseUrl) {
|
|
|
96
130
|
// Node's URL parser preserves IPv6 brackets in hostname (e.g. "[::1]"),
|
|
97
131
|
// so strip them before comparing to bare address literals.
|
|
98
132
|
const host = parsed.hostname.toLowerCase().replace(/^\[|\]$/g, '');
|
|
99
|
-
|
|
133
|
+
const allowedHosts = new Set(allowedInsecureHttpHosts.map(normalizeAllowedInsecureHttpHost));
|
|
134
|
+
if (host !== 'localhost' && host !== '127.0.0.1' && host !== '::1' && !allowedHosts.has(host)) {
|
|
100
135
|
// Use parsed.origin (scheme+host+port, no credentials) instead of the full URL.
|
|
101
|
-
throw new Error(`Insecure http:// LLM base URLs are only allowed for localhost/127.0.0.1
|
|
136
|
+
throw new Error(`Insecure http:// LLM base URLs are only allowed for localhost/127.0.0.1 ` +
|
|
137
|
+
`or hosts listed by --allow-insecure-connection / ${LLM_ALLOW_INSECURE_CONNECTION_ENV}. ` +
|
|
102
138
|
`Use https:// for remote endpoints (got ${parsed.origin})`);
|
|
103
139
|
}
|
|
104
140
|
}
|
|
@@ -145,7 +181,7 @@ export function buildRequestUrl(baseUrl, apiVersion) {
|
|
|
145
181
|
*/
|
|
146
182
|
export async function callLLM(prompt, config, systemPrompt, options) {
|
|
147
183
|
// Validate base URL before any fetch (CodeQL js/http-to-file-access)
|
|
148
|
-
validateLLMBaseUrl(config.baseUrl);
|
|
184
|
+
validateLLMBaseUrl(config.baseUrl, config.allowedInsecureHttpHosts);
|
|
149
185
|
const messages = [];
|
|
150
186
|
if (systemPrompt) {
|
|
151
187
|
messages.push({ role: 'system', content: systemPrompt });
|
package/dist/mcp/tools.d.ts
CHANGED
|
@@ -24,14 +24,6 @@ export interface ToolDefinition {
|
|
|
24
24
|
minLength?: number;
|
|
25
25
|
}>;
|
|
26
26
|
required: string[];
|
|
27
|
-
/**
|
|
28
|
-
* JSON-Schema `anyOf` for cross-property constraints `required` cannot express
|
|
29
|
-
* — e.g. "at least one of route/file". Forwarded verbatim to clients by the
|
|
30
|
-
* server's ListTools handler, so MCP clients see the constraint.
|
|
31
|
-
*/
|
|
32
|
-
anyOf?: Array<{
|
|
33
|
-
required: string[];
|
|
34
|
-
}>;
|
|
35
27
|
};
|
|
36
28
|
}
|
|
37
29
|
/**
|
package/dist/mcp/tools.js
CHANGED
|
@@ -726,9 +726,6 @@ Response shape is keyed on how many routes match, not on the data: exactly one m
|
|
|
726
726
|
repo: { type: 'string', description: 'Repository name or path.' },
|
|
727
727
|
},
|
|
728
728
|
required: [],
|
|
729
|
-
// Exactly one lookup key is needed, but either works (route wins if both
|
|
730
|
-
// are passed) — so the structural constraint is "at least one of route/file".
|
|
731
|
-
anyOf: [{ required: ['route'] }, { required: ['file'] }],
|
|
732
729
|
},
|
|
733
730
|
},
|
|
734
731
|
{
|
package/package.json
CHANGED