lurqrun 0.1.6 → 0.1.8
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 +311 -139
- package/dist/auditHandler-VWCXSDVO.js +24 -0
- package/dist/autopilotInit-Q62MX5M6.js +178 -0
- package/dist/bin/lurq.js +3255 -23319
- package/dist/browserAuth-J7APSD4P.js +96 -0
- package/dist/cache-AEAKCV4I.js +12 -0
- package/dist/check-2JTQDQA6.js +2613 -0
- package/dist/check-PQSQ2MMA.js +192 -0
- package/dist/checkUpgrade-5YW77OBL.js +24 -0
- package/dist/chunk-2GAYNIHG.js +40 -0
- package/dist/chunk-2O6Z37IT.js +423 -0
- package/dist/chunk-2WD4QAH4.js +116 -0
- package/dist/chunk-3676XOBU.js +2235 -0
- package/dist/chunk-3D2MPGOQ.js +181 -0
- package/dist/chunk-3GHJR7XR.js +2049 -0
- package/dist/chunk-4ESO33J6.js +34 -0
- package/dist/chunk-4JHDSVIN.js +1229 -0
- package/dist/chunk-4KCPOMG6.js +68 -0
- package/dist/chunk-53SG6XWA.js +141 -0
- package/dist/chunk-5ABNDB3I.js +37 -0
- package/dist/chunk-5ALIJFDA.js +114 -0
- package/dist/chunk-63MUJNZL.js +318 -0
- package/dist/chunk-64ZF7KO4.js +1571 -0
- package/dist/chunk-6DM6UQFD.js +318 -0
- package/dist/chunk-6EQ3ARXA.js +304 -0
- package/dist/chunk-6L67QMCP.js +92 -0
- package/dist/chunk-72Y2FDVS.js +172 -0
- package/dist/chunk-A3TYI2TF.js +288 -0
- package/dist/chunk-B2UUYRBX.js +536 -0
- package/dist/chunk-BJT2LHAR.js +320 -0
- package/dist/chunk-BM3LF5T6.js +132 -0
- package/dist/chunk-BN3NM47Q.js +214 -0
- package/dist/chunk-BUALU27S.js +123 -0
- package/dist/chunk-C6GLE626.js +160 -0
- package/dist/chunk-D3K6IZVR.js +12845 -0
- package/dist/chunk-D3VTZS7V.js +222 -0
- package/dist/chunk-DM37MTOO.js +636 -0
- package/dist/chunk-DONTW3NE.js +171 -0
- package/dist/chunk-DSROTZ3Z.js +38 -0
- package/dist/chunk-ELGCAM7P.js +184 -0
- package/dist/chunk-EQCXENKZ.js +130 -0
- package/dist/chunk-EYAKM6YN.js +96 -0
- package/dist/chunk-FDFPMBCA.js +112 -0
- package/dist/chunk-FVYFV5HA.js +154 -0
- package/dist/chunk-GE37NVLN.js +15 -0
- package/dist/chunk-GM5Y555O.js +143 -0
- package/dist/chunk-GRJXQBQZ.js +793 -0
- package/dist/chunk-HWFX4BC4.js +177 -0
- package/dist/chunk-I3M2KJXB.js +56 -0
- package/dist/chunk-IFNONGRQ.js +603 -0
- package/dist/chunk-J3U2CPSK.js +136 -0
- package/dist/chunk-J6XQ7OPZ.js +74 -0
- package/dist/chunk-KMV4Y6R3.js +127 -0
- package/dist/chunk-KP5ZDDPE.js +340 -0
- package/dist/chunk-KYKSZ3XZ.js +202 -0
- package/dist/chunk-LEGPB7DO.js +81 -0
- package/dist/chunk-LUAKDHLZ.js +40 -0
- package/dist/chunk-MHUEVBVO.js +35 -0
- package/dist/chunk-MYGSLK44.js +37 -0
- package/dist/chunk-NEZNAPPN.js +41 -0
- package/dist/chunk-NPVN2THM.js +197 -0
- package/dist/chunk-NYZU5NCW.js +23 -0
- package/dist/chunk-O4KHCV6I.js +320 -0
- package/dist/chunk-ONDVTXUO.js +240 -0
- package/dist/chunk-OTJFHESY.js +176 -0
- package/dist/chunk-P6MIKSPY.js +243 -0
- package/dist/chunk-Q5VPF4WI.js +26 -0
- package/dist/chunk-QCRBX3LV.js +165 -0
- package/dist/chunk-QKGYDMWB.js +7409 -0
- package/dist/chunk-QSL6QN5X.js +115 -0
- package/dist/chunk-QUIMQ7YL.js +73 -0
- package/dist/chunk-RG3YLU6E.js +49 -0
- package/dist/chunk-SHCEBP3J.js +107 -0
- package/dist/chunk-SHZFOSXC.js +462 -0
- package/dist/chunk-SOXFMMXC.js +151 -0
- package/dist/chunk-TDTYV6QI.js +119 -0
- package/dist/chunk-TNTXWSYA.js +19 -0
- package/dist/chunk-TUXQ32WE.js +2251 -0
- package/dist/chunk-VGCDRK3S.js +37 -0
- package/dist/chunk-VLRHFMGJ.js +58 -0
- package/dist/chunk-WHX2ZFCG.js +4048 -0
- package/dist/chunk-X4W64MHU.js +2005 -0
- package/dist/chunk-XBQA4I36.js +92 -0
- package/dist/chunk-XGOV6KV4.js +413 -0
- package/dist/chunk-XIFQIFSZ.js +940 -0
- package/dist/chunk-XLXPVN3Y.js +15393 -0
- package/dist/chunk-ZJMLQ37G.js +90 -0
- package/dist/client-MU4EUDZV.js +15 -0
- package/dist/commands-UOOTMBKA.js +1023 -0
- package/dist/compat-Q5DVPITN.js +201 -0
- package/dist/constants-DJFOFZ44.js +24 -0
- package/dist/dashboardIssue-KBKLQMGC.js +274 -0
- package/dist/diff-I4SNUHU3.js +13 -0
- package/dist/dist-4XXIXHHZ.js +12945 -0
- package/dist/domains-VRT4BRVZ.js +14 -0
- package/dist/e2b-FVPHDULZ.js +172 -0
- package/dist/env-UN6KGZ7S.js +15 -0
- package/dist/envCheck-KUPTORFQ.js +71 -0
- package/dist/extract-6WG265BL.js +455 -0
- package/dist/fetch-PJ3SC32M.js +22 -0
- package/dist/firstTouch-COJCMOB3.js +55 -0
- package/dist/fix-3EDWNKW3.js +220 -0
- package/dist/handlers-4Q37WIFT.js +55 -0
- package/dist/hook-NA66XJLC.js +412 -0
- package/dist/http-25GJ6SKK.js +6928 -0
- package/dist/ingestQueue-FSVJC3AA.js +37 -0
- package/dist/install-G2HHXYJ2.js +20 -0
- package/dist/installSkill-5TIMYIJZ.js +70 -0
- package/dist/inventory-5AWSLJYU.js +31 -0
- package/dist/local-XPZMRH5R.js +13 -0
- package/dist/manifest-ENMIC54B.js +13 -0
- package/dist/mcp-PENLL2PK.js +500 -0
- package/dist/mcpConfig-3LYPRY5Q.js +80 -0
- package/dist/mcpHandlers-PO7GENA7.js +22 -0
- package/dist/mcpScan-ULP626TQ.js +1735 -0
- package/dist/mcpScanWorkflow-27O44FOE.js +98 -0
- package/dist/mcpStack-ZNYIFXZ5.js +20 -0
- package/dist/model-ASMZWLM7.js +214 -0
- package/dist/packages-RWF2LXST.js +40 -0
- package/dist/plan-SDILIDZT.js +465 -0
- package/dist/planView-ZAKRTGVS.js +82 -0
- package/dist/policy-JAGNMQJI.js +115 -0
- package/dist/probe-3IK646WN.js +610 -0
- package/dist/publicUpgrades-WTBGLDIK.js +42 -0
- package/dist/references-I6Y4BSRR.js +16 -0
- package/dist/release-7IU2R4YG.js +128 -0
- package/dist/remote-FN6225AH.js +42 -0
- package/dist/rename-OS6JYBZQ.js +204 -0
- package/dist/reportRuns-O4X7ZFTX.js +77 -0
- package/dist/safeFetch-COERXGFN.js +19 -0
- package/dist/sandbox-SAPSRKEI.js +39 -0
- package/dist/sarif-WLVJFJ7U.js +125 -0
- package/dist/server-OLTY3NYT.js +59 -0
- package/dist/surface-7MHE3RL7.js +143 -0
- package/dist/surface-JS2XU3U3.js +38 -0
- package/dist/surfaceHandlers-4B6TMZ34.js +20 -0
- package/dist/typecheck-NGOKNAJY.js +282 -0
- package/dist/types-M5JP2W3D.js +10 -0
- package/dist/uninstall-I2IJP5JZ.js +200 -0
- package/dist/upgrade-FS26DJGG.js +650 -0
- package/dist/upgradePlan-ZS5IOVMZ.js +181 -0
- package/dist/upkeepHandler-VFGYXJKB.js +13 -0
- package/dist/userConfig-ICASHI47.js +20 -0
- package/dist/watch-OCNEUXG6.js +204 -0
- package/package.json +26 -20
- package/templates/agent-rules.md +4 -4
- package/templates/skill-instructions.md +35 -17
- package/dist/bin/lurq.js.map +0 -1
- package/dist/index.d.ts +0 -805
- package/dist/index.js +0 -23570
- package/dist/index.js.map +0 -1
- package/drizzle/0000_confused_fabian_cortez.sql +0 -54
- package/drizzle/0001_aspiring_timeslip.sql +0 -15
- package/drizzle/0002_lyrical_lila_cheney.sql +0 -14
- package/drizzle/0003_violet_giant_girl.sql +0 -14
- package/drizzle/0004_slim_valeria_richards.sql +0 -15
- package/drizzle/0005_petite_luke_cage.sql +0 -12
- package/drizzle/0006_calm_nighthawk.sql +0 -3
- package/drizzle/0007_slimy_naoko.sql +0 -1
- package/drizzle/0008_peaceful_tigra.sql +0 -1
- package/drizzle/0009_smooth_harrier.sql +0 -10
- package/drizzle/0010_curved_amphibian.sql +0 -2
- package/drizzle/0011_compat_provenance_closures.sql +0 -11
- package/drizzle/0012_api_surfaces.sql +0 -9
- package/drizzle/0013_nice_thanos.sql +0 -1
- package/drizzle/0014_luxuriant_malcolm_colcord.sql +0 -10
- package/drizzle/0015_past_fat_cobra.sql +0 -11
- package/drizzle/0016_futuristic_junta.sql +0 -50
- package/drizzle/0017_absent_grandmaster.sql +0 -20
- package/drizzle/0018_rainy_proemial_gods.sql +0 -1
- package/drizzle/0019_dashing_korvac.sql +0 -11
- package/drizzle/0020_normal_vulture.sql +0 -3
- package/drizzle/0021_dazzling_blue_shield.sql +0 -17
- package/drizzle/0022_massive_ghost_rider.sql +0 -24
- package/drizzle/0023_next_shatterstar.sql +0 -1
- package/drizzle/0024_dusty_maelstrom.sql +0 -15
- package/drizzle/0025_gifted_harpoon.sql +0 -5
- package/drizzle/0026_awesome_mordo.sql +0 -1
- package/drizzle/0027_charming_ulik.sql +0 -2
- package/drizzle/0028_romantic_loners.sql +0 -2
- package/drizzle/0029_busy_korvac.sql +0 -16
- package/drizzle/0030_steady_mathemanic.sql +0 -6
- package/drizzle/0031_redundant_maginty.sql +0 -2
- package/drizzle/0032_stack_resolutions.sql +0 -14
- package/drizzle/0033_graceful_tyger_tiger.sql +0 -3
- package/drizzle/0034_uneven_thunderbird.sql +0 -1
- package/drizzle/0035_api_key_scopes.sql +0 -1
- package/drizzle/0036_policy_audit.sql +0 -21
- package/drizzle/0037_subscription_seats.sql +0 -1
- package/drizzle/0038_symbol_offsets.sql +0 -2
- package/drizzle/0039_mcp_runtime_scans.sql +0 -80
- package/drizzle/0040_subscription_overage_and_interval.sql +0 -4
- package/drizzle/0041_account_email.sql +0 -38
- package/drizzle/0042_alert_channels.sql +0 -21
- package/drizzle/meta/0000_snapshot.json +0 -370
- package/drizzle/meta/0001_snapshot.json +0 -482
- package/drizzle/meta/0002_snapshot.json +0 -575
- package/drizzle/meta/0003_snapshot.json +0 -667
- package/drizzle/meta/0004_snapshot.json +0 -769
- package/drizzle/meta/0005_snapshot.json +0 -864
- package/drizzle/meta/0006_snapshot.json +0 -882
- package/drizzle/meta/0007_snapshot.json +0 -876
- package/drizzle/meta/0008_snapshot.json +0 -882
- package/drizzle/meta/0009_snapshot.json +0 -948
- package/drizzle/meta/0010_snapshot.json +0 -969
- package/drizzle/meta/0011_snapshot.json +0 -1049
- package/drizzle/meta/0012_snapshot.json +0 -1115
- package/drizzle/meta/0013_snapshot.json +0 -1121
- package/drizzle/meta/0014_snapshot.json +0 -1190
- package/drizzle/meta/0015_snapshot.json +0 -1280
- package/drizzle/meta/0016_snapshot.json +0 -1683
- package/drizzle/meta/0017_snapshot.json +0 -1834
- package/drizzle/meta/0018_snapshot.json +0 -1834
- package/drizzle/meta/0019_snapshot.json +0 -1909
- package/drizzle/meta/0020_snapshot.json +0 -1921
- package/drizzle/meta/0021_snapshot.json +0 -2045
- package/drizzle/meta/0022_snapshot.json +0 -2246
- package/drizzle/meta/0023_snapshot.json +0 -2252
- package/drizzle/meta/0024_snapshot.json +0 -2375
- package/drizzle/meta/0025_snapshot.json +0 -2407
- package/drizzle/meta/0026_snapshot.json +0 -2413
- package/drizzle/meta/0027_snapshot.json +0 -2425
- package/drizzle/meta/0028_snapshot.json +0 -2438
- package/drizzle/meta/0029_snapshot.json +0 -2546
- package/drizzle/meta/0030_snapshot.json +0 -2586
- package/drizzle/meta/0031_snapshot.json +0 -2598
- package/drizzle/meta/0032_snapshot.json +0 -2699
- package/drizzle/meta/0033_snapshot.json +0 -2712
- package/drizzle/meta/0034_snapshot.json +0 -2719
- package/drizzle/meta/0035_snapshot.json +0 -2726
- package/drizzle/meta/0036_snapshot.json +0 -2876
- package/drizzle/meta/0037_snapshot.json +0 -2883
- package/drizzle/meta/0038_snapshot.json +0 -2895
- package/drizzle/meta/0039_snapshot.json +0 -3501
- package/drizzle/meta/0040_snapshot.json +0 -3527
- package/drizzle/meta/0041_snapshot.json +0 -3824
- package/drizzle/meta/0042_snapshot.json +0 -3960
- package/drizzle/meta/_journal.json +0 -307
- package/src/data/seed.json +0 -253
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
init_esm_shims
|
|
4
|
+
} from "./chunk-I3M2KJXB.js";
|
|
5
|
+
|
|
6
|
+
// src/fix/domains.ts
|
|
7
|
+
init_esm_shims();
|
|
8
|
+
var DOMAIN_SCAN_LIMIT = 2e4;
|
|
9
|
+
var DOMAINS = {
|
|
10
|
+
/** Variables the code reads that no .env file declares. No key, no network. */
|
|
11
|
+
env: async (root, opts) => {
|
|
12
|
+
const { envFindings } = await import("./env-UN6KGZ7S.js");
|
|
13
|
+
const plan = envFindings(root, { limit: opts.limit ?? DOMAIN_SCAN_LIMIT });
|
|
14
|
+
return { findings: plan.findings, truncated: plan.truncated };
|
|
15
|
+
},
|
|
16
|
+
/**
|
|
17
|
+
* Model identifiers the provider has retired or put an end date on.
|
|
18
|
+
*
|
|
19
|
+
* Reads source only — no network, no key, no arguments. That is why it runs
|
|
20
|
+
* by default: the check costs nothing the scan is not already paying, and
|
|
21
|
+
* the failure it catches is one nothing else in the toolchain can see.
|
|
22
|
+
*/
|
|
23
|
+
model: async (root, opts) => {
|
|
24
|
+
const { modelFindings } = await import("./model-ASMZWLM7.js");
|
|
25
|
+
const plan = modelFindings(root, { limit: opts.limit ?? DOMAIN_SCAN_LIMIT });
|
|
26
|
+
return { findings: plan.findings, truncated: plan.truncated };
|
|
27
|
+
},
|
|
28
|
+
/**
|
|
29
|
+
* Renames the package itself proves, the manifest ranges an upgrade leaves
|
|
30
|
+
* stale, and a brief for everything that needs judgement.
|
|
31
|
+
*
|
|
32
|
+
* Reads the two published versions from npm, so it needs the network but no
|
|
33
|
+
* API key — the same contract `check-upgrade` advertises.
|
|
34
|
+
*/
|
|
35
|
+
package: async (root, opts) => {
|
|
36
|
+
const targets = opts.upgrade ?? [];
|
|
37
|
+
if (targets.length === 0) {
|
|
38
|
+
return {
|
|
39
|
+
findings: [],
|
|
40
|
+
skipped: "no upgrades given. Pass `upgrade` with the versions to assess, or run `lurq fix` in the project to have them worked out from the index."
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
const { scanReferences } = await import("./references-I6Y4BSRR.js");
|
|
44
|
+
const { checkUpgrade } = await import("./upgrade-FS26DJGG.js");
|
|
45
|
+
const { renamePlan } = await import("./rename-OS6JYBZQ.js");
|
|
46
|
+
const { manifestFindings } = await import("./manifest-ENMIC54B.js");
|
|
47
|
+
const stats = { files: 0, truncated: false };
|
|
48
|
+
const refs = scanReferences(root, { limit: opts.limit ?? DOMAIN_SCAN_LIMIT, stats });
|
|
49
|
+
const report = await checkUpgrade(targets, refs, { rootDir: root });
|
|
50
|
+
return {
|
|
51
|
+
findings: [...renamePlan(report).findings, ...manifestFindings(root, targets).findings],
|
|
52
|
+
truncated: stats.truncated
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
var AVAILABLE = ["env", "model", "package"];
|
|
57
|
+
var SOURCE_DOMAINS = AVAILABLE.filter((d) => d !== "package");
|
|
58
|
+
async function runDomains(opts = {}) {
|
|
59
|
+
const root = opts.dir?.trim() || process.cwd();
|
|
60
|
+
const wanted = opts.domains?.length ? opts.domains : AVAILABLE;
|
|
61
|
+
const findings = [];
|
|
62
|
+
const ran = [];
|
|
63
|
+
const skipped = [];
|
|
64
|
+
let truncated = false;
|
|
65
|
+
for (const domain of wanted) {
|
|
66
|
+
const run = DOMAINS[domain];
|
|
67
|
+
if (!run) {
|
|
68
|
+
skipped.push({ domain, reason: `lurq has no detector for the ${domain} domain yet` });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
try {
|
|
72
|
+
const result = await run(root, opts);
|
|
73
|
+
if (result.skipped) {
|
|
74
|
+
skipped.push({ domain, reason: result.skipped });
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
findings.push(...result.findings);
|
|
78
|
+
if (result.truncated) truncated = true;
|
|
79
|
+
ran.push(domain);
|
|
80
|
+
} catch (err) {
|
|
81
|
+
skipped.push({ domain, reason: err instanceof Error ? err.message : String(err) });
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { root, ran, skipped, findings, truncated };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export {
|
|
88
|
+
DOMAIN_SCAN_LIMIT,
|
|
89
|
+
AVAILABLE,
|
|
90
|
+
SOURCE_DOMAINS,
|
|
91
|
+
runDomains
|
|
92
|
+
};
|
|
@@ -0,0 +1,413 @@
|
|
|
1
|
+
import { createRequire as __lurqCreateRequire } from 'node:module'; const require = __lurqCreateRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
classifyServer,
|
|
4
|
+
mcpConfigPaths
|
|
5
|
+
} from "./chunk-63MUJNZL.js";
|
|
6
|
+
import {
|
|
7
|
+
init_esm_shims
|
|
8
|
+
} from "./chunk-I3M2KJXB.js";
|
|
9
|
+
|
|
10
|
+
// src/mcpScan/redact.ts
|
|
11
|
+
init_esm_shims();
|
|
12
|
+
var SHAPES = [
|
|
13
|
+
/\bsk-(?:ant-|proj-)?[A-Za-z0-9_-]{16,}/g,
|
|
14
|
+
/\bgh[pousr]_[A-Za-z0-9]{20,}/g,
|
|
15
|
+
/\bgithub_pat_[A-Za-z0-9_]{20,}/g,
|
|
16
|
+
/\bxox[abpors]-[A-Za-z0-9-]{10,}/g,
|
|
17
|
+
/\bAKIA[0-9A-Z]{16}\b/g,
|
|
18
|
+
/\beyJ[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}\.[A-Za-z0-9_-]{10,}/g,
|
|
19
|
+
/\blurq_(?:live|test)_[A-Za-z0-9_-]{16,}/g,
|
|
20
|
+
/\b(?:sk|rk|pk)_(?:live|test)_[A-Za-z0-9]{16,}/g
|
|
21
|
+
];
|
|
22
|
+
var BEARER = /\b(Bearer|Basic|token)\s+[A-Za-z0-9._~+/=-]{12,}/gi;
|
|
23
|
+
var REDACTED = "[redacted]";
|
|
24
|
+
function looksLikeSecret(value) {
|
|
25
|
+
return SHAPES.some((re) => {
|
|
26
|
+
re.lastIndex = 0;
|
|
27
|
+
const m = re.exec(value);
|
|
28
|
+
return m !== null && m.index === 0 && m[0].length === value.length;
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
function scrub(text, secrets = []) {
|
|
32
|
+
let out = text;
|
|
33
|
+
for (const s of secrets) if (s && out.includes(s)) out = out.split(s).join(REDACTED);
|
|
34
|
+
for (const re of SHAPES) out = out.replace(re, REDACTED);
|
|
35
|
+
return out.replace(BEARER, `$1 ${REDACTED}`);
|
|
36
|
+
}
|
|
37
|
+
function scrubDeep(value, secrets = []) {
|
|
38
|
+
if (typeof value === "string") return scrub(value, secrets);
|
|
39
|
+
if (Array.isArray(value)) return value.map((v) => scrubDeep(v, secrets));
|
|
40
|
+
if (value && typeof value === "object") {
|
|
41
|
+
const out = {};
|
|
42
|
+
for (const [k, v] of Object.entries(value)) {
|
|
43
|
+
out[scrub(k, secrets)] = scrubDeep(v, secrets);
|
|
44
|
+
}
|
|
45
|
+
return out;
|
|
46
|
+
}
|
|
47
|
+
return value;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// src/mcpScan/config.ts
|
|
51
|
+
init_esm_shims();
|
|
52
|
+
import { createHash } from "crypto";
|
|
53
|
+
import { existsSync, readFileSync, realpathSync } from "fs";
|
|
54
|
+
import { homedir } from "os";
|
|
55
|
+
import { basename, isAbsolute, join, relative, resolve } from "path";
|
|
56
|
+
var MIN_SECRET_LENGTH = 6;
|
|
57
|
+
var SECRET_FLAG = /^--?(?:api[-_]?key|token|access[-_]?token|auth|password|secret|pat|key)$/i;
|
|
58
|
+
var SECRET_NAME = /(key|token|secret|password|passwd|auth|cookie|credential|session|pat)\b/i;
|
|
59
|
+
var strings = (v) => Array.isArray(v) ? v.filter((a) => typeof a === "string") : [];
|
|
60
|
+
var record = (v) => {
|
|
61
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return {};
|
|
62
|
+
const out = {};
|
|
63
|
+
for (const [k, val] of Object.entries(v)) {
|
|
64
|
+
if (typeof val === "string") out[k] = val;
|
|
65
|
+
else if (typeof val === "number" || typeof val === "boolean") out[k] = String(val);
|
|
66
|
+
}
|
|
67
|
+
return out;
|
|
68
|
+
};
|
|
69
|
+
function expandVars(value, env, missing) {
|
|
70
|
+
return value.replace(/\$\{([^}]+)\}/g, (whole, body) => {
|
|
71
|
+
if (body.startsWith("input:")) {
|
|
72
|
+
missing.add(body);
|
|
73
|
+
return whole;
|
|
74
|
+
}
|
|
75
|
+
const expr = body.startsWith("env:") ? body.slice(4) : body;
|
|
76
|
+
const [name, fallback] = expr.split(":-", 2);
|
|
77
|
+
const v = env[name.trim()];
|
|
78
|
+
if (v !== void 0 && v !== "") return v;
|
|
79
|
+
if (fallback !== void 0) return fallback;
|
|
80
|
+
missing.add(name.trim());
|
|
81
|
+
return "";
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
var sha = (s) => createHash("sha256").update(s).digest("hex");
|
|
85
|
+
function hostPath(url) {
|
|
86
|
+
try {
|
|
87
|
+
const u = new URL(url);
|
|
88
|
+
return `${u.host.toLowerCase()}${u.pathname.replace(/\/+$/, "")}`;
|
|
89
|
+
} catch {
|
|
90
|
+
return url.split("?")[0].slice(0, 200);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function splitPypi(spec) {
|
|
94
|
+
const m = /^([A-Za-z0-9][A-Za-z0-9._-]*)(?:\[[^\]]*\])?(?:(?:==|@)(\d[^\s,;]*))?/.exec(spec);
|
|
95
|
+
if (!m) return { name: spec, version: null };
|
|
96
|
+
return { name: m[1].toLowerCase().replace(/_/g, "-"), version: m[2] ?? null };
|
|
97
|
+
}
|
|
98
|
+
function splitImage(image) {
|
|
99
|
+
const noDigest = image.split("@")[0];
|
|
100
|
+
const lastColon = noDigest.lastIndexOf(":");
|
|
101
|
+
const hasTag = lastColon > noDigest.lastIndexOf("/");
|
|
102
|
+
const name = hasTag ? noDigest.slice(0, lastColon) : noDigest;
|
|
103
|
+
const tag = hasTag ? noDigest.slice(lastColon + 1) : null;
|
|
104
|
+
return { name: name.toLowerCase(), version: tag && /^v?\d/.test(tag) ? tag : null };
|
|
105
|
+
}
|
|
106
|
+
var DOCKER_VALUE_FLAGS = /* @__PURE__ */ new Set([
|
|
107
|
+
"-e",
|
|
108
|
+
"--env",
|
|
109
|
+
"-v",
|
|
110
|
+
"--volume",
|
|
111
|
+
"-p",
|
|
112
|
+
"--publish",
|
|
113
|
+
"--name",
|
|
114
|
+
"--network",
|
|
115
|
+
"-w",
|
|
116
|
+
"--workdir",
|
|
117
|
+
"--env-file",
|
|
118
|
+
"-u",
|
|
119
|
+
"--user",
|
|
120
|
+
"--mount",
|
|
121
|
+
"--entrypoint",
|
|
122
|
+
"--platform"
|
|
123
|
+
]);
|
|
124
|
+
function identify(alias, command, args, url) {
|
|
125
|
+
if (url) return { kind: "remote", registry: "remote", packageName: null, pinnedVersion: null };
|
|
126
|
+
let cmd = command ?? "";
|
|
127
|
+
let rest = args;
|
|
128
|
+
if (/^cmd(\.exe)?$/i.test(basename(cmd)) && /^\/c$/i.test(rest[0] ?? "")) {
|
|
129
|
+
cmd = rest[1] ?? "";
|
|
130
|
+
rest = rest.slice(2);
|
|
131
|
+
}
|
|
132
|
+
const bin = basename(cmd).replace(/\.(cmd|exe)$/i, "");
|
|
133
|
+
const base = classifyServer(alias, { command: bin, args: rest });
|
|
134
|
+
if (base.kind === "npm-stdio" && base.packageName) {
|
|
135
|
+
return {
|
|
136
|
+
kind: base.kind,
|
|
137
|
+
registry: "npm",
|
|
138
|
+
packageName: base.packageName,
|
|
139
|
+
pinnedVersion: base.version
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
if (bin === "uvx" || bin === "pipx" || bin === "uv") {
|
|
143
|
+
let tokens = rest;
|
|
144
|
+
if (bin === "pipx" && tokens[0] === "run") tokens = tokens.slice(1);
|
|
145
|
+
if (bin === "uv" && tokens[0] === "tool" && tokens[1] === "run") tokens = tokens.slice(2);
|
|
146
|
+
const from = tokens.indexOf("--from");
|
|
147
|
+
const spec = from >= 0 ? tokens[from + 1] : tokens.find((t, i) => !t.startsWith("-") && tokens[i - 1] !== "--python");
|
|
148
|
+
if (spec) {
|
|
149
|
+
const { name, version } = splitPypi(spec);
|
|
150
|
+
return {
|
|
151
|
+
kind: "other-registry",
|
|
152
|
+
registry: "pypi",
|
|
153
|
+
packageName: name,
|
|
154
|
+
pinnedVersion: version
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
if ((bin === "docker" || bin === "podman") && rest[0] === "run") {
|
|
159
|
+
for (let i = 1; i < rest.length; i++) {
|
|
160
|
+
const a = rest[i];
|
|
161
|
+
if (DOCKER_VALUE_FLAGS.has(a)) {
|
|
162
|
+
i++;
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
if (a.startsWith("-")) continue;
|
|
166
|
+
const { name, version } = splitImage(a);
|
|
167
|
+
return {
|
|
168
|
+
kind: "other-registry",
|
|
169
|
+
registry: "docker",
|
|
170
|
+
packageName: name,
|
|
171
|
+
pinnedVersion: version
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return { kind: base.kind, registry: "local", packageName: null, pinnedVersion: null };
|
|
176
|
+
}
|
|
177
|
+
function serverKeyFor(registry, packageName, url, alias) {
|
|
178
|
+
if (registry === "remote" && url) return `remote:${hostPath(url)}`;
|
|
179
|
+
if (packageName && registry !== "local" && registry !== "remote")
|
|
180
|
+
return `${registry}:${packageName}`;
|
|
181
|
+
return `local:${alias}`;
|
|
182
|
+
}
|
|
183
|
+
function collectSecrets(args, env, headers) {
|
|
184
|
+
const found = /* @__PURE__ */ new Set();
|
|
185
|
+
const add = (v) => {
|
|
186
|
+
if (!v || v.length < MIN_SECRET_LENGTH) return;
|
|
187
|
+
found.add(v);
|
|
188
|
+
const bearer = /^(?:bearer|token|basic)\s+(.+)$/i.exec(v);
|
|
189
|
+
if (bearer?.[1] && bearer[1].length >= MIN_SECRET_LENGTH) found.add(bearer[1]);
|
|
190
|
+
};
|
|
191
|
+
for (const [k, v] of Object.entries(env)) if (SECRET_NAME.test(k) || looksLikeSecret(v)) add(v);
|
|
192
|
+
for (const v of Object.values(headers)) add(v);
|
|
193
|
+
args.forEach((a, i) => {
|
|
194
|
+
if (looksLikeSecret(a)) add(a);
|
|
195
|
+
const eq = /^(--?[A-Za-z-_]+)=(.+)$/.exec(a);
|
|
196
|
+
if (eq && SECRET_FLAG.test(eq[1])) add(eq[2]);
|
|
197
|
+
if (SECRET_FLAG.test(a)) add(args[i + 1]);
|
|
198
|
+
const kv = /^([A-Z][A-Z0-9_]+)=(.+)$/.exec(a);
|
|
199
|
+
if (kv && SECRET_NAME.test(kv[1])) add(kv[2]);
|
|
200
|
+
});
|
|
201
|
+
return [...found].sort((a, b) => b.length - a.length);
|
|
202
|
+
}
|
|
203
|
+
function fingerprintConfig(spec, secrets, root, home) {
|
|
204
|
+
const normPath = (a) => {
|
|
205
|
+
let out = a;
|
|
206
|
+
if (root && out.startsWith(root)) out = `<project>${out.slice(root.length)}`;
|
|
207
|
+
if (home && out.startsWith(home)) out = `~${out.slice(home.length)}`;
|
|
208
|
+
return out;
|
|
209
|
+
};
|
|
210
|
+
const args = spec.args.map((a) => {
|
|
211
|
+
if (secrets.includes(a)) return "<secret>";
|
|
212
|
+
const kv = /^([A-Za-z-_][A-Za-z0-9-_]*)=(.*)$/.exec(a);
|
|
213
|
+
if (kv && secrets.includes(kv[2])) return `${kv[1]}=<secret>`;
|
|
214
|
+
if (spec.packageName && spec.registry === "npm" && a.startsWith(spec.packageName))
|
|
215
|
+
return spec.packageName;
|
|
216
|
+
if (spec.packageName && spec.registry === "pypi" && splitPypi(a).name === spec.packageName) {
|
|
217
|
+
return spec.packageName;
|
|
218
|
+
}
|
|
219
|
+
if (spec.packageName && spec.registry === "docker" && splitImage(a).name === spec.packageName) {
|
|
220
|
+
return spec.packageName;
|
|
221
|
+
}
|
|
222
|
+
return normPath(a);
|
|
223
|
+
});
|
|
224
|
+
const shape = {
|
|
225
|
+
t: spec.transport,
|
|
226
|
+
c: spec.command ? basename(spec.command) : null,
|
|
227
|
+
a: args,
|
|
228
|
+
e: Object.keys(spec.env).sort(),
|
|
229
|
+
h: Object.keys(spec.headers).map((h) => h.toLowerCase()).sort(),
|
|
230
|
+
u: spec.url ? hostPath(spec.url) : null
|
|
231
|
+
};
|
|
232
|
+
return sha(JSON.stringify(shape)).slice(0, 16);
|
|
233
|
+
}
|
|
234
|
+
function transportOf(raw, url, file) {
|
|
235
|
+
const declared = typeof raw.type === "string" ? raw.type : typeof raw.transport === "string" ? raw.transport : "";
|
|
236
|
+
const t = declared.toLowerCase().replace(/[-_]/g, "");
|
|
237
|
+
if (t === "stdio") return "stdio";
|
|
238
|
+
if (t === "sse") return "sse";
|
|
239
|
+
if (t === "http" || t === "streamablehttp") return "http";
|
|
240
|
+
if (typeof raw.httpUrl === "string") return "http";
|
|
241
|
+
if (url) {
|
|
242
|
+
return basename(file) === "settings.json" && file.includes(".gemini") ? "sse" : "auto";
|
|
243
|
+
}
|
|
244
|
+
return "stdio";
|
|
245
|
+
}
|
|
246
|
+
function readJson(path) {
|
|
247
|
+
try {
|
|
248
|
+
return JSON.parse(readFileSync(path, "utf8"));
|
|
249
|
+
} catch {
|
|
250
|
+
return null;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
var canonicalPath = (p) => {
|
|
254
|
+
try {
|
|
255
|
+
return realpathSync(p);
|
|
256
|
+
} catch {
|
|
257
|
+
return resolve(p);
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
function claudeProject(home, root) {
|
|
261
|
+
const cfg = readJson(join(home, ".claude.json"));
|
|
262
|
+
const projects = cfg?.projects;
|
|
263
|
+
if (!projects) return null;
|
|
264
|
+
const want = canonicalPath(root);
|
|
265
|
+
for (const [path, state] of Object.entries(projects)) {
|
|
266
|
+
if (path === root || canonicalPath(path) === want) return state;
|
|
267
|
+
}
|
|
268
|
+
return null;
|
|
269
|
+
}
|
|
270
|
+
function readServerConfigs(root, opts = {}) {
|
|
271
|
+
const abs = resolve(root);
|
|
272
|
+
const home = opts.home ?? homedir();
|
|
273
|
+
const env = opts.env ?? process.env;
|
|
274
|
+
const filesRead = [];
|
|
275
|
+
const notes = [];
|
|
276
|
+
const rel = (p) => isAbsolute(p) && p.startsWith(abs) ? relative(abs, p) || "." : p.replace(home, "~");
|
|
277
|
+
const project = claudeProject(home, abs);
|
|
278
|
+
const approved = new Set(strings(project?.enabledMcpjsonServers));
|
|
279
|
+
const rejected = new Set(strings(project?.disabledMcpjsonServers));
|
|
280
|
+
const approveAll = project?.enableAllProjectMcpServers === true;
|
|
281
|
+
const entries = [];
|
|
282
|
+
for (const path of mcpConfigPaths(abs, home)) {
|
|
283
|
+
const inProject = path.startsWith(abs + "/") || path.startsWith(abs + "\\");
|
|
284
|
+
if (opts.projectOnly && !inProject) continue;
|
|
285
|
+
if (!existsSync(path)) continue;
|
|
286
|
+
const cfg = readJson(path);
|
|
287
|
+
if (!cfg || typeof cfg !== "object") {
|
|
288
|
+
notes.push(`could not parse ${rel(path)}`);
|
|
289
|
+
continue;
|
|
290
|
+
}
|
|
291
|
+
let found = 0;
|
|
292
|
+
for (const section of ["mcpServers", "servers"]) {
|
|
293
|
+
const block = cfg[section];
|
|
294
|
+
if (!block || typeof block !== "object") continue;
|
|
295
|
+
for (const [alias, raw] of Object.entries(block)) {
|
|
296
|
+
if (!raw || typeof raw !== "object") continue;
|
|
297
|
+
entries.push({
|
|
298
|
+
alias,
|
|
299
|
+
raw,
|
|
300
|
+
file: path,
|
|
301
|
+
section,
|
|
302
|
+
scope: inProject ? "project" : "user"
|
|
303
|
+
});
|
|
304
|
+
found++;
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (found) filesRead.push(rel(path));
|
|
308
|
+
}
|
|
309
|
+
if (!opts.projectOnly && project?.mcpServers && typeof project.mcpServers === "object") {
|
|
310
|
+
for (const [alias, raw] of Object.entries(project.mcpServers)) {
|
|
311
|
+
if (!raw || typeof raw !== "object") continue;
|
|
312
|
+
entries.push({
|
|
313
|
+
alias,
|
|
314
|
+
raw,
|
|
315
|
+
file: join(home, ".claude.json"),
|
|
316
|
+
section: `projects[${abs}].mcpServers`,
|
|
317
|
+
scope: "local"
|
|
318
|
+
});
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
322
|
+
for (const { alias, raw, file, section, scope } of entries) {
|
|
323
|
+
const missing = /* @__PURE__ */ new Set();
|
|
324
|
+
const x = (v) => expandVars(v, env, missing);
|
|
325
|
+
const rawUrl = typeof raw.httpUrl === "string" ? raw.httpUrl : typeof raw.url === "string" ? raw.url : typeof raw.serverUrl === "string" ? raw.serverUrl : null;
|
|
326
|
+
const url = rawUrl ? x(rawUrl) : null;
|
|
327
|
+
const command = typeof raw.command === "string" ? x(raw.command) : null;
|
|
328
|
+
const args = strings(raw.args).map(x);
|
|
329
|
+
const envVars = Object.fromEntries(Object.entries(record(raw.env)).map(([k, v]) => [k, x(v)]));
|
|
330
|
+
const headers = Object.fromEntries(
|
|
331
|
+
Object.entries(record(raw.headers)).map(([k, v]) => [k, x(v)])
|
|
332
|
+
);
|
|
333
|
+
const cwd = typeof raw.cwd === "string" ? resolve(abs, x(raw.cwd)) : null;
|
|
334
|
+
const transport = transportOf(raw, url, file);
|
|
335
|
+
if (!url && !command) {
|
|
336
|
+
notes.push(`${rel(file)}: server "${alias}" has neither a command nor a url`);
|
|
337
|
+
continue;
|
|
338
|
+
}
|
|
339
|
+
const id = identify(alias, command, args, url);
|
|
340
|
+
const secrets = collectSecrets(args, envVars, headers);
|
|
341
|
+
const serverKey = serverKeyFor(id.registry, id.packageName, url, alias);
|
|
342
|
+
const configFingerprint = fingerprintConfig(
|
|
343
|
+
{
|
|
344
|
+
transport,
|
|
345
|
+
command,
|
|
346
|
+
args,
|
|
347
|
+
env: envVars,
|
|
348
|
+
headers,
|
|
349
|
+
url,
|
|
350
|
+
registry: id.registry,
|
|
351
|
+
packageName: id.packageName
|
|
352
|
+
},
|
|
353
|
+
secrets,
|
|
354
|
+
abs,
|
|
355
|
+
home
|
|
356
|
+
);
|
|
357
|
+
let trusted = true;
|
|
358
|
+
let trustReason = scope === "project" ? "approved" : "your own configuration";
|
|
359
|
+
if (scope === "project") {
|
|
360
|
+
const isMcpJson = basename(file) === ".mcp.json";
|
|
361
|
+
if (isMcpJson && rejected.has(alias)) {
|
|
362
|
+
trusted = false;
|
|
363
|
+
trustReason = "you declined this server in Claude Code";
|
|
364
|
+
} else if (opts.trustProject) {
|
|
365
|
+
trustReason = "trusted with --trust-project";
|
|
366
|
+
} else if (isMcpJson && (approveAll || approved.has(alias))) {
|
|
367
|
+
trustReason = "approved in Claude Code";
|
|
368
|
+
} else {
|
|
369
|
+
trusted = false;
|
|
370
|
+
trustReason = `committed to the repository (${rel(file)}) and not approved; pass --trust-project to launch it`;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const spec = {
|
|
374
|
+
alias,
|
|
375
|
+
transport,
|
|
376
|
+
command,
|
|
377
|
+
args,
|
|
378
|
+
env: envVars,
|
|
379
|
+
cwd,
|
|
380
|
+
url,
|
|
381
|
+
headers,
|
|
382
|
+
...id,
|
|
383
|
+
serverKey,
|
|
384
|
+
configFingerprint,
|
|
385
|
+
scope,
|
|
386
|
+
trusted,
|
|
387
|
+
trustReason,
|
|
388
|
+
unresolved: [...missing].sort(),
|
|
389
|
+
disabled: raw.disabled === true || raw.enabled === false,
|
|
390
|
+
secrets,
|
|
391
|
+
sources: [{ file: rel(file), section }]
|
|
392
|
+
};
|
|
393
|
+
const key = `${serverKey}#${configFingerprint}`;
|
|
394
|
+
const seen = byKey.get(key);
|
|
395
|
+
if (seen) {
|
|
396
|
+
seen.sources.push(...spec.sources);
|
|
397
|
+
if (spec.trusted && !seen.trusted) {
|
|
398
|
+
seen.trusted = true;
|
|
399
|
+
seen.trustReason = spec.trustReason;
|
|
400
|
+
}
|
|
401
|
+
seen.disabled &&= spec.disabled;
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
byKey.set(key, spec);
|
|
405
|
+
}
|
|
406
|
+
return { root: abs, servers: [...byKey.values()], filesRead, notes };
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
export {
|
|
410
|
+
scrub,
|
|
411
|
+
scrubDeep,
|
|
412
|
+
readServerConfigs
|
|
413
|
+
};
|