@epoch-agent/infra 0.1.0 → 0.2.0
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 +20 -20
- package/dist/index.d.ts +638 -37
- package/dist/index.js +798 -388
- package/dist/locales/en.yaml +3190 -2396
- package/dist/locales/zh.yaml +3355 -2740
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import { platform, homedir, tmpdir } from 'os';
|
|
2
|
-
import { join, dirname, delimiter, resolve as resolve$1, isAbsolute,
|
|
2
|
+
import { join, dirname, basename, delimiter, resolve as resolve$1, isAbsolute, win32, posix, relative } from 'path';
|
|
3
3
|
import { existsSync, readFileSync, writeFileSync, mkdirSync, openSync, closeSync, writeSync, renameSync, rmSync, chmodSync, lstatSync, realpathSync, accessSync, constants } from 'fs';
|
|
4
4
|
import Database from 'better-sqlite3';
|
|
5
5
|
import { execFileSync, execFile, spawn } from 'child_process';
|
|
6
6
|
import { TextDecoder } from 'util';
|
|
7
|
-
import { createRequire } from 'module';
|
|
8
7
|
import { fileURLToPath } from 'url';
|
|
9
8
|
import { load } from 'js-yaml';
|
|
9
|
+
import { createRequire } from 'module';
|
|
10
10
|
import { randomBytes, createCipheriv, createDecipheriv, createHash } from 'crypto';
|
|
11
11
|
|
|
12
12
|
// src/paths.ts
|
|
@@ -132,6 +132,16 @@ function projectHooksPath(projectRoot) {
|
|
|
132
132
|
function projectPoliciesDir(projectRoot) {
|
|
133
133
|
return join(projectRoot, PROJECT_DIR_NAME, "policies");
|
|
134
134
|
}
|
|
135
|
+
var CLAUDE_DIR_NAME = ".claude";
|
|
136
|
+
function claudeUserSettingsPath(home = homedir()) {
|
|
137
|
+
return join(home, CLAUDE_DIR_NAME, "settings.json");
|
|
138
|
+
}
|
|
139
|
+
function projectClaudeSettingsPath(projectRoot) {
|
|
140
|
+
return join(projectRoot, CLAUDE_DIR_NAME, "settings.json");
|
|
141
|
+
}
|
|
142
|
+
function projectClaudeLocalSettingsPath(projectRoot) {
|
|
143
|
+
return join(projectRoot, CLAUDE_DIR_NAME, "settings.local.json");
|
|
144
|
+
}
|
|
135
145
|
function managedSettingsPath(os = platform()) {
|
|
136
146
|
const FILE = "managed-settings.json";
|
|
137
147
|
if (os === "win32") {
|
|
@@ -269,7 +279,6 @@ function writeArtifact(dir, name, extension, data, onExisting = "unique") {
|
|
|
269
279
|
handle.close();
|
|
270
280
|
return handle.path;
|
|
271
281
|
}
|
|
272
|
-
|
|
273
282
|
// src/text-slice.ts
|
|
274
283
|
function isHighSurrogate(code) {
|
|
275
284
|
return code >= 55296 && code <= 56319;
|
|
@@ -459,7 +468,6 @@ function schemaVersion(db, namespace) {
|
|
|
459
468
|
const row = db.prepare("SELECT MAX(version) AS v FROM _epoch_migrations WHERE namespace = ?").get(namespace);
|
|
460
469
|
return row?.v ?? 0;
|
|
461
470
|
}
|
|
462
|
-
|
|
463
471
|
// src/mask.ts
|
|
464
472
|
function maskApiKey(key) {
|
|
465
473
|
if (!key || key.length < 8) return "***";
|
|
@@ -491,7 +499,6 @@ function maskSensitive(data) {
|
|
|
491
499
|
}
|
|
492
500
|
return masked;
|
|
493
501
|
}
|
|
494
|
-
|
|
495
502
|
// src/logger.ts
|
|
496
503
|
var currentLevel = "info";
|
|
497
504
|
function setLogLevel(level) {
|
|
@@ -529,6 +536,177 @@ function createLogger(module) {
|
|
|
529
536
|
}
|
|
530
537
|
};
|
|
531
538
|
}
|
|
539
|
+
var LANGS = ["zh", "en"];
|
|
540
|
+
var DEFAULT_LANG = "zh";
|
|
541
|
+
function isLang(value) {
|
|
542
|
+
return LANGS.includes(value);
|
|
543
|
+
}
|
|
544
|
+
var catalogs = /* @__PURE__ */ new Map();
|
|
545
|
+
var pending = [];
|
|
546
|
+
var localesDirCache;
|
|
547
|
+
var currentLangValue;
|
|
548
|
+
function localesDir() {
|
|
549
|
+
if (localesDirCache !== void 0) return localesDirCache ?? void 0;
|
|
550
|
+
const override = process.env.EPOCH_LOCALES_DIR;
|
|
551
|
+
if (override) {
|
|
552
|
+
localesDirCache = existsSync(override) ? override : null;
|
|
553
|
+
return localesDirCache ?? void 0;
|
|
554
|
+
}
|
|
555
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
556
|
+
for (let depth = 0; depth < 8; depth += 1) {
|
|
557
|
+
if (basename(dir) === "node_modules") break;
|
|
558
|
+
const candidate = join(dir, "locales");
|
|
559
|
+
if (existsSync(join(candidate, `${DEFAULT_LANG}.yaml`))) {
|
|
560
|
+
localesDirCache = candidate;
|
|
561
|
+
return candidate;
|
|
562
|
+
}
|
|
563
|
+
const parent = dirname(dir);
|
|
564
|
+
if (parent === dir) break;
|
|
565
|
+
dir = parent;
|
|
566
|
+
}
|
|
567
|
+
localesDirCache = null;
|
|
568
|
+
return void 0;
|
|
569
|
+
}
|
|
570
|
+
function flatten(value, prefix, out) {
|
|
571
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
572
|
+
for (const [key, child] of Object.entries(value)) {
|
|
573
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
574
|
+
if (typeof child === "string") out[path] = child;
|
|
575
|
+
else flatten(child, path, out);
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
function fallbackDetail(key, vars) {
|
|
579
|
+
const text = t(key, vars);
|
|
580
|
+
if (text !== key) return text;
|
|
581
|
+
switch (key) {
|
|
582
|
+
case "i18n.locales_missing":
|
|
583
|
+
return "locales directory not found; UI text will show as key paths";
|
|
584
|
+
case "i18n.catalog_missing":
|
|
585
|
+
return `locales/${vars["file"]} is missing; ${vars["lang"]} text falls back to the default language`;
|
|
586
|
+
case "i18n.catalog_invalid":
|
|
587
|
+
return `locales/${vars["file"]} is not valid YAML (${vars["reason"]}); fell back to the built-in default`;
|
|
588
|
+
default:
|
|
589
|
+
return key;
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
function loadCatalog(lang) {
|
|
593
|
+
const cached2 = catalogs.get(lang);
|
|
594
|
+
if (cached2) return cached2;
|
|
595
|
+
const result = readCatalog(lang);
|
|
596
|
+
catalogs.set(lang, result);
|
|
597
|
+
return result;
|
|
598
|
+
}
|
|
599
|
+
function readCatalog(lang) {
|
|
600
|
+
const dir = localesDir();
|
|
601
|
+
if (!dir) {
|
|
602
|
+
pending.push({
|
|
603
|
+
code: "locales-missing",
|
|
604
|
+
render: () => fallbackDetail("i18n.locales_missing", {})
|
|
605
|
+
});
|
|
606
|
+
return { entries: {}, error: "\u627E\u4E0D\u5230 locales \u76EE\u5F55" };
|
|
607
|
+
}
|
|
608
|
+
const file = `${lang}.yaml`;
|
|
609
|
+
const path = join(dir, file);
|
|
610
|
+
if (!existsSync(path)) {
|
|
611
|
+
pending.push({
|
|
612
|
+
code: "catalog-missing",
|
|
613
|
+
render: () => fallbackDetail("i18n.catalog_missing", { file, lang })
|
|
614
|
+
});
|
|
615
|
+
return { entries: {}, error: `${path} \u4E0D\u5B58\u5728` };
|
|
616
|
+
}
|
|
617
|
+
let parsed;
|
|
618
|
+
try {
|
|
619
|
+
parsed = load(readFileSync(path, "utf-8"));
|
|
620
|
+
} catch (err) {
|
|
621
|
+
const reason = (err instanceof Error ? err.message : String(err)).split("\n")[0] ?? "";
|
|
622
|
+
pending.push({
|
|
623
|
+
code: "catalog-invalid",
|
|
624
|
+
render: () => fallbackDetail("i18n.catalog_invalid", { file, reason })
|
|
625
|
+
});
|
|
626
|
+
return { entries: {}, error: reason };
|
|
627
|
+
}
|
|
628
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
629
|
+
const reason = "\u9876\u5C42\u4E0D\u662F\u952E\u503C\u5BF9";
|
|
630
|
+
pending.push({
|
|
631
|
+
code: "catalog-invalid",
|
|
632
|
+
render: () => fallbackDetail("i18n.catalog_invalid", { file, reason })
|
|
633
|
+
});
|
|
634
|
+
return { entries: {}, error: reason };
|
|
635
|
+
}
|
|
636
|
+
const entries = {};
|
|
637
|
+
flatten(parsed, "", entries);
|
|
638
|
+
return { entries };
|
|
639
|
+
}
|
|
640
|
+
function resetI18n() {
|
|
641
|
+
catalogs.clear();
|
|
642
|
+
pending = [];
|
|
643
|
+
localesDirCache = void 0;
|
|
644
|
+
currentLangValue = void 0;
|
|
645
|
+
}
|
|
646
|
+
var PLACEHOLDER = /\{(\w+)\}/g;
|
|
647
|
+
function placeholdersOf(text) {
|
|
648
|
+
const found = [];
|
|
649
|
+
for (const match of text.matchAll(PLACEHOLDER)) {
|
|
650
|
+
const name = match[1];
|
|
651
|
+
if (name && !found.includes(name)) found.push(name);
|
|
652
|
+
}
|
|
653
|
+
return found;
|
|
654
|
+
}
|
|
655
|
+
function t(key, vars, lang) {
|
|
656
|
+
const target = lang ?? currentLang();
|
|
657
|
+
const text = loadCatalog(target).entries[key] ?? loadCatalog(DEFAULT_LANG).entries[key] ?? key;
|
|
658
|
+
if (!vars) return text;
|
|
659
|
+
return text.replace(PLACEHOLDER, (whole, name) => {
|
|
660
|
+
const value = vars[name];
|
|
661
|
+
return value === void 0 ? whole : String(value);
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
function currentLang() {
|
|
665
|
+
currentLangValue ??= resolveLang();
|
|
666
|
+
return currentLangValue;
|
|
667
|
+
}
|
|
668
|
+
function setLang(lang) {
|
|
669
|
+
currentLangValue = lang;
|
|
670
|
+
}
|
|
671
|
+
function uiDateLocale() {
|
|
672
|
+
switch (currentLang()) {
|
|
673
|
+
case "zh":
|
|
674
|
+
return "zh-CN";
|
|
675
|
+
case "en":
|
|
676
|
+
return "en-US";
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
function systemLocaleSignals() {
|
|
680
|
+
const env = process.env;
|
|
681
|
+
const signals = [env.LC_ALL, env.LC_MESSAGES, env.LANG, env.LANGUAGE].filter(
|
|
682
|
+
(v) => typeof v === "string" && v.length > 0
|
|
683
|
+
);
|
|
684
|
+
try {
|
|
685
|
+
signals.push(new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
686
|
+
} catch {
|
|
687
|
+
}
|
|
688
|
+
return signals;
|
|
689
|
+
}
|
|
690
|
+
function langFromLocale(tag) {
|
|
691
|
+
const primary = tag.split(/[.@]/)[0]?.replace("_", "-").toLowerCase() ?? "";
|
|
692
|
+
if (!primary || primary === "c" || primary === "posix") return void 0;
|
|
693
|
+
if (primary === "zh" || primary.startsWith("zh-")) return "zh";
|
|
694
|
+
return "en";
|
|
695
|
+
}
|
|
696
|
+
function resolveLang(configured, signals) {
|
|
697
|
+
const fromEnv = process.env.EPOCH_LANGUAGE;
|
|
698
|
+
if (fromEnv && isLang(fromEnv)) return fromEnv;
|
|
699
|
+
if (configured && isLang(configured)) return configured;
|
|
700
|
+
for (const tag of signals ?? systemLocaleSignals()) {
|
|
701
|
+
const lang = langFromLocale(tag);
|
|
702
|
+
if (lang) return lang;
|
|
703
|
+
}
|
|
704
|
+
return DEFAULT_LANG;
|
|
705
|
+
}
|
|
706
|
+
function i18nDiagnostics() {
|
|
707
|
+
return pending.map((item) => ({ code: item.code, detail: item.render() }));
|
|
708
|
+
}
|
|
709
|
+
// src/platform.ts
|
|
532
710
|
var IS_WINDOWS = platform() === "win32";
|
|
533
711
|
var WINDOWS_HIDE_FLAGS = IS_WINDOWS ? { windowsHide: true } : {};
|
|
534
712
|
function getPythonCommand() {
|
|
@@ -538,9 +716,7 @@ function getPythonCommand() {
|
|
|
538
716
|
var SHELL_KINDS = ["cmd", "powershell", "pwsh"];
|
|
539
717
|
var SHELL_EXECUTABLES = {
|
|
540
718
|
cmd: "cmd.exe",
|
|
541
|
-
// Windows PowerShell 5.1,随系统自带
|
|
542
719
|
powershell: "powershell.exe",
|
|
543
|
-
// PowerShell 7+,要自己装
|
|
544
720
|
pwsh: "pwsh.exe"
|
|
545
721
|
};
|
|
546
722
|
var configuredShell = null;
|
|
@@ -563,7 +739,7 @@ function probeShell(kind) {
|
|
|
563
739
|
for (const dir of dirs) {
|
|
564
740
|
if (existsSync(join(dir, exe))) return null;
|
|
565
741
|
}
|
|
566
|
-
return
|
|
742
|
+
return t("infra_misc.shell_not_on_path", { kind, exe });
|
|
567
743
|
}
|
|
568
744
|
var PS_EXIT_TRAILER = [
|
|
569
745
|
"$__epochOk = $?",
|
|
@@ -605,15 +781,10 @@ function encodingForCodePage(codePage) {
|
|
|
605
781
|
const table = {
|
|
606
782
|
65001: "utf-8",
|
|
607
783
|
936: "gbk",
|
|
608
|
-
// 简体中文
|
|
609
784
|
950: "big5",
|
|
610
|
-
// 繁体中文
|
|
611
785
|
932: "shift_jis",
|
|
612
|
-
// 日文
|
|
613
786
|
949: "euc-kr",
|
|
614
|
-
// 韩文
|
|
615
787
|
866: "ibm866",
|
|
616
|
-
// 西里尔 OEM
|
|
617
788
|
874: "windows-874",
|
|
618
789
|
1250: "windows-1250",
|
|
619
790
|
1251: "windows-1251",
|
|
@@ -695,7 +866,10 @@ function createStreamDecoder(fallbackEncoding = detectConsoleEncoding()) {
|
|
|
695
866
|
}
|
|
696
867
|
};
|
|
697
868
|
}
|
|
698
|
-
|
|
869
|
+
function ripgrepInstallHint() {
|
|
870
|
+
if (IS_WINDOWS) return t("infra_misc.ripgrep_install_windows");
|
|
871
|
+
return process.platform === "darwin" ? "brew install ripgrep" : "apt install ripgrep / dnf install ripgrep / pacman -S ripgrep";
|
|
872
|
+
}
|
|
699
873
|
function ensureExecutable(path) {
|
|
700
874
|
try {
|
|
701
875
|
accessSync(path, constants.X_OK);
|
|
@@ -777,7 +951,7 @@ function resolve(options) {
|
|
|
777
951
|
}
|
|
778
952
|
}
|
|
779
953
|
}
|
|
780
|
-
return { mode: "missing", command: "rg", hint:
|
|
954
|
+
return { mode: "missing", command: "rg", hint: ripgrepInstallHint() };
|
|
781
955
|
}
|
|
782
956
|
var cached = null;
|
|
783
957
|
function resolveRipgrep(options) {
|
|
@@ -812,24 +986,55 @@ async function killProcessTree(options) {
|
|
|
812
986
|
const { pid, pty, detached = false, platform: platform10 = platform() } = options;
|
|
813
987
|
if (!isRealChildPid(pid)) return;
|
|
814
988
|
if (platform10 === "win32") {
|
|
815
|
-
const
|
|
989
|
+
const snapshot = await windowsSnapshot();
|
|
990
|
+
if (snapshot.pids.size > 0 && !snapshot.pids.has(pid)) {
|
|
991
|
+
tryPtyKill(pty);
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const descendants2 = walkTree(pid, (parent) => snapshot.childrenOf.get(parent) ?? []);
|
|
995
|
+
await killPids(descendants2, { signal: "SIGKILL" });
|
|
996
|
+
const swept = isAlive(pid) ? await runProbe("taskkill", ["/pid", String(pid), "/f", "/t"]) : "";
|
|
816
997
|
tryPtyKill(pty);
|
|
817
|
-
await killPids([
|
|
818
|
-
await runProbe("taskkill", ["/pid", String(pid), "/f", "/t"]);
|
|
998
|
+
if (!swept && isAlive(pid)) await killPids([pid], { signal: "SIGKILL" });
|
|
819
999
|
return;
|
|
820
1000
|
}
|
|
821
1001
|
const descendants = await collectProcessTree(pid, platform10);
|
|
822
1002
|
await killPids([...descendants, pid], options, {
|
|
823
|
-
// 只有 detached 才允许碰 -pid —— 这一行就是文件头那条约束的落点
|
|
824
1003
|
...detached ? { groupLeaderPid: pid } : {},
|
|
825
1004
|
...pty ? { pty } : {}
|
|
826
1005
|
});
|
|
827
1006
|
}
|
|
1007
|
+
async function collectProcessTreeStamped(rootPid, platform10 = platform()) {
|
|
1008
|
+
if (!isRealChildPid(rootPid)) return [];
|
|
1009
|
+
if (platform10 !== "win32") {
|
|
1010
|
+
const pids = await collectProcessTree(rootPid, platform10);
|
|
1011
|
+
return pids.map((pid) => ({ pid, born: "" }));
|
|
1012
|
+
}
|
|
1013
|
+
const snapshot = await windowsSnapshot();
|
|
1014
|
+
return walkTree(rootPid, (parent) => snapshot.childrenOf.get(parent) ?? []).map((pid) => ({
|
|
1015
|
+
pid,
|
|
1016
|
+
born: snapshot.bornOf.get(pid) ?? ""
|
|
1017
|
+
}));
|
|
1018
|
+
}
|
|
1019
|
+
async function killStampedPids(stamped, options = {}, platform10 = platform()) {
|
|
1020
|
+
if (stamped.length === 0) return;
|
|
1021
|
+
if (platform10 !== "win32") {
|
|
1022
|
+
await killPids(
|
|
1023
|
+
stamped.map((s) => s.pid),
|
|
1024
|
+
options
|
|
1025
|
+
);
|
|
1026
|
+
return;
|
|
1027
|
+
}
|
|
1028
|
+
const snapshot = await windowsSnapshot();
|
|
1029
|
+
const targets = snapshot.pids.size === 0 ? stamped.map((s) => s.pid) : stamped.filter((s) => s.born === "" || snapshot.bornOf.get(s.pid) === s.born).map((s) => s.pid);
|
|
1030
|
+
if (targets.length > 0) await killPids(targets, options);
|
|
1031
|
+
}
|
|
828
1032
|
async function killPids(pids, options = {}, extra = {}) {
|
|
829
1033
|
const { escalate = false, isExited = () => false, killTimeoutMs = SIGKILL_TIMEOUT_MS } = options;
|
|
830
1034
|
const targets = pids.filter(isRealChildPid);
|
|
831
1035
|
const first = options.signal ?? (escalate ? "SIGTERM" : "SIGKILL");
|
|
832
1036
|
sweep(targets, first, extra);
|
|
1037
|
+
tryPtyKill(extra.pty, first);
|
|
833
1038
|
if (!escalate || isExited()) return;
|
|
834
1039
|
await delay(killTimeoutMs);
|
|
835
1040
|
if (isExited()) return;
|
|
@@ -838,7 +1043,14 @@ async function killPids(pids, options = {}, extra = {}) {
|
|
|
838
1043
|
function sweep(targets, signal, extra) {
|
|
839
1044
|
if (extra.groupLeaderPid !== void 0) sendSignal(-extra.groupLeaderPid, signal);
|
|
840
1045
|
for (const target of targets) sendSignal(target, signal);
|
|
841
|
-
|
|
1046
|
+
}
|
|
1047
|
+
function isAlive(pid) {
|
|
1048
|
+
try {
|
|
1049
|
+
process.kill(pid, 0);
|
|
1050
|
+
return true;
|
|
1051
|
+
} catch (err) {
|
|
1052
|
+
return err.code === "EPERM";
|
|
1053
|
+
}
|
|
842
1054
|
}
|
|
843
1055
|
function sendSignal(target, signal) {
|
|
844
1056
|
try {
|
|
@@ -855,14 +1067,35 @@ function tryPtyKill(pty, signal) {
|
|
|
855
1067
|
}
|
|
856
1068
|
async function collectProcessTree(rootPid, platform10 = platform()) {
|
|
857
1069
|
if (!isRealChildPid(rootPid)) return [];
|
|
858
|
-
|
|
1070
|
+
if (platform10 === "win32") {
|
|
1071
|
+
const snapshot = await windowsSnapshot();
|
|
1072
|
+
return walkTree(rootPid, (parent) => snapshot.childrenOf.get(parent) ?? []);
|
|
1073
|
+
}
|
|
1074
|
+
const found = [];
|
|
1075
|
+
const seen = /* @__PURE__ */ new Set([rootPid]);
|
|
1076
|
+
let frontier = [rootPid];
|
|
1077
|
+
for (let depth = 0; depth < MAX_TREE_DEPTH && frontier.length > 0; depth++) {
|
|
1078
|
+
const next = [];
|
|
1079
|
+
for (const parent of frontier) {
|
|
1080
|
+
for (const child of await listChildrenViaPgrep(parent)) {
|
|
1081
|
+
if (seen.has(child) || found.length >= MAX_TREE_SIZE) continue;
|
|
1082
|
+
seen.add(child);
|
|
1083
|
+
found.push(child);
|
|
1084
|
+
next.push(child);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
frontier = next;
|
|
1088
|
+
}
|
|
1089
|
+
return found.reverse();
|
|
1090
|
+
}
|
|
1091
|
+
function walkTree(rootPid, childrenOf) {
|
|
859
1092
|
const found = [];
|
|
860
1093
|
const seen = /* @__PURE__ */ new Set([rootPid]);
|
|
861
1094
|
let frontier = [rootPid];
|
|
862
1095
|
for (let depth = 0; depth < MAX_TREE_DEPTH && frontier.length > 0; depth++) {
|
|
863
1096
|
const next = [];
|
|
864
1097
|
for (const parent of frontier) {
|
|
865
|
-
for (const child of
|
|
1098
|
+
for (const child of childrenOf(parent)) {
|
|
866
1099
|
if (seen.has(child) || found.length >= MAX_TREE_SIZE) continue;
|
|
867
1100
|
seen.add(child);
|
|
868
1101
|
found.push(child);
|
|
@@ -877,9 +1110,13 @@ var listChildrenViaPgrep = async (parentPid) => {
|
|
|
877
1110
|
const stdout = await runProbe("pgrep", ["-P", String(parentPid)]);
|
|
878
1111
|
return stdout.split("\n").map((line) => Number.parseInt(line.trim(), 10)).filter(isRealChildPid);
|
|
879
1112
|
};
|
|
880
|
-
async function
|
|
881
|
-
const
|
|
882
|
-
return
|
|
1113
|
+
async function windowsSnapshot() {
|
|
1114
|
+
const records = await readWindowsSnapshot();
|
|
1115
|
+
return {
|
|
1116
|
+
pids: new Set(records.map((record) => record.pid)),
|
|
1117
|
+
childrenOf: buildChildrenMap(records),
|
|
1118
|
+
bornOf: new Map(records.map((record) => [record.pid, record.born]))
|
|
1119
|
+
};
|
|
883
1120
|
}
|
|
884
1121
|
async function readWindowsSnapshot() {
|
|
885
1122
|
const fast = parseWmicCsv(await runProbe("wmic", WMIC_SNAPSHOT_ARGS, SNAPSHOT_MAX_BUFFER));
|
|
@@ -917,7 +1154,6 @@ function parseWmicCsv(stdout) {
|
|
|
917
1154
|
return {
|
|
918
1155
|
pid: Number.parseInt(cells[atPid] ?? "", 10),
|
|
919
1156
|
ppid: Number.parseInt(cells[atPpid] ?? "", 10),
|
|
920
|
-
// `20260807071417.130820+480` → 留到秒的那 14 位就够比大小了
|
|
921
1157
|
born: normalizeBorn((cells[atBorn] ?? "").slice(0, 14))
|
|
922
1158
|
};
|
|
923
1159
|
});
|
|
@@ -958,7 +1194,6 @@ function runProbe(file, args2, maxBuffer) {
|
|
|
958
1194
|
function delay(ms) {
|
|
959
1195
|
return new Promise((resolve5) => setTimeout(resolve5, ms));
|
|
960
1196
|
}
|
|
961
|
-
|
|
962
1197
|
// src/shell-parse.ts
|
|
963
1198
|
function defaultFlavor() {
|
|
964
1199
|
return IS_WINDOWS ? "windows" : "posix";
|
|
@@ -1111,7 +1346,7 @@ function parseShellCommand(command, flavor) {
|
|
|
1111
1346
|
const hasSubstitution = kind === "windows" ? detectWindowsSubstitution(stripped) : detectPosixSubstitution(stripped);
|
|
1112
1347
|
const tokens = tokenize(stripped, kind);
|
|
1113
1348
|
if (tokens === null) {
|
|
1114
|
-
return { segments: [], hasSubstitution, parseError: "
|
|
1349
|
+
return { segments: [], hasSubstitution, parseError: t("infra_misc.unclosed_quote"), stripped };
|
|
1115
1350
|
}
|
|
1116
1351
|
const segments = [];
|
|
1117
1352
|
let words = [];
|
|
@@ -1156,7 +1391,6 @@ function hasSideEffectChannel(parsed) {
|
|
|
1156
1391
|
if (parsed.segments.length > 1) return true;
|
|
1157
1392
|
return parsed.segments.some((s) => s.redirects.length > 0);
|
|
1158
1393
|
}
|
|
1159
|
-
|
|
1160
1394
|
// src/command-safety.ts
|
|
1161
1395
|
function rootMatches(rule, root) {
|
|
1162
1396
|
if (typeof rule === "string") return root === rule;
|
|
@@ -1206,7 +1440,6 @@ function inlineCodeExecOf(seg) {
|
|
|
1206
1440
|
return args(seg).some((a) => entry.flags.includes(a)) ? seg.root : null;
|
|
1207
1441
|
}
|
|
1208
1442
|
var CODE_EXEC_ROOTS = [
|
|
1209
|
-
// 解释器
|
|
1210
1443
|
"python",
|
|
1211
1444
|
"python2",
|
|
1212
1445
|
"python3",
|
|
@@ -1218,25 +1451,21 @@ var CODE_EXEC_ROOTS = [
|
|
|
1218
1451
|
"perl",
|
|
1219
1452
|
"php",
|
|
1220
1453
|
"lua",
|
|
1221
|
-
// 包运行器 —— 后面跟什么都能跑
|
|
1222
1454
|
"npx",
|
|
1223
1455
|
"bunx",
|
|
1224
1456
|
"npm run",
|
|
1225
1457
|
"yarn run",
|
|
1226
1458
|
"pnpm run",
|
|
1227
1459
|
"bun run",
|
|
1228
|
-
// shell 与远程执行
|
|
1229
1460
|
...SHELLS,
|
|
1230
1461
|
"ssh",
|
|
1231
1462
|
"eval",
|
|
1232
1463
|
"exec",
|
|
1233
1464
|
"env",
|
|
1234
1465
|
"xargs",
|
|
1235
|
-
// 提权
|
|
1236
1466
|
"sudo",
|
|
1237
1467
|
"doas",
|
|
1238
1468
|
"su",
|
|
1239
|
-
// PowerShell
|
|
1240
1469
|
"powershell",
|
|
1241
1470
|
"pwsh"
|
|
1242
1471
|
];
|
|
@@ -1279,69 +1508,70 @@ var ENCODED_COMMAND_RULE = {
|
|
|
1279
1508
|
const got = m[1].toLowerCase();
|
|
1280
1509
|
return got.length > 0 && got.startsWith("e") && "encodedcommand".startsWith(got);
|
|
1281
1510
|
}),
|
|
1282
|
-
desc:
|
|
1511
|
+
desc: () => t("danger.ps_encoded_command")
|
|
1283
1512
|
};
|
|
1284
1513
|
var POSIX_RULES = [
|
|
1285
1514
|
{
|
|
1286
1515
|
root: "rm",
|
|
1287
1516
|
when: (s) => hasRecursiveForce(s) && args(s).some(isDangerousRmTarget),
|
|
1288
|
-
desc: "
|
|
1517
|
+
desc: () => t("danger.rm_system_root")
|
|
1289
1518
|
},
|
|
1290
1519
|
{
|
|
1291
1520
|
root: "rm",
|
|
1292
1521
|
when: (s) => hasRecursiveForce(s) && args(s).some(isEmptyVarSlash),
|
|
1293
|
-
desc:
|
|
1522
|
+
desc: () => t("danger.rm_empty_var")
|
|
1294
1523
|
},
|
|
1295
1524
|
{
|
|
1296
|
-
// `find / -delete` / `find / -exec rm` —— 老实现整条漏了
|
|
1297
1525
|
root: "find",
|
|
1298
1526
|
when: (s) => args(s).some(isDangerousRmTarget) && args(s).some((a) => a === "-delete" || a === "-exec" || a === "-execdir"),
|
|
1299
|
-
desc:
|
|
1527
|
+
desc: () => t("danger.find_delete_system")
|
|
1300
1528
|
},
|
|
1301
|
-
{ root: /^mkfs(\.\w+)?$/, desc: "
|
|
1529
|
+
{ root: /^mkfs(\.\w+)?$/, desc: () => t("danger.mkfs") },
|
|
1302
1530
|
{
|
|
1303
1531
|
root: "dd",
|
|
1304
1532
|
when: (s) => args(s).some((a) => a.startsWith("of=/dev/")),
|
|
1305
|
-
desc: "
|
|
1533
|
+
desc: () => t("danger.dd_raw_device")
|
|
1534
|
+
},
|
|
1535
|
+
{
|
|
1536
|
+
root: "dd",
|
|
1537
|
+
when: (s) => args(s).some((a) => a.startsWith("if=")),
|
|
1538
|
+
desc: () => t("danger.dd_disk_io")
|
|
1306
1539
|
},
|
|
1307
|
-
{ root: "
|
|
1308
|
-
{ root: "
|
|
1309
|
-
{ root: ["fdisk", "parted"], desc: "\u78C1\u76D8\u5206\u533A\u64CD\u4F5C" },
|
|
1540
|
+
{ root: "shred", desc: () => t("danger.shred") },
|
|
1541
|
+
{ root: ["fdisk", "parted"], desc: () => t("danger.partition") },
|
|
1310
1542
|
{
|
|
1311
1543
|
root: "diskutil",
|
|
1312
1544
|
when: (s) => (args(s)[0] ?? "").toLowerCase().startsWith("erase"),
|
|
1313
|
-
desc: "
|
|
1545
|
+
desc: () => t("danger.partition")
|
|
1314
1546
|
},
|
|
1315
|
-
{ root: "chmod", when: (s) => args(s).includes("777"), desc: "
|
|
1547
|
+
{ root: "chmod", when: (s) => args(s).includes("777"), desc: () => t("danger.chmod_777") },
|
|
1316
1548
|
{
|
|
1317
1549
|
root: "chown",
|
|
1318
1550
|
when: (s) => args(s).some((a) => a === "root" || a.startsWith("root:")),
|
|
1319
|
-
desc:
|
|
1551
|
+
desc: () => t("danger.chown_root")
|
|
1320
1552
|
},
|
|
1321
1553
|
ENCODED_COMMAND_RULE,
|
|
1322
|
-
{ root: ["sudo", "doas"], desc:
|
|
1323
|
-
{ root: "su", when: (s) => args(s).includes("-"), desc:
|
|
1554
|
+
{ root: ["sudo", "doas"], desc: () => t("danger.privilege_posix") },
|
|
1555
|
+
{ root: "su", when: (s) => args(s).includes("-"), desc: () => t("danger.privilege_posix") },
|
|
1324
1556
|
{
|
|
1325
|
-
// 下载后直接执行:上一段是下载工具,这一段是被管道喂进来的 shell
|
|
1326
1557
|
root: SHELLS,
|
|
1327
1558
|
when: (s, prev) => s.pipedInto && prev !== void 0 && FETCHERS.includes(prev.root),
|
|
1328
|
-
desc:
|
|
1559
|
+
desc: () => t("danger.curl_pipe_sh")
|
|
1329
1560
|
},
|
|
1330
1561
|
{
|
|
1331
1562
|
root: INTERPRETERS,
|
|
1332
1563
|
when: (s, prev) => s.pipedInto && prev !== void 0 && FETCHERS.includes(prev.root),
|
|
1333
|
-
desc: "
|
|
1564
|
+
desc: () => t("danger.download_pipe_interpreter")
|
|
1334
1565
|
},
|
|
1335
1566
|
{
|
|
1336
|
-
// 通用「管道进 shell」。放在上面两条之后,让来源明确的先给出更准确的描述
|
|
1337
1567
|
root: SHELLS,
|
|
1338
1568
|
when: (s) => s.pipedInto,
|
|
1339
|
-
desc:
|
|
1569
|
+
desc: () => t("danger.pipe_to_shell")
|
|
1340
1570
|
},
|
|
1341
1571
|
{
|
|
1342
1572
|
root: "git",
|
|
1343
1573
|
when: (s) => args(s)[0] === "push" && args(s).some((a) => a === "--force" || a === "-f" || a === "+"),
|
|
1344
|
-
desc: "
|
|
1574
|
+
desc: () => t("danger.force_push")
|
|
1345
1575
|
},
|
|
1346
1576
|
{
|
|
1347
1577
|
root: "git",
|
|
@@ -1351,77 +1581,80 @@ var POSIX_RULES = [
|
|
|
1351
1581
|
if (sub === "clean") return rest.some((a) => isShortCluster(a) && a.includes("f"));
|
|
1352
1582
|
return false;
|
|
1353
1583
|
},
|
|
1354
|
-
desc: "
|
|
1584
|
+
desc: () => t("danger.discard_changes")
|
|
1355
1585
|
},
|
|
1356
|
-
{ root: ["shutdown", "reboot", "halt", "poweroff"], desc:
|
|
1586
|
+
{ root: ["shutdown", "reboot", "halt", "poweroff"], desc: () => t("danger.shutdown") },
|
|
1357
1587
|
{
|
|
1358
1588
|
root: ["kill", "killall"],
|
|
1359
1589
|
when: (s) => args(s).includes("-9") && args(s).some((a) => a === "-1" || a === "1"),
|
|
1360
|
-
desc:
|
|
1590
|
+
desc: () => t("danger.kill_all")
|
|
1361
1591
|
},
|
|
1362
1592
|
{
|
|
1363
1593
|
root: ["iptables", "ip6tables", "nft", "pfctl"],
|
|
1364
1594
|
when: (s) => args(s).some((a) => a === "-F" || a === "--flush"),
|
|
1365
|
-
desc: "
|
|
1595
|
+
desc: () => t("danger.flush_firewall")
|
|
1366
1596
|
},
|
|
1367
1597
|
{
|
|
1368
1598
|
root: ["launchctl", "systemctl"],
|
|
1369
1599
|
when: (s) => ["disable", "unload"].includes(args(s)[0] ?? ""),
|
|
1370
|
-
desc: "
|
|
1600
|
+
desc: () => t("danger.disable_service")
|
|
1371
1601
|
},
|
|
1372
|
-
{ root: "history", when: (s) => args(s).includes("-c"), desc: "
|
|
1602
|
+
{ root: "history", when: (s) => args(s).includes("-c"), desc: () => t("danger.clear_history") },
|
|
1373
1603
|
{
|
|
1374
1604
|
root: "tee",
|
|
1375
1605
|
when: (s) => args(s).some((a) => a.startsWith("/etc/")),
|
|
1376
|
-
desc:
|
|
1606
|
+
desc: () => t("danger.write_etc")
|
|
1377
1607
|
},
|
|
1378
1608
|
{
|
|
1379
|
-
// 重定向到裸设备 / 历史文件。这条不看 root,任何命令都可能这么写
|
|
1380
1609
|
root: /.*/,
|
|
1381
1610
|
when: (s) => s.redirects.some((r) => isRawDevice(r) || /(^|\/)\.?\w*history$/.test(r)),
|
|
1382
|
-
desc: "
|
|
1611
|
+
desc: () => t("danger.write_device_or_history")
|
|
1383
1612
|
}
|
|
1384
1613
|
];
|
|
1385
1614
|
var WINDOWS_RULES = [
|
|
1386
1615
|
{
|
|
1387
1616
|
root: ["del", "erase", "rd", "rmdir"],
|
|
1388
1617
|
when: hasDangerousWinTarget,
|
|
1389
|
-
desc:
|
|
1618
|
+
desc: () => t("danger.win_rm_system")
|
|
1390
1619
|
},
|
|
1391
1620
|
{
|
|
1392
1621
|
root: "remove-item",
|
|
1393
1622
|
when: (s) => hasWinSwitch(s, "recurse") && hasDangerousWinTarget(s),
|
|
1394
|
-
desc:
|
|
1623
|
+
desc: () => t("danger.ps_rm_system")
|
|
1395
1624
|
},
|
|
1396
1625
|
{
|
|
1397
1626
|
root: "format",
|
|
1398
1627
|
when: (s) => args(s).some((a) => /^[a-z]:$/i.test(a)),
|
|
1399
|
-
desc: "
|
|
1628
|
+
desc: () => t("danger.win_format")
|
|
1400
1629
|
},
|
|
1401
|
-
{ root: "diskpart", desc: "
|
|
1402
|
-
{ root: "cipher", when: (s) => hasWinSwitch(s, "w", true), desc: "
|
|
1630
|
+
{ root: "diskpart", desc: () => t("danger.partition") },
|
|
1631
|
+
{ root: "cipher", when: (s) => hasWinSwitch(s, "w", true), desc: () => t("danger.cipher_wipe") },
|
|
1403
1632
|
{
|
|
1404
1633
|
root: "vssadmin",
|
|
1405
1634
|
when: (s) => (args(s)[0] ?? "").toLowerCase() === "delete",
|
|
1406
|
-
desc: "
|
|
1635
|
+
desc: () => t("danger.delete_shadow_copies")
|
|
1407
1636
|
},
|
|
1408
1637
|
{
|
|
1409
1638
|
root: "wmic",
|
|
1410
1639
|
when: (s) => args(s).map((a) => a.toLowerCase()).join(" ").includes("shadowcopy delete"),
|
|
1411
|
-
desc: "
|
|
1640
|
+
desc: () => t("danger.delete_shadow_copies")
|
|
1412
1641
|
},
|
|
1413
1642
|
{
|
|
1414
1643
|
root: "reg",
|
|
1415
1644
|
when: (s) => (args(s)[0] ?? "").toLowerCase() === "delete" && hasWinSwitch(s, "f", true),
|
|
1416
|
-
desc: "
|
|
1645
|
+
desc: () => t("danger.reg_delete")
|
|
1417
1646
|
},
|
|
1418
|
-
{ root: "bcdedit", desc: "
|
|
1419
|
-
{
|
|
1420
|
-
|
|
1647
|
+
{ root: "bcdedit", desc: () => t("danger.bcdedit") },
|
|
1648
|
+
{
|
|
1649
|
+
root: "sc",
|
|
1650
|
+
when: (s) => (args(s)[0] ?? "").toLowerCase() === "delete",
|
|
1651
|
+
desc: () => t("danger.sc_delete")
|
|
1652
|
+
},
|
|
1653
|
+
{ root: "takeown", when: (s) => hasWinSwitch(s, "r", true), desc: () => t("danger.takeown") },
|
|
1421
1654
|
{
|
|
1422
1655
|
root: "icacls",
|
|
1423
1656
|
when: (s) => hasWinSwitch(s, "grant") && args(s).some((a) => /^(everyone|users)(:|$)/i.test(a)),
|
|
1424
|
-
desc:
|
|
1657
|
+
desc: () => t("danger.icacls_everyone")
|
|
1425
1658
|
},
|
|
1426
1659
|
{
|
|
1427
1660
|
root: "netsh",
|
|
@@ -1429,75 +1662,82 @@ var WINDOWS_RULES = [
|
|
|
1429
1662
|
const lower = args(s).map((a) => a.toLowerCase());
|
|
1430
1663
|
return (lower[0] === "advfirewall" || lower[0] === "firewall") && lower.some((a) => a === "off" || a === "disable");
|
|
1431
1664
|
},
|
|
1432
|
-
desc: "
|
|
1665
|
+
desc: () => t("danger.disable_firewall")
|
|
1433
1666
|
},
|
|
1434
1667
|
{
|
|
1435
1668
|
root: "set-mppreference",
|
|
1436
1669
|
when: (s) => hasWinSwitch(s, "disablerealtimemonitoring"),
|
|
1437
|
-
desc:
|
|
1670
|
+
desc: () => t("danger.disable_defender")
|
|
1438
1671
|
},
|
|
1439
1672
|
{
|
|
1440
1673
|
root: "net",
|
|
1441
1674
|
when: (s) => ["user", "localgroup"].includes((args(s)[0] ?? "").toLowerCase()) && hasWinSwitch(s, "add", true),
|
|
1442
|
-
desc:
|
|
1675
|
+
desc: () => t("danger.add_admin_user")
|
|
1443
1676
|
},
|
|
1444
|
-
{ root: "runas", desc: "
|
|
1677
|
+
{ root: "runas", desc: () => t("danger.privilege_runas") },
|
|
1445
1678
|
{
|
|
1446
1679
|
root: "start-process",
|
|
1447
1680
|
when: (s) => (winSwitchValue(s, "verb") ?? "").toLowerCase() === "runas",
|
|
1448
|
-
desc:
|
|
1681
|
+
desc: () => t("danger.privilege_start_process")
|
|
1449
1682
|
},
|
|
1450
1683
|
{
|
|
1451
1684
|
root: ["iex", "invoke-expression"],
|
|
1452
1685
|
when: (s) => s.pipedInto,
|
|
1453
|
-
desc:
|
|
1686
|
+
desc: () => t("danger.iwr_iex")
|
|
1454
1687
|
},
|
|
1455
1688
|
{
|
|
1456
1689
|
root: ["powershell", "pwsh", "cmd"],
|
|
1457
1690
|
when: (s, prev) => s.pipedInto && prev !== void 0 && ["curl", "wget", "iwr", "irm", "invoke-webrequest", "invoke-restmethod"].includes(prev.root),
|
|
1458
|
-
desc:
|
|
1691
|
+
desc: () => t("danger.download_pipe_shell")
|
|
1459
1692
|
},
|
|
1460
1693
|
{
|
|
1461
1694
|
root: "certutil",
|
|
1462
1695
|
when: (s) => hasWinSwitch(s, "urlcache"),
|
|
1463
|
-
desc:
|
|
1696
|
+
desc: () => t("danger.certutil_download")
|
|
1697
|
+
},
|
|
1698
|
+
{
|
|
1699
|
+
root: "bitsadmin",
|
|
1700
|
+
when: (s) => hasWinSwitch(s, "transfer"),
|
|
1701
|
+
desc: () => t("danger.bitsadmin")
|
|
1464
1702
|
},
|
|
1465
|
-
{ root: "bitsadmin", when: (s) => hasWinSwitch(s, "transfer"), desc: "bitsadmin \u540E\u53F0\u4E0B\u8F7D" },
|
|
1466
1703
|
ENCODED_COMMAND_RULE,
|
|
1467
1704
|
{
|
|
1468
1705
|
root: ["powershell", "pwsh"],
|
|
1469
1706
|
when: (s) => hasWinSwitch(s, "noprofile") || (winSwitchValue(s, "windowstyle") ?? "").toLowerCase() === "hidden" || (winSwitchValue(s, "w") ?? "").toLowerCase() === "hidden",
|
|
1470
|
-
desc:
|
|
1707
|
+
desc: () => t("danger.ps_hidden_window")
|
|
1471
1708
|
},
|
|
1472
1709
|
{
|
|
1473
1710
|
root: ["powershell", "pwsh"],
|
|
1474
1711
|
when: (s) => ["bypass", "unrestricted"].includes(
|
|
1475
1712
|
(winSwitchValue(s, "executionpolicy") ?? "").toLowerCase()
|
|
1476
1713
|
),
|
|
1477
|
-
desc:
|
|
1714
|
+
desc: () => t("danger.ps_bypass_policy")
|
|
1478
1715
|
},
|
|
1479
|
-
{ root: "mshta", desc: "mshta
|
|
1480
|
-
{ root: "rundll32", desc:
|
|
1716
|
+
{ root: "mshta", desc: () => t("danger.mshta") },
|
|
1717
|
+
{ root: "rundll32", desc: () => t("danger.rundll32") },
|
|
1481
1718
|
{
|
|
1482
1719
|
root: "regsvr32",
|
|
1483
1720
|
when: (s) => args(s).some((a) => /^[/-]i:/i.test(a)),
|
|
1484
|
-
desc:
|
|
1721
|
+
desc: () => t("danger.regsvr32")
|
|
1485
1722
|
},
|
|
1486
1723
|
{
|
|
1487
1724
|
root: "shutdown",
|
|
1488
1725
|
when: (s) => hasWinSwitch(s, "s", true) || hasWinSwitch(s, "r", true),
|
|
1489
|
-
desc:
|
|
1726
|
+
desc: () => t("danger.shutdown")
|
|
1490
1727
|
},
|
|
1491
|
-
{ root: ["restart-computer", "stop-computer"], desc:
|
|
1728
|
+
{ root: ["restart-computer", "stop-computer"], desc: () => t("danger.shutdown") },
|
|
1492
1729
|
{
|
|
1493
1730
|
root: "schtasks",
|
|
1494
1731
|
when: (s) => hasWinSwitch(s, "create", true),
|
|
1495
|
-
desc: "
|
|
1732
|
+
desc: () => t("danger.schtasks_create")
|
|
1496
1733
|
},
|
|
1497
|
-
{
|
|
1734
|
+
{
|
|
1735
|
+
root: ["new-scheduledtask", "register-scheduledtask"],
|
|
1736
|
+
desc: () => t("danger.register_scheduled_task")
|
|
1737
|
+
}
|
|
1498
1738
|
];
|
|
1499
1739
|
var WHOLE_STRING_RULES = [
|
|
1500
|
-
{ pattern: /:\s*\(\s*\)\s*\{.*\}\s*;?\s*:/, desc:
|
|
1740
|
+
{ pattern: /:\s*\(\s*\)\s*\{.*\}\s*;?\s*:/, desc: () => t("danger.fork_bomb") }
|
|
1501
1741
|
];
|
|
1502
1742
|
var WINDOWS_TABLE = [...WINDOWS_RULES, ...POSIX_RULES];
|
|
1503
1743
|
function parseForSafety(command, platform10) {
|
|
@@ -1511,7 +1751,7 @@ function checkDangerousCommand(command, opts) {
|
|
|
1511
1751
|
const platform10 = opts?.platform ?? currentPlatform();
|
|
1512
1752
|
const parsed = parseForSafety(command, platform10);
|
|
1513
1753
|
for (const { pattern, desc } of WHOLE_STRING_RULES) {
|
|
1514
|
-
if (pattern.test(parsed.stripped)) return { dangerous: true, desc };
|
|
1754
|
+
if (pattern.test(parsed.stripped)) return { dangerous: true, desc: desc() };
|
|
1515
1755
|
}
|
|
1516
1756
|
const table = platform10 === "win32" ? WINDOWS_TABLE : POSIX_RULES;
|
|
1517
1757
|
for (let i = 0; i < parsed.segments.length; i++) {
|
|
@@ -1520,7 +1760,7 @@ function checkDangerousCommand(command, opts) {
|
|
|
1520
1760
|
for (const rule of table) {
|
|
1521
1761
|
if (!rootMatches(rule.root, seg.root)) continue;
|
|
1522
1762
|
if (rule.when && !rule.when(seg, prev)) continue;
|
|
1523
|
-
return { dangerous: true, desc: rule.desc };
|
|
1763
|
+
return { dangerous: true, desc: rule.desc() };
|
|
1524
1764
|
}
|
|
1525
1765
|
}
|
|
1526
1766
|
return { dangerous: false };
|
|
@@ -1598,29 +1838,31 @@ function isReadOnlyCommand(command) {
|
|
|
1598
1838
|
});
|
|
1599
1839
|
}
|
|
1600
1840
|
var OBFUSCATION_PATTERNS = [
|
|
1601
|
-
{ pattern: /\beval\b/, desc:
|
|
1602
|
-
{ pattern: /\bexec\s+\$/, desc:
|
|
1603
|
-
{ pattern: /\bbase64\s+(-d|--decode)/, desc:
|
|
1604
|
-
{ pattern: /\b(xxd|od)\s+-r/, desc: "
|
|
1605
|
-
{ pattern: /\|\s*(ba|z|k|da)?sh\b/, desc:
|
|
1606
|
-
{ pattern: /\\x[0-9a-f]{2}/i, desc: "
|
|
1607
|
-
{ pattern: /\$\{[^}]*[:#%/][^}]*\}/, desc: "
|
|
1608
|
-
{ pattern: /\bprintf\b[^|;&]*\|/, desc:
|
|
1609
|
-
{ pattern: /\bcurl\b[^|;&]*\|/, desc: "
|
|
1841
|
+
{ pattern: /\beval\b/, desc: () => t("danger.obf_eval") },
|
|
1842
|
+
{ pattern: /\bexec\s+\$/, desc: () => t("danger.obf_exec_var") },
|
|
1843
|
+
{ pattern: /\bbase64\s+(-d|--decode)/, desc: () => t("danger.obf_base64") },
|
|
1844
|
+
{ pattern: /\b(xxd|od)\s+-r/, desc: () => t("danger.obf_hex_restore") },
|
|
1845
|
+
{ pattern: /\|\s*(ba|z|k|da)?sh\b/, desc: () => t("danger.obf_pipe_shell") },
|
|
1846
|
+
{ pattern: /\\x[0-9a-f]{2}/i, desc: () => t("danger.obf_hex_escape") },
|
|
1847
|
+
{ pattern: /\$\{[^}]*[:#%/][^}]*\}/, desc: () => t("danger.obf_var_expansion") },
|
|
1848
|
+
{ pattern: /\bprintf\b[^|;&]*\|/, desc: () => t("danger.obf_printf_pipe") },
|
|
1849
|
+
{ pattern: /\bcurl\b[^|;&]*\|/, desc: () => t("danger.obf_curl_pipe") }
|
|
1610
1850
|
];
|
|
1611
1851
|
function checkObfuscation(command) {
|
|
1612
1852
|
const parsed = parseForSafety(command, currentPlatform());
|
|
1613
|
-
if (parsed.parseError !== void 0)
|
|
1853
|
+
if (parsed.parseError !== void 0) {
|
|
1854
|
+
return t("infra_misc.parse_failed", { reason: parsed.parseError });
|
|
1855
|
+
}
|
|
1614
1856
|
if (parsed.hasSubstitution) {
|
|
1615
|
-
return /`/.test(parsed.stripped) && !/\$\(/.test(parsed.stripped) ? "
|
|
1857
|
+
return /`/.test(parsed.stripped) && !/\$\(/.test(parsed.stripped) ? t("infra_misc.backtick_substitution") : t("infra_misc.dollar_substitution");
|
|
1616
1858
|
}
|
|
1617
1859
|
for (const seg of parsed.segments) {
|
|
1618
1860
|
const interpreter = inlineCodeExecOf(seg);
|
|
1619
|
-
if (interpreter !== null) return
|
|
1861
|
+
if (interpreter !== null) return t("infra_misc.inline_code", { interpreter });
|
|
1620
1862
|
}
|
|
1621
1863
|
const norm = command.replace(/\s+/g, " ").trim();
|
|
1622
1864
|
for (const { pattern, desc } of OBFUSCATION_PATTERNS) {
|
|
1623
|
-
if (pattern.test(norm)) return desc;
|
|
1865
|
+
if (pattern.test(norm)) return desc();
|
|
1624
1866
|
}
|
|
1625
1867
|
return null;
|
|
1626
1868
|
}
|
|
@@ -1705,11 +1947,7 @@ var SYSTEM_READ_PATHS = [
|
|
|
1705
1947
|
"/private/var/db",
|
|
1706
1948
|
"/private/var/folders",
|
|
1707
1949
|
"/private/var/select",
|
|
1708
|
-
// xcode-select 要读它,否则 macOS 自带 python3 起不来
|
|
1709
1950
|
"/private/etc",
|
|
1710
|
-
// macOS 自带的 /usr/bin/python3 是个 Xcode shim:它会去跑 xcodebuild,
|
|
1711
|
-
// 需要读整个 Xcode.app(Info.plist + SharedFrameworks)。
|
|
1712
|
-
// Xcode.app 是公开的应用包,放行读取不泄露任何用户数据。
|
|
1713
1951
|
"/Applications/Xcode.app",
|
|
1714
1952
|
"/Library/Developer"
|
|
1715
1953
|
];
|
|
@@ -1745,9 +1983,6 @@ var SENSITIVE_HOME_PATHS = [
|
|
|
1745
1983
|
"Library/Application Support/Firefox",
|
|
1746
1984
|
"Library/Cookies",
|
|
1747
1985
|
"Library/Messages",
|
|
1748
|
-
// agent 自己的数据:会话、记忆、审批、API key 都在这。
|
|
1749
|
-
// 注意沙箱目录本身就在 ~/.epoch/sandbox 下,所以 buildProfile 必须
|
|
1750
|
-
// 在这条 deny **之后**重新 allow writableDirs,否则连自己的 code.js 都读不到。
|
|
1751
1986
|
".epoch"
|
|
1752
1987
|
];
|
|
1753
1988
|
var SENSITIVE_ABS_PATHS = ["/etc/shadow", "/etc/sudoers", "/private/etc/master.passwd"];
|
|
@@ -1809,7 +2044,6 @@ function networkSection(allowNetwork) {
|
|
|
1809
2044
|
return [
|
|
1810
2045
|
";; ---- \u7F51\u7EDC ----",
|
|
1811
2046
|
"(deny network*)",
|
|
1812
|
-
// 留一个本机 unix socket,否则解析库初始化就可能失败
|
|
1813
2047
|
'(allow network-outbound (literal "/private/var/run/mDNSResponder"))'
|
|
1814
2048
|
];
|
|
1815
2049
|
}
|
|
@@ -1885,7 +2119,6 @@ function isolateWithSeatbelt(command, args2, opts) {
|
|
|
1885
2119
|
]
|
|
1886
2120
|
};
|
|
1887
2121
|
}
|
|
1888
|
-
|
|
1889
2122
|
// src/sandbox/backend.ts
|
|
1890
2123
|
function detectBackend() {
|
|
1891
2124
|
if (platform() === "darwin" && probeSeatbelt()) return "seatbelt";
|
|
@@ -1902,7 +2135,6 @@ function isolate(command, args2, opts) {
|
|
|
1902
2135
|
return null;
|
|
1903
2136
|
}
|
|
1904
2137
|
}
|
|
1905
|
-
|
|
1906
2138
|
// src/sandbox/classify.ts
|
|
1907
2139
|
var DENIAL_SIGNATURES = {
|
|
1908
2140
|
seatbelt: [/Operation not permitted/i, /\bEPERM\b/],
|
|
@@ -1912,13 +2144,10 @@ var DENIAL_SIGNATURES = {
|
|
|
1912
2144
|
var RUNNER_FAILURE_RULES = {
|
|
1913
2145
|
seatbelt: {
|
|
1914
2146
|
fatal: [/^sandbox-exec:/],
|
|
1915
|
-
// sandbox-exec 在 macOS 上已标记 deprecated,它可能(现在或将来)
|
|
1916
|
-
// 往 stderr 打一句提示。那是**信息**不是失败,见方案 46 验收 7
|
|
1917
2147
|
benign: [/^sandbox-exec:.*\bdeprecat/i]
|
|
1918
2148
|
},
|
|
1919
2149
|
bubblewrap: {
|
|
1920
2150
|
fatal: [/^bwrap:/],
|
|
1921
|
-
// bwrap 在部分内核上会为无害的降级打一行提示(比如某个 unshare 跳过了)
|
|
1922
2151
|
benign: [/^bwrap:.*\b(?:skipping|ignoring|deprecat)/i]
|
|
1923
2152
|
},
|
|
1924
2153
|
none: { fatal: [], benign: [] }
|
|
@@ -1979,6 +2208,9 @@ function writableDirsFor(policy) {
|
|
|
1979
2208
|
}
|
|
1980
2209
|
function confine(command, args2, policy) {
|
|
1981
2210
|
const backend = detectBackend();
|
|
2211
|
+
if (policy.enabled === false) {
|
|
2212
|
+
return { confined: false, backend, mode: policy.mode, reason: "config-disabled" };
|
|
2213
|
+
}
|
|
1982
2214
|
if (policy.mode === "danger-full-access") {
|
|
1983
2215
|
return { confined: false, backend, mode: policy.mode, reason: "mode-disabled" };
|
|
1984
2216
|
}
|
|
@@ -2004,40 +2236,299 @@ function confine(command, args2, policy) {
|
|
|
2004
2236
|
runnerFailureRules: RUNNER_FAILURE_RULES[backend]
|
|
2005
2237
|
};
|
|
2006
2238
|
}
|
|
2239
|
+
// src/jobs.ts
|
|
2240
|
+
var bySession2 = /* @__PURE__ */ new Map();
|
|
2241
|
+
var PREFIX = { command: "t", shell: "s", agent: "a" };
|
|
2242
|
+
var counters = { command: 0, shell: 0, agent: 0 };
|
|
2243
|
+
function nextId(kind) {
|
|
2244
|
+
counters[kind] += 1;
|
|
2245
|
+
return `${PREFIX[kind]}${counters[kind]}`;
|
|
2246
|
+
}
|
|
2247
|
+
function bucketOf(sessionId) {
|
|
2248
|
+
const existing = bySession2.get(sessionId);
|
|
2249
|
+
if (existing) return existing;
|
|
2250
|
+
const fresh = /* @__PURE__ */ new Map();
|
|
2251
|
+
bySession2.set(sessionId, fresh);
|
|
2252
|
+
return fresh;
|
|
2253
|
+
}
|
|
2254
|
+
function jobIn(sessionId, id) {
|
|
2255
|
+
return bySession2.get(sessionId)?.get(id);
|
|
2256
|
+
}
|
|
2257
|
+
function append(job, chunk) {
|
|
2258
|
+
job.info.outputBytes += chunk.length;
|
|
2259
|
+
job.buffer += chunk;
|
|
2260
|
+
if (job.buffer.length <= job.spec.maxOutput) return;
|
|
2261
|
+
if (job.spill === void 0) {
|
|
2262
|
+
job.spill = openArtifact(job.spec.artifactsDir, `${job.spec.artifactLabel}-${job.info.id}`, ".txt") ?? null;
|
|
2263
|
+
job.spill?.append(job.buffer);
|
|
2264
|
+
if (job.spill) job.info.artifact = job.spill.path;
|
|
2265
|
+
} else {
|
|
2266
|
+
job.spill?.append(chunk);
|
|
2267
|
+
}
|
|
2268
|
+
const drop = job.buffer.length - job.spec.maxOutput;
|
|
2269
|
+
job.buffer = job.buffer.slice(drop);
|
|
2270
|
+
job.droppedBytes += drop;
|
|
2271
|
+
job.info.truncated = true;
|
|
2272
|
+
}
|
|
2273
|
+
function endJob(job, status, exitCode) {
|
|
2274
|
+
if (job.info.status !== "running") return;
|
|
2275
|
+
job.info.status = status;
|
|
2276
|
+
if (exitCode !== void 0) job.info.exitCode = exitCode;
|
|
2277
|
+
job.info.endedAt = Date.now();
|
|
2278
|
+
job.spill?.close();
|
|
2279
|
+
job.settle();
|
|
2280
|
+
}
|
|
2281
|
+
function registerJob(spec) {
|
|
2282
|
+
const now = Date.now();
|
|
2283
|
+
let settle;
|
|
2284
|
+
const done = new Promise((resolve5) => {
|
|
2285
|
+
settle = resolve5;
|
|
2286
|
+
});
|
|
2287
|
+
const job = {
|
|
2288
|
+
spec,
|
|
2289
|
+
info: {
|
|
2290
|
+
id: nextId(spec.kind),
|
|
2291
|
+
kind: spec.kind,
|
|
2292
|
+
label: spec.label,
|
|
2293
|
+
...spec.cwd ? { cwd: spec.cwd } : {},
|
|
2294
|
+
pid: 0,
|
|
2295
|
+
status: "running",
|
|
2296
|
+
startedAt: now,
|
|
2297
|
+
lastActivity: now,
|
|
2298
|
+
outputBytes: 0,
|
|
2299
|
+
truncated: false
|
|
2300
|
+
},
|
|
2301
|
+
buffer: "",
|
|
2302
|
+
droppedBytes: 0,
|
|
2303
|
+
done,
|
|
2304
|
+
settle
|
|
2305
|
+
};
|
|
2306
|
+
bucketOf(spec.sessionId).set(job.info.id, job);
|
|
2307
|
+
return {
|
|
2308
|
+
id: job.info.id,
|
|
2309
|
+
detail: spec.detail,
|
|
2310
|
+
info: () => ({ ...job.info }),
|
|
2311
|
+
status: () => job.info.status,
|
|
2312
|
+
setPid: (pid) => {
|
|
2313
|
+
job.info.pid = pid;
|
|
2314
|
+
},
|
|
2315
|
+
append: (chunk) => append(job, chunk),
|
|
2316
|
+
touch: () => {
|
|
2317
|
+
job.info.lastActivity = Date.now();
|
|
2318
|
+
},
|
|
2319
|
+
cursor: () => job.info.outputBytes,
|
|
2320
|
+
finish: (status, exitCode) => endJob(job, status, exitCode)
|
|
2321
|
+
};
|
|
2322
|
+
}
|
|
2323
|
+
function listJobs(sessionId, kind) {
|
|
2324
|
+
const bucket = bySession2.get(sessionId);
|
|
2325
|
+
if (!bucket) return [];
|
|
2326
|
+
const all = [...bucket.values()].map((job) => ({ ...job.info }));
|
|
2327
|
+
return kind ? all.filter((info) => info.kind === kind) : all;
|
|
2328
|
+
}
|
|
2329
|
+
function getJob(sessionId, id) {
|
|
2330
|
+
const job = jobIn(sessionId, id);
|
|
2331
|
+
return job ? { ...job.info } : void 0;
|
|
2332
|
+
}
|
|
2333
|
+
function jobDetail(sessionId, id) {
|
|
2334
|
+
return jobIn(sessionId, id)?.spec.detail;
|
|
2335
|
+
}
|
|
2336
|
+
function allJobs(kind) {
|
|
2337
|
+
const out = [];
|
|
2338
|
+
for (const [sessionId, bucket] of bySession2) {
|
|
2339
|
+
for (const job of bucket.values()) {
|
|
2340
|
+
if (kind && job.info.kind !== kind) continue;
|
|
2341
|
+
out.push({ sessionId, info: { ...job.info } });
|
|
2342
|
+
}
|
|
2343
|
+
}
|
|
2344
|
+
return out;
|
|
2345
|
+
}
|
|
2346
|
+
function readJob(sessionId, id, since, maxChunk) {
|
|
2347
|
+
const job = jobIn(sessionId, id);
|
|
2348
|
+
return job ? sliceFrom(job, since, maxChunk) : void 0;
|
|
2349
|
+
}
|
|
2350
|
+
function sliceFrom(job, since, maxChunk) {
|
|
2351
|
+
const from = Math.max(since, job.droppedBytes);
|
|
2352
|
+
const missed = Math.max(0, job.droppedBytes - since);
|
|
2353
|
+
const slice = job.buffer.slice(from - job.droppedBytes);
|
|
2354
|
+
const output = slice.slice(0, maxChunk);
|
|
2355
|
+
return {
|
|
2356
|
+
info: { ...job.info },
|
|
2357
|
+
output,
|
|
2358
|
+
nextCursor: from + output.length,
|
|
2359
|
+
missed,
|
|
2360
|
+
hasMore: output.length < slice.length
|
|
2361
|
+
};
|
|
2362
|
+
}
|
|
2363
|
+
function touchJob(sessionId, id) {
|
|
2364
|
+
const job = jobIn(sessionId, id);
|
|
2365
|
+
if (job) job.info.lastActivity = Date.now();
|
|
2366
|
+
}
|
|
2367
|
+
function appendJob(sessionId, id, chunk) {
|
|
2368
|
+
const job = jobIn(sessionId, id);
|
|
2369
|
+
if (job) append(job, chunk);
|
|
2370
|
+
}
|
|
2371
|
+
async function waitJob(sessionId, id, timeoutMs) {
|
|
2372
|
+
const job = jobIn(sessionId, id);
|
|
2373
|
+
if (!job) return void 0;
|
|
2374
|
+
const snapshot = () => ({ ...job.info });
|
|
2375
|
+
if (job.info.status !== "running") {
|
|
2376
|
+
return { info: snapshot(), timedOut: false, neverEnds: false };
|
|
2377
|
+
}
|
|
2378
|
+
if (!job.spec.terminates) return { info: snapshot(), timedOut: false, neverEnds: true };
|
|
2379
|
+
let timer;
|
|
2380
|
+
const timeout = new Promise((resolve5) => {
|
|
2381
|
+
timer = setTimeout(() => resolve5("timeout"), timeoutMs);
|
|
2382
|
+
timer.unref?.();
|
|
2383
|
+
});
|
|
2384
|
+
try {
|
|
2385
|
+
const winner = await Promise.race([job.done.then(() => "done"), timeout]);
|
|
2386
|
+
return { info: snapshot(), timedOut: winner === "timeout", neverEnds: false };
|
|
2387
|
+
} finally {
|
|
2388
|
+
if (timer) clearTimeout(timer);
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
function stopJob(sessionId, id, status) {
|
|
2392
|
+
const job = jobIn(sessionId, id);
|
|
2393
|
+
if (!job || job.info.status !== "running") return Promise.resolve(false);
|
|
2394
|
+
const stop = job.spec.stop;
|
|
2395
|
+
endJob(job, status ?? job.spec.stoppedStatus ?? "killed");
|
|
2396
|
+
return stop().then(() => true);
|
|
2397
|
+
}
|
|
2398
|
+
function rekeyJobs(from, to, kinds) {
|
|
2399
|
+
if (from === to) return;
|
|
2400
|
+
const moving = bySession2.get(from);
|
|
2401
|
+
if (!moving) return;
|
|
2402
|
+
const wanted = [...moving].filter(([, job]) => kinds.includes(job.info.kind));
|
|
2403
|
+
if (wanted.length === 0) return;
|
|
2404
|
+
for (const [id] of wanted) moving.delete(id);
|
|
2405
|
+
if (moving.size === 0) bySession2.delete(from);
|
|
2406
|
+
const target = bucketOf(to);
|
|
2407
|
+
for (const [id, job] of wanted) {
|
|
2408
|
+
job.spec.sessionId = to;
|
|
2409
|
+
target.set(id, job);
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
function clearAllJobs(kinds) {
|
|
2413
|
+
for (const [sessionId, bucket] of bySession2) {
|
|
2414
|
+
for (const [id, job] of bucket) {
|
|
2415
|
+
if (kinds && !kinds.includes(job.info.kind)) continue;
|
|
2416
|
+
job.spill?.close();
|
|
2417
|
+
job.settle();
|
|
2418
|
+
bucket.delete(id);
|
|
2419
|
+
}
|
|
2420
|
+
if (bucket.size === 0) bySession2.delete(sessionId);
|
|
2421
|
+
}
|
|
2422
|
+
for (const kind of kinds ?? ["command", "shell", "agent"]) counters[kind] = 0;
|
|
2423
|
+
}
|
|
2007
2424
|
var EXEC_PATH_AS_NODE_ENV = {
|
|
2008
2425
|
ELECTRON_RUN_AS_NODE: "1"
|
|
2009
2426
|
};
|
|
2010
|
-
var
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
2015
|
-
|
|
2016
|
-
|
|
2017
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
2021
|
-
|
|
2022
|
-
|
|
2023
|
-
|
|
2427
|
+
var owners = /* @__PURE__ */ new Map();
|
|
2428
|
+
var bySession3 = /* @__PURE__ */ new Map();
|
|
2429
|
+
var TableImpl = class {
|
|
2430
|
+
constructor(fallback) {
|
|
2431
|
+
this.fallback = fallback;
|
|
2432
|
+
}
|
|
2433
|
+
fallback;
|
|
2434
|
+
entries = /* @__PURE__ */ new Map();
|
|
2435
|
+
sessions = /* @__PURE__ */ new Set();
|
|
2436
|
+
released = false;
|
|
2437
|
+
adopt(pid, entry) {
|
|
2438
|
+
this.entries.set(pid, entry);
|
|
2439
|
+
owners.set(pid, this);
|
|
2440
|
+
}
|
|
2441
|
+
forget(pid) {
|
|
2442
|
+
this.entries.delete(pid);
|
|
2443
|
+
if (owners.get(pid) === this) owners.delete(pid);
|
|
2444
|
+
}
|
|
2445
|
+
take(pid) {
|
|
2446
|
+
const entry = this.entries.get(pid);
|
|
2447
|
+
this.forget(pid);
|
|
2448
|
+
return entry;
|
|
2449
|
+
}
|
|
2450
|
+
start(opts) {
|
|
2451
|
+
const child = spawn(opts.file, [...opts.args], {
|
|
2452
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
2453
|
+
detached: false,
|
|
2454
|
+
...opts.cwd ? { cwd: opts.cwd } : {},
|
|
2455
|
+
...opts.spawnOptions
|
|
2024
2456
|
});
|
|
2025
|
-
|
|
2026
|
-
|
|
2027
|
-
|
|
2028
|
-
|
|
2029
|
-
|
|
2030
|
-
|
|
2031
|
-
|
|
2032
|
-
|
|
2033
|
-
|
|
2034
|
-
|
|
2035
|
-
|
|
2036
|
-
|
|
2457
|
+
const pid = child.pid ?? 0;
|
|
2458
|
+
if (pid > 0) this.adopt(pid, { kind: "child", child });
|
|
2459
|
+
const pump = (stream) => {
|
|
2460
|
+
child[stream]?.on("data", (buf) => {
|
|
2461
|
+
opts.onOutput?.(buf.toString("utf8"), stream);
|
|
2462
|
+
});
|
|
2463
|
+
};
|
|
2464
|
+
pump("stdout");
|
|
2465
|
+
pump("stderr");
|
|
2466
|
+
child.on("exit", (code, signal) => {
|
|
2467
|
+
this.forget(pid);
|
|
2468
|
+
opts.onExit?.(code, signal);
|
|
2469
|
+
});
|
|
2470
|
+
child.on("error", () => {
|
|
2471
|
+
this.forget(pid);
|
|
2472
|
+
opts.onExit?.(null, null);
|
|
2473
|
+
});
|
|
2474
|
+
return { pid, child };
|
|
2475
|
+
}
|
|
2476
|
+
trackForeign(pid, kill) {
|
|
2477
|
+
if (pid <= 0) return () => {
|
|
2478
|
+
};
|
|
2479
|
+
this.adopt(pid, { kind: "foreign", kill });
|
|
2480
|
+
return () => {
|
|
2481
|
+
if (this.entries.get(pid)?.kind === "foreign") this.forget(pid);
|
|
2482
|
+
};
|
|
2483
|
+
}
|
|
2484
|
+
count() {
|
|
2485
|
+
return this.entries.size;
|
|
2486
|
+
}
|
|
2487
|
+
async killAll() {
|
|
2488
|
+
const pids = [...this.entries.keys()];
|
|
2489
|
+
await Promise.all(pids.map((pid) => killTrackedProcess(pid)));
|
|
2490
|
+
}
|
|
2491
|
+
claim(sessionId) {
|
|
2492
|
+
if (this.fallback || !sessionId) return;
|
|
2493
|
+
bySession3.set(sessionId, this);
|
|
2494
|
+
this.sessions.add(sessionId);
|
|
2495
|
+
}
|
|
2496
|
+
async release() {
|
|
2497
|
+
if (this.fallback || this.released) return;
|
|
2498
|
+
this.released = true;
|
|
2499
|
+
for (const sid of this.sessions) if (bySession3.get(sid) === this) bySession3.delete(sid);
|
|
2500
|
+
this.sessions.clear();
|
|
2501
|
+
const last = --fallbackRefs <= 0;
|
|
2502
|
+
if (last) fallbackRefs = 0;
|
|
2503
|
+
await Promise.all(last ? [this.killAll(), fallbackTable.killAll()] : [this.killAll()]);
|
|
2504
|
+
}
|
|
2505
|
+
};
|
|
2506
|
+
var fallbackTable = new TableImpl(true);
|
|
2507
|
+
var fallbackRefs = 0;
|
|
2508
|
+
function createProcessTable() {
|
|
2509
|
+
fallbackRefs++;
|
|
2510
|
+
return new TableImpl(false);
|
|
2511
|
+
}
|
|
2512
|
+
function processTableFor(sessionId) {
|
|
2513
|
+
if (!sessionId) return fallbackTable;
|
|
2514
|
+
return bySession3.get(sessionId) ?? fallbackTable;
|
|
2515
|
+
}
|
|
2516
|
+
function processFallbackTable() {
|
|
2517
|
+
return fallbackTable;
|
|
2518
|
+
}
|
|
2519
|
+
function startLongLivedProcess(opts) {
|
|
2520
|
+
return fallbackTable.start(opts);
|
|
2521
|
+
}
|
|
2522
|
+
function trackForeignProcess(pid, kill) {
|
|
2523
|
+
return fallbackTable.trackForeign(pid, kill);
|
|
2037
2524
|
}
|
|
2038
2525
|
async function killTrackedProcess(pid) {
|
|
2039
|
-
const
|
|
2040
|
-
|
|
2526
|
+
const entry = owners.get(pid)?.take(pid);
|
|
2527
|
+
if (entry?.kind === "foreign") {
|
|
2528
|
+
await entry.kill();
|
|
2529
|
+
return;
|
|
2530
|
+
}
|
|
2531
|
+
const child = entry?.child;
|
|
2041
2532
|
await killProcessTree({
|
|
2042
2533
|
pid,
|
|
2043
2534
|
detached: false,
|
|
@@ -2046,10 +2537,10 @@ async function killTrackedProcess(pid) {
|
|
|
2046
2537
|
});
|
|
2047
2538
|
}
|
|
2048
2539
|
function trackedProcessCount() {
|
|
2049
|
-
return
|
|
2540
|
+
return owners.size;
|
|
2050
2541
|
}
|
|
2051
2542
|
async function killAllTrackedProcesses() {
|
|
2052
|
-
const pids = [...
|
|
2543
|
+
const pids = [...owners.keys()];
|
|
2053
2544
|
await Promise.all(pids.map((pid) => killTrackedProcess(pid)));
|
|
2054
2545
|
}
|
|
2055
2546
|
function within(root, abs) {
|
|
@@ -2063,7 +2554,6 @@ function isInWorkspace(target, workDir, extraRoots = []) {
|
|
|
2063
2554
|
if (within(root, abs)) return true;
|
|
2064
2555
|
return extraRoots.some((r) => within(resolve$1(r), abs));
|
|
2065
2556
|
}
|
|
2066
|
-
|
|
2067
2557
|
// src/schema.ts
|
|
2068
2558
|
function parseLenient(schema, input, fallback) {
|
|
2069
2559
|
const result = schema.safeParse(input);
|
|
@@ -2075,8 +2565,7 @@ function parseStrict(schema, input, label) {
|
|
|
2075
2565
|
if (result.success) return result.data;
|
|
2076
2566
|
const issues = toIssues(result.error.issues);
|
|
2077
2567
|
const detail = issues.map((i) => ` - ${i.path}: ${i.message}`).join("\n");
|
|
2078
|
-
throw new Error(
|
|
2079
|
-
${detail}`);
|
|
2568
|
+
throw new Error(t("infra_misc.schema_failed", { path: label, count: issues.length, detail }));
|
|
2080
2569
|
}
|
|
2081
2570
|
var DEFAULT_ISSUE_LIMIT = 8;
|
|
2082
2571
|
function formatIssues(label, issues, limit = DEFAULT_ISSUE_LIMIT) {
|
|
@@ -2086,7 +2575,7 @@ function issueDetails(issues, limit = DEFAULT_ISSUE_LIMIT) {
|
|
|
2086
2575
|
if (issues.length === 0) return [];
|
|
2087
2576
|
const shown = issues.slice(0, limit).map((i) => `${i.path} \u2014\u2014 ${i.message}`);
|
|
2088
2577
|
const rest = issues.length - shown.length;
|
|
2089
|
-
if (rest > 0) shown.push(
|
|
2578
|
+
if (rest > 0) shown.push(t("infra_misc.schema_more", { count: rest }));
|
|
2090
2579
|
return shown;
|
|
2091
2580
|
}
|
|
2092
2581
|
function unknownKeyIssues(known, input, prefix = "") {
|
|
@@ -2094,7 +2583,7 @@ function unknownKeyIssues(known, input, prefix = "") {
|
|
|
2094
2583
|
const knownSet = new Set(known);
|
|
2095
2584
|
return Object.keys(input).filter((key) => !knownSet.has(key)).map((key) => ({
|
|
2096
2585
|
path: prefix ? `${prefix}.${key}` : key,
|
|
2097
|
-
message: "
|
|
2586
|
+
message: t("infra_misc.schema_unknown_key")
|
|
2098
2587
|
}));
|
|
2099
2588
|
}
|
|
2100
2589
|
var MAX_NEST_DEPTH = 5;
|
|
@@ -2129,7 +2618,6 @@ function dedupe(issues) {
|
|
|
2129
2618
|
return true;
|
|
2130
2619
|
});
|
|
2131
2620
|
}
|
|
2132
|
-
|
|
2133
2621
|
// src/async.ts
|
|
2134
2622
|
async function withTimeout(promise, ms, fallback) {
|
|
2135
2623
|
let timer;
|
|
@@ -2158,151 +2646,6 @@ function safeInit(name, factory, diags) {
|
|
|
2158
2646
|
return null;
|
|
2159
2647
|
}
|
|
2160
2648
|
}
|
|
2161
|
-
var LANGS = ["zh", "en"];
|
|
2162
|
-
var DEFAULT_LANG = "zh";
|
|
2163
|
-
function isLang(value) {
|
|
2164
|
-
return LANGS.includes(value);
|
|
2165
|
-
}
|
|
2166
|
-
var catalogs = /* @__PURE__ */ new Map();
|
|
2167
|
-
var pending = [];
|
|
2168
|
-
var localesDirCache;
|
|
2169
|
-
var currentLangValue;
|
|
2170
|
-
function localesDir() {
|
|
2171
|
-
if (localesDirCache !== void 0) return localesDirCache ?? void 0;
|
|
2172
|
-
const override = process.env.EPOCH_LOCALES_DIR;
|
|
2173
|
-
if (override) {
|
|
2174
|
-
localesDirCache = existsSync(override) ? override : null;
|
|
2175
|
-
return localesDirCache ?? void 0;
|
|
2176
|
-
}
|
|
2177
|
-
let dir = dirname(fileURLToPath(import.meta.url));
|
|
2178
|
-
for (let depth = 0; depth < 8; depth += 1) {
|
|
2179
|
-
if (basename(dir) === "node_modules") break;
|
|
2180
|
-
const candidate = join(dir, "locales");
|
|
2181
|
-
if (existsSync(join(candidate, `${DEFAULT_LANG}.yaml`))) {
|
|
2182
|
-
localesDirCache = candidate;
|
|
2183
|
-
return candidate;
|
|
2184
|
-
}
|
|
2185
|
-
const parent = dirname(dir);
|
|
2186
|
-
if (parent === dir) break;
|
|
2187
|
-
dir = parent;
|
|
2188
|
-
}
|
|
2189
|
-
localesDirCache = null;
|
|
2190
|
-
return void 0;
|
|
2191
|
-
}
|
|
2192
|
-
function flatten(value, prefix, out) {
|
|
2193
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) return;
|
|
2194
|
-
for (const [key, child] of Object.entries(value)) {
|
|
2195
|
-
const path = prefix ? `${prefix}.${key}` : key;
|
|
2196
|
-
if (typeof child === "string") out[path] = child;
|
|
2197
|
-
else flatten(child, path, out);
|
|
2198
|
-
}
|
|
2199
|
-
}
|
|
2200
|
-
function loadCatalog(lang) {
|
|
2201
|
-
const cached2 = catalogs.get(lang);
|
|
2202
|
-
if (cached2) return cached2;
|
|
2203
|
-
const result = readCatalog(lang);
|
|
2204
|
-
catalogs.set(lang, result);
|
|
2205
|
-
return result;
|
|
2206
|
-
}
|
|
2207
|
-
function readCatalog(lang) {
|
|
2208
|
-
const dir = localesDir();
|
|
2209
|
-
if (!dir) {
|
|
2210
|
-
pending.push({ code: "locales-missing", render: () => t("i18n.locales_missing") });
|
|
2211
|
-
return { entries: {}, error: "\u627E\u4E0D\u5230 locales \u76EE\u5F55" };
|
|
2212
|
-
}
|
|
2213
|
-
const file = `${lang}.yaml`;
|
|
2214
|
-
const path = join(dir, file);
|
|
2215
|
-
if (!existsSync(path)) {
|
|
2216
|
-
pending.push({
|
|
2217
|
-
code: "catalog-missing",
|
|
2218
|
-
render: () => t("i18n.catalog_missing", { file, lang })
|
|
2219
|
-
});
|
|
2220
|
-
return { entries: {}, error: `${path} \u4E0D\u5B58\u5728` };
|
|
2221
|
-
}
|
|
2222
|
-
let parsed;
|
|
2223
|
-
try {
|
|
2224
|
-
parsed = load(readFileSync(path, "utf-8"));
|
|
2225
|
-
} catch (err) {
|
|
2226
|
-
const reason = (err instanceof Error ? err.message : String(err)).split("\n")[0] ?? "";
|
|
2227
|
-
pending.push({
|
|
2228
|
-
code: "catalog-invalid",
|
|
2229
|
-
render: () => t("i18n.catalog_invalid", { file, reason })
|
|
2230
|
-
});
|
|
2231
|
-
return { entries: {}, error: reason };
|
|
2232
|
-
}
|
|
2233
|
-
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2234
|
-
const reason = "\u9876\u5C42\u4E0D\u662F\u952E\u503C\u5BF9";
|
|
2235
|
-
pending.push({
|
|
2236
|
-
code: "catalog-invalid",
|
|
2237
|
-
render: () => t("i18n.catalog_invalid", { file, reason })
|
|
2238
|
-
});
|
|
2239
|
-
return { entries: {}, error: reason };
|
|
2240
|
-
}
|
|
2241
|
-
const entries = {};
|
|
2242
|
-
flatten(parsed, "", entries);
|
|
2243
|
-
return { entries };
|
|
2244
|
-
}
|
|
2245
|
-
function resetI18n() {
|
|
2246
|
-
catalogs.clear();
|
|
2247
|
-
pending = [];
|
|
2248
|
-
localesDirCache = void 0;
|
|
2249
|
-
currentLangValue = void 0;
|
|
2250
|
-
}
|
|
2251
|
-
var PLACEHOLDER = /\{(\w+)\}/g;
|
|
2252
|
-
function placeholdersOf(text) {
|
|
2253
|
-
const found = [];
|
|
2254
|
-
for (const match of text.matchAll(PLACEHOLDER)) {
|
|
2255
|
-
const name = match[1];
|
|
2256
|
-
if (name && !found.includes(name)) found.push(name);
|
|
2257
|
-
}
|
|
2258
|
-
return found;
|
|
2259
|
-
}
|
|
2260
|
-
function t(key, vars, lang) {
|
|
2261
|
-
const target = lang ?? currentLang();
|
|
2262
|
-
const text = loadCatalog(target).entries[key] ?? loadCatalog(DEFAULT_LANG).entries[key] ?? key;
|
|
2263
|
-
if (!vars) return text;
|
|
2264
|
-
return text.replace(PLACEHOLDER, (whole, name) => {
|
|
2265
|
-
const value = vars[name];
|
|
2266
|
-
return value === void 0 ? whole : String(value);
|
|
2267
|
-
});
|
|
2268
|
-
}
|
|
2269
|
-
function currentLang() {
|
|
2270
|
-
currentLangValue ??= resolveLang();
|
|
2271
|
-
return currentLangValue;
|
|
2272
|
-
}
|
|
2273
|
-
function setLang(lang) {
|
|
2274
|
-
currentLangValue = lang;
|
|
2275
|
-
}
|
|
2276
|
-
function systemLocaleSignals() {
|
|
2277
|
-
const env = process.env;
|
|
2278
|
-
const signals = [env.LC_ALL, env.LC_MESSAGES, env.LANG, env.LANGUAGE].filter(
|
|
2279
|
-
(v) => typeof v === "string" && v.length > 0
|
|
2280
|
-
);
|
|
2281
|
-
try {
|
|
2282
|
-
signals.push(new Intl.DateTimeFormat().resolvedOptions().locale);
|
|
2283
|
-
} catch {
|
|
2284
|
-
}
|
|
2285
|
-
return signals;
|
|
2286
|
-
}
|
|
2287
|
-
function langFromLocale(tag) {
|
|
2288
|
-
const primary = tag.split(/[.@]/)[0]?.replace("_", "-").toLowerCase() ?? "";
|
|
2289
|
-
if (!primary || primary === "c" || primary === "posix") return void 0;
|
|
2290
|
-
if (primary === "zh" || primary.startsWith("zh-")) return "zh";
|
|
2291
|
-
return "en";
|
|
2292
|
-
}
|
|
2293
|
-
function resolveLang(configured, signals) {
|
|
2294
|
-
const fromEnv = process.env.EPOCH_LANGUAGE;
|
|
2295
|
-
if (fromEnv && isLang(fromEnv)) return fromEnv;
|
|
2296
|
-
if (configured && isLang(configured)) return configured;
|
|
2297
|
-
for (const tag of signals ?? systemLocaleSignals()) {
|
|
2298
|
-
const lang = langFromLocale(tag);
|
|
2299
|
-
if (lang) return lang;
|
|
2300
|
-
}
|
|
2301
|
-
return DEFAULT_LANG;
|
|
2302
|
-
}
|
|
2303
|
-
function i18nDiagnostics() {
|
|
2304
|
-
return pending.map((item) => ({ code: item.code, detail: item.render() }));
|
|
2305
|
-
}
|
|
2306
2649
|
var SECRET_SERVICE = "epoch-agent";
|
|
2307
2650
|
function secretServiceFor(homeDir) {
|
|
2308
2651
|
if (homeDir === join(homedir(), ".epoch")) return SECRET_SERVICE;
|
|
@@ -2320,7 +2663,7 @@ var OP_TIMEOUT_MS = 1e4;
|
|
|
2320
2663
|
var BACKEND_ENV_VAR = "EPOCH_SECRET_BACKEND";
|
|
2321
2664
|
function assertValidSecretName(name) {
|
|
2322
2665
|
if (!/^[A-Za-z0-9._:-]{1,128}$/.test(name)) {
|
|
2323
|
-
throw new Error(
|
|
2666
|
+
throw new Error(t("secret.bad_name", { name }));
|
|
2324
2667
|
}
|
|
2325
2668
|
}
|
|
2326
2669
|
function secretIndexPath(homeDir) {
|
|
@@ -2329,7 +2672,6 @@ function secretIndexPath(homeDir) {
|
|
|
2329
2672
|
function dpapiStorePath(homeDir) {
|
|
2330
2673
|
return join(homeDir, "secrets.dpapi.json");
|
|
2331
2674
|
}
|
|
2332
|
-
|
|
2333
2675
|
// src/secret/catalog.ts
|
|
2334
2676
|
var FILE_VERSION = 1;
|
|
2335
2677
|
var SecretCatalog = class {
|
|
@@ -2338,7 +2680,6 @@ var SecretCatalog = class {
|
|
|
2338
2680
|
}
|
|
2339
2681
|
filePath;
|
|
2340
2682
|
cache = null;
|
|
2341
|
-
/** 已登记的名字,顺序稳定(排序过),方便 `epoch config secret list` 输出稳定 */
|
|
2342
2683
|
read() {
|
|
2343
2684
|
if (this.cache) return [...this.cache];
|
|
2344
2685
|
this.cache = parse(this.filePath);
|
|
@@ -2354,22 +2695,14 @@ var SecretCatalog = class {
|
|
|
2354
2695
|
if (!names.includes(name)) return;
|
|
2355
2696
|
this.write(names.filter((n) => n !== name));
|
|
2356
2697
|
}
|
|
2357
|
-
/** 整体替换。预取自愈用:把「钥匙串里真读得出来」的那份写回去 */
|
|
2358
2698
|
replace(names) {
|
|
2359
2699
|
const next = [...new Set(names)].sort();
|
|
2360
2700
|
if (sameList(this.read(), next)) return;
|
|
2361
2701
|
this.write(next);
|
|
2362
2702
|
}
|
|
2363
|
-
/** 仅供测试:丢掉内存缓存,强制重新读盘 */
|
|
2364
2703
|
invalidate() {
|
|
2365
2704
|
this.cache = null;
|
|
2366
2705
|
}
|
|
2367
|
-
/**
|
|
2368
|
-
* 原子写 + 建文件时就 0600。
|
|
2369
|
-
*
|
|
2370
|
-
* mode 在**创建时**生效,写完再 chmod 会留 TOCTOU 窗口
|
|
2371
|
-
* [对标 hermes mcp_oauth.py:388 的 `_write_secure_json`]。
|
|
2372
|
-
*/
|
|
2373
2706
|
write(names) {
|
|
2374
2707
|
const sorted = [...names].sort();
|
|
2375
2708
|
const file = { version: FILE_VERSION, names: sorted };
|
|
@@ -2384,7 +2717,11 @@ var SecretCatalog = class {
|
|
|
2384
2717
|
} catch (err) {
|
|
2385
2718
|
rmSync(tmp, { force: true });
|
|
2386
2719
|
throw new Error(
|
|
2387
|
-
|
|
2720
|
+
t("secret.op_failed", {
|
|
2721
|
+
op: t("secret.op_write"),
|
|
2722
|
+
name: t("secret.catalog_label"),
|
|
2723
|
+
detail: `${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2724
|
+
})
|
|
2388
2725
|
);
|
|
2389
2726
|
}
|
|
2390
2727
|
this.cache = sorted;
|
|
@@ -2411,10 +2748,8 @@ function run(file, args2, opts) {
|
|
|
2411
2748
|
args2,
|
|
2412
2749
|
{
|
|
2413
2750
|
timeout: opts.timeoutMs,
|
|
2414
|
-
// 凭据值可能几 KB(OAuth token 串),默认 1MB 够用但显式写出来更清楚
|
|
2415
2751
|
maxBuffer: 1024 * 1024,
|
|
2416
2752
|
encoding: "utf-8",
|
|
2417
|
-
// Windows 上别弹黑框:PowerShell 冷启动会闪一个控制台窗口
|
|
2418
2753
|
...WINDOWS_HIDE_FLAGS
|
|
2419
2754
|
},
|
|
2420
2755
|
(err, stdout, stderr) => {
|
|
@@ -2444,12 +2779,15 @@ function classify(err) {
|
|
|
2444
2779
|
};
|
|
2445
2780
|
}
|
|
2446
2781
|
function describeFailure(file, r) {
|
|
2447
|
-
if (r.missing) return
|
|
2448
|
-
if (r.timedOut) return
|
|
2782
|
+
if (r.missing) return t("secret.exec_missing", { file });
|
|
2783
|
+
if (r.timedOut) return t("secret.exec_timeout", { file });
|
|
2449
2784
|
const detail = r.stderr.trim().split("\n")[0]?.slice(0, 200);
|
|
2450
|
-
return
|
|
2785
|
+
return t("secret.exec_exit", {
|
|
2786
|
+
file,
|
|
2787
|
+
code: r.code ?? -1,
|
|
2788
|
+
detail: detail ? t("secret.exec_exit_detail", { detail }) : ""
|
|
2789
|
+
});
|
|
2451
2790
|
}
|
|
2452
|
-
|
|
2453
2791
|
// src/secret/dpapi.ts
|
|
2454
2792
|
var FILE_VERSION2 = 1;
|
|
2455
2793
|
var SHELLS2 = ["powershell.exe", "pwsh"];
|
|
@@ -2476,20 +2814,38 @@ var DpapiStore = class {
|
|
|
2476
2814
|
shell;
|
|
2477
2815
|
backend = "dpapi";
|
|
2478
2816
|
encrypted = true;
|
|
2479
|
-
detail
|
|
2817
|
+
get detail() {
|
|
2818
|
+
return t("secret.backend_dpapi");
|
|
2819
|
+
}
|
|
2480
2820
|
cache = null;
|
|
2481
2821
|
async get(name) {
|
|
2482
2822
|
assertValidSecretName(name);
|
|
2483
2823
|
const blob = this.load().secrets[name];
|
|
2484
2824
|
if (!blob) return void 0;
|
|
2485
2825
|
const r = await this.ps(UNPROTECT_SCRIPT, blob);
|
|
2486
|
-
if (r.code !== 0)
|
|
2826
|
+
if (r.code !== 0) {
|
|
2827
|
+
throw new Error(
|
|
2828
|
+
t("secret.op_failed", {
|
|
2829
|
+
op: t("secret.op_read"),
|
|
2830
|
+
name,
|
|
2831
|
+
detail: describeFailure(this.shell, r)
|
|
2832
|
+
})
|
|
2833
|
+
);
|
|
2834
|
+
}
|
|
2487
2835
|
return Buffer.from(r.stdout.trim(), "base64").toString("utf-8");
|
|
2488
2836
|
}
|
|
2489
2837
|
async set(name, value) {
|
|
2490
2838
|
assertValidSecretName(name);
|
|
2491
2839
|
const r = await this.ps(PROTECT_SCRIPT, Buffer.from(value, "utf-8").toString("base64"));
|
|
2492
|
-
if (r.code !== 0)
|
|
2840
|
+
if (r.code !== 0) {
|
|
2841
|
+
throw new Error(
|
|
2842
|
+
t("secret.op_failed", {
|
|
2843
|
+
op: t("secret.op_write"),
|
|
2844
|
+
name,
|
|
2845
|
+
detail: describeFailure(this.shell, r)
|
|
2846
|
+
})
|
|
2847
|
+
);
|
|
2848
|
+
}
|
|
2493
2849
|
const file = this.load();
|
|
2494
2850
|
file.secrets[name] = stripBlank(r.stdout);
|
|
2495
2851
|
this.save(file);
|
|
@@ -2503,7 +2859,6 @@ var DpapiStore = class {
|
|
|
2503
2859
|
}
|
|
2504
2860
|
return Promise.resolve();
|
|
2505
2861
|
}
|
|
2506
|
-
/** 密文就在自己的文件里,名字直接是 key —— 这个后端不需要 catalog.ts 的索引 */
|
|
2507
2862
|
list() {
|
|
2508
2863
|
return Promise.resolve(Object.keys(this.load().secrets).sort());
|
|
2509
2864
|
}
|
|
@@ -2515,7 +2870,6 @@ var DpapiStore = class {
|
|
|
2515
2870
|
this.cache = readDpapiFile(this.filePath);
|
|
2516
2871
|
return this.cache;
|
|
2517
2872
|
}
|
|
2518
|
-
/** 原子写 + 建文件时就 0600(Windows 上 mode 是空操作,但这份代码也跑在测试里) */
|
|
2519
2873
|
save(file) {
|
|
2520
2874
|
mkdirSync(dirname(this.filePath), { recursive: true });
|
|
2521
2875
|
const tmp = `${this.filePath}.${process.pid}.tmp`;
|
|
@@ -2525,7 +2879,11 @@ var DpapiStore = class {
|
|
|
2525
2879
|
} catch (err) {
|
|
2526
2880
|
rmSync(tmp, { force: true });
|
|
2527
2881
|
throw new Error(
|
|
2528
|
-
|
|
2882
|
+
t("secret.op_failed", {
|
|
2883
|
+
op: t("secret.op_write"),
|
|
2884
|
+
name: t("secret.dpapi_file_label"),
|
|
2885
|
+
detail: `${this.filePath}: ${err instanceof Error ? err.message : String(err)}`
|
|
2886
|
+
})
|
|
2529
2887
|
);
|
|
2530
2888
|
}
|
|
2531
2889
|
this.cache = file;
|
|
@@ -2548,10 +2906,10 @@ function readDpapiFile(filePath) {
|
|
|
2548
2906
|
}
|
|
2549
2907
|
}
|
|
2550
2908
|
async function probeDpapi() {
|
|
2551
|
-
if (platform() !== "win32") return { reason: "
|
|
2909
|
+
if (platform() !== "win32") return { reason: t("secret.not_windows") };
|
|
2552
2910
|
const expect = randomBytes(8).toString("hex");
|
|
2553
2911
|
const payload = Buffer.from(expect, "utf-8").toString("base64");
|
|
2554
|
-
let last = "
|
|
2912
|
+
let last = t("secret.no_powershell");
|
|
2555
2913
|
for (const shell of SHELLS2) {
|
|
2556
2914
|
const sealed = await run(shell, [...PS_FLAGS, PROTECT_SCRIPT], {
|
|
2557
2915
|
timeoutMs: POWERSHELL_TIMEOUT_MS,
|
|
@@ -2559,7 +2917,7 @@ async function probeDpapi() {
|
|
|
2559
2917
|
});
|
|
2560
2918
|
if (sealed.missing) continue;
|
|
2561
2919
|
if (sealed.code !== 0) {
|
|
2562
|
-
last =
|
|
2920
|
+
last = t("secret.probe_encrypt_failed", { detail: describeFailure(shell, sealed) });
|
|
2563
2921
|
continue;
|
|
2564
2922
|
}
|
|
2565
2923
|
const back = await run(shell, [...PS_FLAGS, UNPROTECT_SCRIPT], {
|
|
@@ -2567,11 +2925,11 @@ async function probeDpapi() {
|
|
|
2567
2925
|
stdin: stripBlank(sealed.stdout)
|
|
2568
2926
|
});
|
|
2569
2927
|
if (back.code !== 0) {
|
|
2570
|
-
last =
|
|
2928
|
+
last = t("secret.probe_decrypt_failed", { detail: describeFailure(shell, back) });
|
|
2571
2929
|
continue;
|
|
2572
2930
|
}
|
|
2573
2931
|
if (Buffer.from(back.stdout.trim(), "base64").toString("utf-8") !== expect) {
|
|
2574
|
-
last = "
|
|
2932
|
+
last = t("secret.probe_crypto_mismatch");
|
|
2575
2933
|
continue;
|
|
2576
2934
|
}
|
|
2577
2935
|
return { shell };
|
|
@@ -2584,11 +2942,6 @@ function stripBlank(s) {
|
|
|
2584
2942
|
var SECURITY = "/usr/bin/security";
|
|
2585
2943
|
var NOT_FOUND = 44;
|
|
2586
2944
|
var KeychainStore = class {
|
|
2587
|
-
/**
|
|
2588
|
-
* @param service 钥匙串里的服务名。**按家目录分命名空间**,见
|
|
2589
|
-
* [types.ts](./types.js) 的 `secretServiceFor()` ——
|
|
2590
|
-
* 钥匙串是全局的,写死一个名字会让多 profile 共用同一批凭据
|
|
2591
|
-
*/
|
|
2592
2945
|
constructor(catalog, service) {
|
|
2593
2946
|
this.catalog = catalog;
|
|
2594
2947
|
this.service = service;
|
|
@@ -2597,14 +2950,24 @@ var KeychainStore = class {
|
|
|
2597
2950
|
service;
|
|
2598
2951
|
backend = "keychain";
|
|
2599
2952
|
encrypted = true;
|
|
2600
|
-
detail
|
|
2953
|
+
get detail() {
|
|
2954
|
+
return t("secret.backend_keychain");
|
|
2955
|
+
}
|
|
2601
2956
|
async get(name) {
|
|
2602
2957
|
assertValidSecretName(name);
|
|
2603
2958
|
const r = await run(SECURITY, ["find-generic-password", "-s", this.service, "-a", name, "-w"], {
|
|
2604
2959
|
timeoutMs: OP_TIMEOUT_MS
|
|
2605
2960
|
});
|
|
2606
2961
|
if (r.code === NOT_FOUND) return void 0;
|
|
2607
|
-
if (r.code !== 0)
|
|
2962
|
+
if (r.code !== 0) {
|
|
2963
|
+
throw new Error(
|
|
2964
|
+
t("secret.op_failed", {
|
|
2965
|
+
op: t("secret.op_read"),
|
|
2966
|
+
name,
|
|
2967
|
+
detail: describeFailure(SECURITY, r)
|
|
2968
|
+
})
|
|
2969
|
+
);
|
|
2970
|
+
}
|
|
2608
2971
|
return decode(r.stdout);
|
|
2609
2972
|
}
|
|
2610
2973
|
async set(name, value) {
|
|
@@ -2625,7 +2988,15 @@ var KeychainStore = class {
|
|
|
2625
2988
|
],
|
|
2626
2989
|
{ timeoutMs: OP_TIMEOUT_MS }
|
|
2627
2990
|
);
|
|
2628
|
-
if (r.code !== 0)
|
|
2991
|
+
if (r.code !== 0) {
|
|
2992
|
+
throw new Error(
|
|
2993
|
+
t("secret.op_failed", {
|
|
2994
|
+
op: t("secret.op_write"),
|
|
2995
|
+
name,
|
|
2996
|
+
detail: describeFailure(SECURITY, r)
|
|
2997
|
+
})
|
|
2998
|
+
);
|
|
2999
|
+
}
|
|
2629
3000
|
this.catalog.add(name);
|
|
2630
3001
|
}
|
|
2631
3002
|
async delete(name) {
|
|
@@ -2634,24 +3005,28 @@ var KeychainStore = class {
|
|
|
2634
3005
|
timeoutMs: OP_TIMEOUT_MS
|
|
2635
3006
|
});
|
|
2636
3007
|
if (r.code !== 0 && r.code !== NOT_FOUND) {
|
|
2637
|
-
throw new Error(
|
|
3008
|
+
throw new Error(
|
|
3009
|
+
t("secret.op_failed", {
|
|
3010
|
+
op: t("secret.op_delete"),
|
|
3011
|
+
name,
|
|
3012
|
+
detail: describeFailure(SECURITY, r)
|
|
3013
|
+
})
|
|
3014
|
+
);
|
|
2638
3015
|
}
|
|
2639
3016
|
this.catalog.remove(name);
|
|
2640
3017
|
}
|
|
2641
|
-
/** 见 catalog.ts:钥匙串本身没有便宜的列举方式,索引是提示、`get` 才是权威 */
|
|
2642
3018
|
list() {
|
|
2643
3019
|
return Promise.resolve(this.catalog.read());
|
|
2644
3020
|
}
|
|
2645
|
-
/** 预取时发现索引里有、钥匙串里没有的名字,就地剔掉(自愈) */
|
|
2646
3021
|
healCatalog(available) {
|
|
2647
3022
|
this.catalog.replace(available);
|
|
2648
3023
|
}
|
|
2649
3024
|
};
|
|
2650
3025
|
async function probeKeychain(service) {
|
|
2651
|
-
if (platform() !== "darwin") return "
|
|
3026
|
+
if (platform() !== "darwin") return t("secret.not_macos");
|
|
2652
3027
|
const dk = await run(SECURITY, ["default-keychain"], { timeoutMs: PROBE_TIMEOUT_MS2 });
|
|
2653
3028
|
if (dk.code !== 0 || dk.stdout.trim() === "") {
|
|
2654
|
-
return
|
|
3029
|
+
return t("secret.no_default_keychain", { detail: describeFailure(SECURITY, dk) });
|
|
2655
3030
|
}
|
|
2656
3031
|
const account = `__probe__${randomBytes(8).toString("hex")}`;
|
|
2657
3032
|
const expect = randomBytes(8).toString("hex");
|
|
@@ -2659,13 +3034,17 @@ async function probeKeychain(service) {
|
|
|
2659
3034
|
const added = await run(SECURITY, ["add-generic-password", "-U", ...args2, "-w", encode(expect)], {
|
|
2660
3035
|
timeoutMs: PROBE_TIMEOUT_MS2
|
|
2661
3036
|
});
|
|
2662
|
-
if (added.code !== 0)
|
|
3037
|
+
if (added.code !== 0) {
|
|
3038
|
+
return t("secret.probe_write_failed", { detail: describeFailure(SECURITY, added) });
|
|
3039
|
+
}
|
|
2663
3040
|
const read = await run(SECURITY, ["find-generic-password", ...args2, "-w"], {
|
|
2664
3041
|
timeoutMs: PROBE_TIMEOUT_MS2
|
|
2665
3042
|
});
|
|
2666
3043
|
await run(SECURITY, ["delete-generic-password", ...args2], { timeoutMs: PROBE_TIMEOUT_MS2 });
|
|
2667
|
-
if (read.code !== 0)
|
|
2668
|
-
|
|
3044
|
+
if (read.code !== 0) {
|
|
3045
|
+
return t("secret.probe_read_failed", { detail: describeFailure(SECURITY, read) });
|
|
3046
|
+
}
|
|
3047
|
+
if (decode(read.stdout) !== expect) return t("secret.probe_mismatch");
|
|
2669
3048
|
return null;
|
|
2670
3049
|
}
|
|
2671
3050
|
function encode(value) {
|
|
@@ -2676,10 +3055,6 @@ function decode(stdout) {
|
|
|
2676
3055
|
}
|
|
2677
3056
|
var SECRET_TOOL = "secret-tool";
|
|
2678
3057
|
var LibsecretStore = class {
|
|
2679
|
-
/**
|
|
2680
|
-
* @param service Secret Service 里的 `service` 属性值。**按家目录分命名空间**,
|
|
2681
|
-
* 见 [types.ts](./types.js) 的 `secretServiceFor()`
|
|
2682
|
-
*/
|
|
2683
3058
|
constructor(catalog, service) {
|
|
2684
3059
|
this.catalog = catalog;
|
|
2685
3060
|
this.service = service;
|
|
@@ -2688,8 +3063,9 @@ var LibsecretStore = class {
|
|
|
2688
3063
|
service;
|
|
2689
3064
|
backend = "libsecret";
|
|
2690
3065
|
encrypted = true;
|
|
2691
|
-
detail
|
|
2692
|
-
|
|
3066
|
+
get detail() {
|
|
3067
|
+
return t("secret.backend_libsecret");
|
|
3068
|
+
}
|
|
2693
3069
|
attrs(name) {
|
|
2694
3070
|
return ["service", this.service, "account", name];
|
|
2695
3071
|
}
|
|
@@ -2698,7 +3074,13 @@ var LibsecretStore = class {
|
|
|
2698
3074
|
const r = await run(SECRET_TOOL, ["lookup", ...this.attrs(name)], { timeoutMs: OP_TIMEOUT_MS });
|
|
2699
3075
|
if (r.code === 0) return decode2(r.stdout);
|
|
2700
3076
|
if (r.stdout.trim() === "" && !r.timedOut && !r.missing) return void 0;
|
|
2701
|
-
throw new Error(
|
|
3077
|
+
throw new Error(
|
|
3078
|
+
t("secret.op_failed", {
|
|
3079
|
+
op: t("secret.op_read"),
|
|
3080
|
+
name,
|
|
3081
|
+
detail: describeFailure(SECRET_TOOL, r)
|
|
3082
|
+
})
|
|
3083
|
+
);
|
|
2702
3084
|
}
|
|
2703
3085
|
async set(name, value) {
|
|
2704
3086
|
assertValidSecretName(name);
|
|
@@ -2707,31 +3089,40 @@ var LibsecretStore = class {
|
|
|
2707
3089
|
["store", `--label=${this.service}: ${name}`, ...this.attrs(name)],
|
|
2708
3090
|
{ timeoutMs: OP_TIMEOUT_MS, stdin: encode2(value) }
|
|
2709
3091
|
);
|
|
2710
|
-
if (r.code !== 0)
|
|
3092
|
+
if (r.code !== 0) {
|
|
3093
|
+
throw new Error(
|
|
3094
|
+
t("secret.op_failed", {
|
|
3095
|
+
op: t("secret.op_write"),
|
|
3096
|
+
name,
|
|
3097
|
+
detail: describeFailure(SECRET_TOOL, r)
|
|
3098
|
+
})
|
|
3099
|
+
);
|
|
3100
|
+
}
|
|
2711
3101
|
this.catalog.add(name);
|
|
2712
3102
|
}
|
|
2713
3103
|
async delete(name) {
|
|
2714
3104
|
assertValidSecretName(name);
|
|
2715
3105
|
const r = await run(SECRET_TOOL, ["clear", ...this.attrs(name)], { timeoutMs: OP_TIMEOUT_MS });
|
|
2716
3106
|
if (r.code !== 0 && (r.timedOut || r.missing)) {
|
|
2717
|
-
throw new Error(
|
|
3107
|
+
throw new Error(
|
|
3108
|
+
t("secret.op_failed", {
|
|
3109
|
+
op: t("secret.op_delete"),
|
|
3110
|
+
name,
|
|
3111
|
+
detail: describeFailure(SECRET_TOOL, r)
|
|
3112
|
+
})
|
|
3113
|
+
);
|
|
2718
3114
|
}
|
|
2719
3115
|
this.catalog.remove(name);
|
|
2720
3116
|
}
|
|
2721
|
-
/**
|
|
2722
|
-
* `secret-tool search --all` 能列,但输出格式随 libsecret 版本变,
|
|
2723
|
-
* 而且要多走一次 D-Bus 往返。索引更便宜也更稳定 —— 见 catalog.ts。
|
|
2724
|
-
*/
|
|
2725
3117
|
list() {
|
|
2726
3118
|
return Promise.resolve(this.catalog.read());
|
|
2727
3119
|
}
|
|
2728
|
-
/** 预取时发现索引里有、keyring 里没有的名字,就地剔掉(自愈) */
|
|
2729
3120
|
healCatalog(available) {
|
|
2730
3121
|
this.catalog.replace(available);
|
|
2731
3122
|
}
|
|
2732
3123
|
};
|
|
2733
3124
|
async function probeLibsecret(service) {
|
|
2734
|
-
if (platform() === "darwin" || platform() === "win32") return "
|
|
3125
|
+
if (platform() === "darwin" || platform() === "win32") return t("secret.not_linux");
|
|
2735
3126
|
const account = `__probe__${randomBytes(8).toString("hex")}`;
|
|
2736
3127
|
const expect = randomBytes(8).toString("hex");
|
|
2737
3128
|
const args2 = ["service", service, "account", account];
|
|
@@ -2740,16 +3131,20 @@ async function probeLibsecret(service) {
|
|
|
2740
3131
|
stdin: encode2(expect)
|
|
2741
3132
|
});
|
|
2742
3133
|
if (stored.missing) {
|
|
2743
|
-
return "
|
|
3134
|
+
return t("secret.no_secret_tool");
|
|
2744
3135
|
}
|
|
2745
3136
|
if (stored.timedOut) {
|
|
2746
|
-
return "secret
|
|
3137
|
+
return t("secret.secret_tool_hang");
|
|
3138
|
+
}
|
|
3139
|
+
if (stored.code !== 0) {
|
|
3140
|
+
return t("secret.probe_write_failed", { detail: describeFailure(SECRET_TOOL, stored) });
|
|
2747
3141
|
}
|
|
2748
|
-
if (stored.code !== 0) return `\u5199\u5165\u63A2\u6D4B\u5931\u8D25\uFF08${describeFailure(SECRET_TOOL, stored)}\uFF09`;
|
|
2749
3142
|
const read = await run(SECRET_TOOL, ["lookup", ...args2], { timeoutMs: PROBE_TIMEOUT_MS2 });
|
|
2750
3143
|
await run(SECRET_TOOL, ["clear", ...args2], { timeoutMs: PROBE_TIMEOUT_MS2 });
|
|
2751
|
-
if (read.code !== 0)
|
|
2752
|
-
|
|
3144
|
+
if (read.code !== 0) {
|
|
3145
|
+
return t("secret.probe_read_failed", { detail: describeFailure(SECRET_TOOL, read) });
|
|
3146
|
+
}
|
|
3147
|
+
if (decode2(read.stdout) !== expect) return t("secret.probe_mismatch");
|
|
2753
3148
|
return null;
|
|
2754
3149
|
}
|
|
2755
3150
|
function encode2(value) {
|
|
@@ -2759,18 +3154,17 @@ function decode2(stdout) {
|
|
|
2759
3154
|
return Buffer.from(stdout.trim(), "base64").toString("utf-8");
|
|
2760
3155
|
}
|
|
2761
3156
|
var PlaintextStore = class {
|
|
2762
|
-
/**
|
|
2763
|
-
* @param reason 为什么降级。**必须带修复方法** —— 只说「明文」而不说怎么修,
|
|
2764
|
-
* 用户除了忍着没有别的选择
|
|
2765
|
-
*/
|
|
2766
3157
|
constructor(envFilePath, reason) {
|
|
2767
3158
|
this.envFilePath = envFilePath;
|
|
2768
|
-
this.
|
|
3159
|
+
this.reason = reason;
|
|
2769
3160
|
}
|
|
2770
3161
|
envFilePath;
|
|
3162
|
+
reason;
|
|
2771
3163
|
backend = "plaintext";
|
|
2772
3164
|
encrypted = false;
|
|
2773
|
-
detail
|
|
3165
|
+
get detail() {
|
|
3166
|
+
return t("secret.plaintext_note", { reason: this.reason, path: this.envFilePath });
|
|
3167
|
+
}
|
|
2774
3168
|
get(name) {
|
|
2775
3169
|
return Promise.resolve(parseEnvText(readText(this.envFilePath))[name]);
|
|
2776
3170
|
}
|
|
@@ -2850,7 +3244,7 @@ var TAG_BYTES = 16;
|
|
|
2850
3244
|
var dataKey = null;
|
|
2851
3245
|
function setDataKey(key) {
|
|
2852
3246
|
if (key !== null && key.length !== KEY_BYTES) {
|
|
2853
|
-
throw new Error(
|
|
3247
|
+
throw new Error(t("secret.bad_key_length", { expected: KEY_BYTES, actual: key.length }));
|
|
2854
3248
|
}
|
|
2855
3249
|
dataKey = key;
|
|
2856
3250
|
}
|
|
@@ -2879,9 +3273,9 @@ function isEnvelope(text) {
|
|
|
2879
3273
|
return text.startsWith(ENVELOPE_PREFIX);
|
|
2880
3274
|
}
|
|
2881
3275
|
function openEnvelope(key, blob) {
|
|
2882
|
-
if (!isEnvelope(blob)) throw new Error("
|
|
3276
|
+
if (!isEnvelope(blob)) throw new Error(t("secret.not_envelope"));
|
|
2883
3277
|
const raw = Buffer.from(blob.slice(ENVELOPE_PREFIX.length), "base64");
|
|
2884
|
-
if (raw.length < IV_BYTES + TAG_BYTES) throw new Error("
|
|
3278
|
+
if (raw.length < IV_BYTES + TAG_BYTES) throw new Error(t("secret.envelope_too_short"));
|
|
2885
3279
|
const iv = raw.subarray(0, IV_BYTES);
|
|
2886
3280
|
const tag = raw.subarray(IV_BYTES, IV_BYTES + TAG_BYTES);
|
|
2887
3281
|
const body = raw.subarray(IV_BYTES + TAG_BYTES);
|
|
@@ -2899,7 +3293,10 @@ async function migrateEnvSecrets(opts) {
|
|
|
2899
3293
|
try {
|
|
2900
3294
|
existing = new Set(await store.list());
|
|
2901
3295
|
} catch (err) {
|
|
2902
|
-
return {
|
|
3296
|
+
return {
|
|
3297
|
+
migrated: [],
|
|
3298
|
+
warning: t("secret.migrate_read_failed", { detail: message(err) })
|
|
3299
|
+
};
|
|
2903
3300
|
}
|
|
2904
3301
|
const todo = candidates.filter((n) => !existing.has(n));
|
|
2905
3302
|
if (todo.length === 0) return { migrated: [] };
|
|
@@ -2913,7 +3310,11 @@ async function migrateEnvSecrets(opts) {
|
|
|
2913
3310
|
} catch (err) {
|
|
2914
3311
|
return {
|
|
2915
3312
|
migrated,
|
|
2916
|
-
warning:
|
|
3313
|
+
warning: t("secret.migrate_failed", {
|
|
3314
|
+
name,
|
|
3315
|
+
path: envFilePath,
|
|
3316
|
+
detail: message(err)
|
|
3317
|
+
})
|
|
2917
3318
|
};
|
|
2918
3319
|
}
|
|
2919
3320
|
}
|
|
@@ -2921,7 +3322,10 @@ async function migrateEnvSecrets(opts) {
|
|
|
2921
3322
|
try {
|
|
2922
3323
|
renameSync(envFilePath, backupPath);
|
|
2923
3324
|
} catch (err) {
|
|
2924
|
-
return {
|
|
3325
|
+
return {
|
|
3326
|
+
migrated,
|
|
3327
|
+
warning: t("secret.migrate_partial", { path: envFilePath, detail: message(err) })
|
|
3328
|
+
};
|
|
2925
3329
|
}
|
|
2926
3330
|
return { migrated, backupPath };
|
|
2927
3331
|
}
|
|
@@ -2943,14 +3347,16 @@ function readOrEmpty(path) {
|
|
|
2943
3347
|
function message(err) {
|
|
2944
3348
|
return err instanceof Error ? err.message : String(err);
|
|
2945
3349
|
}
|
|
2946
|
-
|
|
2947
3350
|
// src/secret/index.ts
|
|
2948
3351
|
async function createSecretStore(opts = {}) {
|
|
2949
3352
|
if (opts.inject) return opts.inject;
|
|
2950
3353
|
const homeDir = opts.homeDir ?? resolveHomeDir();
|
|
2951
3354
|
const forced = readForcedBackend();
|
|
2952
3355
|
if (forced === "plaintext") {
|
|
2953
|
-
return new PlaintextStore(
|
|
3356
|
+
return new PlaintextStore(
|
|
3357
|
+
envPath(homeDir),
|
|
3358
|
+
t("secret.backend_plaintext_forced", { envVar: BACKEND_ENV_VAR })
|
|
3359
|
+
);
|
|
2954
3360
|
}
|
|
2955
3361
|
const reason = await tryBackends(homeDir, forced);
|
|
2956
3362
|
if (typeof reason !== "string") return reason;
|
|
@@ -2968,14 +3374,18 @@ async function tryBackends(homeDir, forced) {
|
|
|
2968
3374
|
if (want("dpapi") && platform() === "win32") {
|
|
2969
3375
|
const { shell, reason } = await probeDpapi();
|
|
2970
3376
|
if (shell) return new DpapiStore(dpapiStorePath(homeDir), shell);
|
|
2971
|
-
return reason ?? "
|
|
3377
|
+
return reason ?? t("secret.dpapi_unavailable");
|
|
2972
3378
|
}
|
|
2973
3379
|
if (want("libsecret") && platform() !== "darwin" && platform() !== "win32") {
|
|
2974
3380
|
const why = await probeLibsecret(service);
|
|
2975
3381
|
if (why === null) return new LibsecretStore(catalog, service);
|
|
2976
3382
|
return why;
|
|
2977
3383
|
}
|
|
2978
|
-
return forced === null ?
|
|
3384
|
+
return forced === null ? t("secret.no_backend", { platform: platform() }) : t("secret.forced_unavailable", {
|
|
3385
|
+
envVar: BACKEND_ENV_VAR,
|
|
3386
|
+
forced,
|
|
3387
|
+
platform: platform()
|
|
3388
|
+
});
|
|
2979
3389
|
}
|
|
2980
3390
|
function readForcedBackend() {
|
|
2981
3391
|
const raw = process.env[BACKEND_ENV_VAR]?.trim().toLowerCase();
|
|
@@ -2988,7 +3398,7 @@ async function prefetchProviderSecrets(store, names) {
|
|
|
2988
3398
|
try {
|
|
2989
3399
|
stored = await store.list();
|
|
2990
3400
|
} catch (err) {
|
|
2991
|
-
return { values: {}, warning:
|
|
3401
|
+
return { values: {}, warning: t("secret.list_failed", { detail: message2(err) }) };
|
|
2992
3402
|
}
|
|
2993
3403
|
const wanted = stored.filter((n) => names.includes(n));
|
|
2994
3404
|
const values2 = {};
|
|
@@ -2999,7 +3409,7 @@ async function prefetchProviderSecrets(store, names) {
|
|
|
2999
3409
|
if (value) values2[name] = value;
|
|
3000
3410
|
else missing.push(name);
|
|
3001
3411
|
} catch (err) {
|
|
3002
|
-
return { values: values2, warning:
|
|
3412
|
+
return { values: values2, warning: t("secret.read_failed", { name, detail: message2(err) }) };
|
|
3003
3413
|
}
|
|
3004
3414
|
}
|
|
3005
3415
|
if (missing.length > 0) {
|
|
@@ -3096,4 +3506,4 @@ function message2(err) {
|
|
|
3096
3506
|
* Modifications Copyright 2024-2026 bowen
|
|
3097
3507
|
*/
|
|
3098
3508
|
|
|
3099
|
-
export { CODE_EXEC_ROOTS, DEFAULT_LANG, DEFAULT_YAML, DENIAL_SIGNATURES, ENVELOPE_PREFIX, EXEC_PATH_AS_NODE_ENV, IS_WINDOWS, LANGS, PROJECT_DIR_NAME, PlaintextStore,
|
|
3509
|
+
export { CLAUDE_DIR_NAME, CODE_EXEC_ROOTS, DEFAULT_LANG, DEFAULT_YAML, DENIAL_SIGNATURES, ENVELOPE_PREFIX, EXEC_PATH_AS_NODE_ENV, IS_WINDOWS, LANGS, PROJECT_DIR_NAME, PlaintextStore, RUNNER_FAILURE_RULES, SECRET_MODE, SECRET_SERVICE, SHELL_KINDS, SIGKILL_TIMEOUT_MS, SecretCatalog, TOOLCHAIN_CACHE_DIRS, WINDOWS_HIDE_FLAGS, agentsDir, allJobs, appendJob, approvalsPath, artifactsDir, assertValidSecretName, automationDir, automationLogsDir, automationWorkDir, budgetStatePath, buildBwrapArgs, buildProfile, checkDangerousCommand, checkObfuscation, checkpointsDir, classifyFailure, claudeUserSettingsPath, clearAllJobs, closeAllDatabases, collectProcessTree, collectProcessTreeStamped, commandsDir, configPath, confine, countCodePoints, createLogger, createProcessTable, createSecretStore, createStreamDecoder, currentLang, dbPath, detectBackend, detectConsoleEncoding, encodingForCodePage, envPath, formatIssues, generateDataKey, getDataKey, getDefaultShell, getJob, getPythonCommand, getSecretStore, getSecretValues, hasSideEffectChannel, headCodePoints, hooksPath, i18nDiagnostics, isEnvelope, isInWorkspace, isLang, isReadOnlyCommand, isSensitiveKey, isolate, issueDetails, jobDetail, keepHeadAndTail, keybindingsPath, killAllTrackedProcesses, killPids, killProcessTree, killStampedPids, killTrackedProcess, listJobs, listStoredSecretNames, loadCatalog, localesDir, managedSettingsPath, marketplacesPath, maskApiKey, maskSensitive, mcpAuthPath, mcpConfigPath, mcpSchemaCachePath, memoriesDir, migrate, migrateEnvSecrets, normalizeForMatch, openArtifact, openDatabase, openEnvelope, parseDataKey, parseEnvText, parseLenient, parseShellCommand, parseStrict, placeholdersOf, pluginsDir, pluginsStatePath, policiesDir, powershellCommandArg, prefetchProviderSecrets, probeBubblewrap, probeSeatbelt, probeShell, processFallbackTable, processTableFor, projectClaudeLocalSettingsPath, projectClaudeSettingsPath, projectCommandsDir, projectHooksPath, projectLocalSettingsPath, projectPoliciesDir, projectSchemasDir, projectSettingsPath, projectSkillsDir, readConfigYaml, readJob, readKey, refCount, registerJob, rekeyJobs, releaseDatabase, removeEnvVarText, resetI18n, resetRipgrepCache, resetSecretState, resolveHomeDir, resolveLang, resolveProfile, resolveRipgrep, resolveShellKind, ripgrepInstallHint, safeInit, sandboxModeForLevel, schemaVersion, sealEnvelope, setDataKey, setEnvVarText, setLang, setLogLevel, setScalar, setSecretStore, setSecretValues, setSectionField, setShell, shellPtyArgs, shellSpawnArgs, skillsDir, splitWords, startLongLivedProcess, stopJob, stripShellWrapper, systemLocaleSignals, t, tailCodePoints, touchJob, trackForeignProcess, trackedProcessCount, trustPath, trustedImportsPath, uiDateLocale, unknownKeyIssues, unsetScalar, unsetSectionField, waitJob, withTimeout, workspacesPath, worktreesDir, writeArtifact, writeConfigYaml, writeEnvFile };
|