@kenhkl/agent-moss 0.16.14 → 0.17.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.js CHANGED
@@ -1,5 +1,11 @@
1
1
  var __defProp = Object.defineProperty;
2
2
  var __getOwnPropNames = Object.getOwnPropertyNames;
3
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
4
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
5
+ }) : x)(function(x) {
6
+ if (typeof require !== "undefined") return require.apply(this, arguments);
7
+ throw Error('Dynamic require of "' + x + '" is not supported');
8
+ });
3
9
  var __esm = (fn, res) => function __init() {
4
10
  return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
5
11
  };
@@ -25,6 +31,10 @@ var init_esm_shims = __esm({
25
31
  import { join } from "path";
26
32
  import { homedir } from "os";
27
33
  import { readFileSync } from "fs";
34
+ function isSafeId(value) {
35
+ if (!value || value.length > ID_MAX_LEN) return false;
36
+ return ID_PATTERN.test(value);
37
+ }
28
38
  function getRulesRoot() {
29
39
  const raw = (process.env[ENV_RULES_ROOT] ?? "").trim();
30
40
  return raw ? raw : null;
@@ -98,7 +108,7 @@ function getTokenStatsPath() {
98
108
  }
99
109
  return join(getDataRoot(), "agent_moss_token_stats.json");
100
110
  }
101
- var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT;
111
+ var ENV_DATA_ROOT, ENV_INSTANCE, DEFAULT_DATA_ROOT, ENV_RULES_ROOT, ID_MAX_LEN, ID_PATTERN;
102
112
  var init_paths = __esm({
103
113
  "src/infra/paths.ts"() {
104
114
  "use strict";
@@ -107,6 +117,8 @@ var init_paths = __esm({
107
117
  ENV_INSTANCE = "AGENT_MOSS_INSTANCE";
108
118
  DEFAULT_DATA_ROOT = join(homedir(), ".config", "agentmoss");
109
119
  ENV_RULES_ROOT = "AGENT_MOSS_RULES_ROOT";
120
+ ID_MAX_LEN = 64;
121
+ ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]*$/;
110
122
  }
111
123
  });
112
124
 
@@ -184,7 +196,7 @@ function parseDirEnv(envName) {
184
196
  function rulesDirs() {
185
197
  if (isBuiltinOnly()) return [];
186
198
  const dirs = [];
187
- for (const env of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env));
199
+ for (const env2 of RULES_DIR_ENVS) dirs.push(...parseDirEnv(env2));
188
200
  dirs.push(resolve(dataRootDir(), "rules"));
189
201
  return [...new Set(dirs)];
190
202
  }
@@ -232,6 +244,7 @@ function loadJson(name) {
232
244
  }
233
245
  }
234
246
  function resolveScenarioRulesFile(scenarioId) {
247
+ if (!isSafeId(scenarioId)) return null;
235
248
  const rel = join2("scenarios", scenarioId, "rules.json");
236
249
  for (const dir of rulesDirs()) {
237
250
  const p = resolve(dir, rel);
@@ -716,6 +729,491 @@ var init_rules_loader = __esm({
716
729
  }
717
730
  });
718
731
 
732
+ // src/infra/security-policy.ts
733
+ import { createHash as createHash2, randomBytes, timingSafeEqual } from "crypto";
734
+ import { chmodSync, closeSync, existsSync as existsSync3, lstatSync, mkdirSync, openSync, readFileSync as readFileSync4, readSync, realpathSync, writeSync } from "fs";
735
+ import { homedir as homedir2, tmpdir } from "os";
736
+ import { basename, isAbsolute, join as join4, relative, resolve as resolve2, dirname as dirname2 } from "path";
737
+ function env(name, dflt = "") {
738
+ return (process.env[name] ?? dflt).trim();
739
+ }
740
+ function envInt(name, dflt) {
741
+ const raw = env(name);
742
+ if (!raw) return dflt;
743
+ const n = Number.parseInt(raw, 10);
744
+ return Number.isFinite(n) ? n : dflt;
745
+ }
746
+ function splitList(raw) {
747
+ return raw.replace(/;/g, ",").split(",").map((x) => x.trim()).filter(Boolean);
748
+ }
749
+ function originCheckEnabled() {
750
+ return TRUTHY2.includes(env("AGENT_MOSS_ORIGIN_CHECK", "1").toLowerCase());
751
+ }
752
+ function allowedOrigins() {
753
+ return splitList(env("AGENT_MOSS_CORS_ORIGINS"));
754
+ }
755
+ function normOrigin(o) {
756
+ return (o || "").trim().replace(/\/+$/, "");
757
+ }
758
+ function originAllowed(origin, hostHeader, extra) {
759
+ const o = normOrigin(origin);
760
+ if (!o) return true;
761
+ const entries = (extra ?? allowedOrigins()).map(normOrigin);
762
+ if (entries.includes(o)) return true;
763
+ const host = (hostHeader || "").trim();
764
+ if (!host) return false;
765
+ try {
766
+ const u = new URL(o);
767
+ return u.host === host;
768
+ } catch {
769
+ return false;
770
+ }
771
+ }
772
+ function authMode() {
773
+ return (env("AGENT_MOSS_AUTH_MODE", AUTH_MODE_AUTO) || AUTH_MODE_AUTO).toLowerCase();
774
+ }
775
+ function autoTokenPath() {
776
+ let root;
777
+ try {
778
+ root = getDataRoot();
779
+ } catch {
780
+ root = join4(homedir2(), ".config", "agentmoss");
781
+ }
782
+ return join4(root, "auth_token");
783
+ }
784
+ function readFdToken() {
785
+ const raw = env("AGENT_MOSS_AUTH_TOKEN_FD");
786
+ if (!raw) return "";
787
+ const fd = Number.parseInt(raw, 10);
788
+ if (!Number.isFinite(fd)) return "";
789
+ if (fdTokenCache.has(fd)) return fdTokenCache.get(fd);
790
+ const buf = Buffer.alloc(8192);
791
+ let n = 0;
792
+ try {
793
+ n = readSync(fd, buf, 0, buf.length, null);
794
+ } catch (e) {
795
+ console.error(`[AgentMoss] \u4ECE fd ${fd} \u8BFB\u53D6\u8BBF\u95EE token \u5931\u8D25:`, e);
796
+ }
797
+ try {
798
+ closeSync(fd);
799
+ } catch {
800
+ }
801
+ const tok = n > 0 ? buf.subarray(0, n).toString("utf-8").trim() : "";
802
+ fdTokenCache.set(fd, tok);
803
+ return tok;
804
+ }
805
+ function readExplicitToken() {
806
+ const tok = env("AGENT_MOSS_AUTH_TOKEN");
807
+ if (tok) return tok;
808
+ const fdTok = readFdToken();
809
+ if (fdTok) return fdTok;
810
+ const path6 = env("AGENT_MOSS_AUTH_TOKEN_FILE");
811
+ if (path6) {
812
+ try {
813
+ return readFileSync4(path6, "utf-8").trim();
814
+ } catch {
815
+ return "";
816
+ }
817
+ }
818
+ return "";
819
+ }
820
+ function ensureAutoToken() {
821
+ const path6 = autoTokenPath();
822
+ try {
823
+ if (existsSync3(path6) && !lstatSync(path6).isSymbolicLink()) {
824
+ const tok2 = readFileSync4(path6, "utf-8").trim();
825
+ if (tok2) {
826
+ try {
827
+ chmodSync(path6, 384);
828
+ } catch {
829
+ }
830
+ return tok2;
831
+ }
832
+ }
833
+ const tok = randomBytes(32).toString("base64url");
834
+ ensurePrivateDir(dirname2(path6));
835
+ if (!writePrivateText(path6, tok + "\n")) return "";
836
+ console.warn(
837
+ `[AgentMoss] \u5DF2\u751F\u6210\u8BBF\u95EE token \u5E76\u5199\u5165 ${path6}\uFF080600\uFF09\u3002\u5BA2\u6237\u7AEF\u9700\u5E26 \`Authorization: Bearer <token>\`\uFF1B\u67E5\u770B\uFF1Acat ${path6}\uFF1B\u5982\u9700\u5173\u95ED\u9274\u6743\uFF1AAGENT_MOSS_AUTH_MODE=off`
838
+ );
839
+ return tok;
840
+ } catch (e) {
841
+ console.error("[AgentMoss] \u751F\u6210\u8BBF\u95EE token \u5931\u8D25\uFF08\u672C\u6B21\u8FD0\u884C\u5C06\u4E0D\u9274\u6743\uFF09:", e);
842
+ return "";
843
+ }
844
+ }
845
+ function parseAgentTokens(raw) {
846
+ const out = {};
847
+ for (const pair of splitList(raw)) {
848
+ const i = pair.indexOf("=");
849
+ if (i > 0) {
850
+ const a = pair.slice(0, i).trim();
851
+ const t = pair.slice(i + 1).trim();
852
+ if (a && t) out[a] = t;
853
+ }
854
+ }
855
+ return out;
856
+ }
857
+ function resolveAuth() {
858
+ const rawAgents = env("AGENT_MOSS_AGENT_TOKENS");
859
+ const key = [
860
+ authMode(),
861
+ env("AGENT_MOSS_AUTH_TOKEN"),
862
+ env("AGENT_MOSS_AUTH_TOKEN_FILE"),
863
+ env("AGENT_MOSS_AUTH_TOKEN_FD"),
864
+ rawAgents,
865
+ env("AGENT_MOSS_DATA_DIR")
866
+ ].join("\0");
867
+ if (tokenCache && tokenCache.key === key) {
868
+ return [tokenCache.token, tokenCache.agents, tokenCache.error];
869
+ }
870
+ let error = "";
871
+ let token = readExplicitToken();
872
+ if (!token && env("AGENT_MOSS_AUTH_TOKEN_FD") && authMode() !== AUTH_MODE_OFF) {
873
+ error = "AGENT_MOSS_AUTH_TOKEN_FD \u5DF2\u8BBE\u7F6E\uFF0C\u4F46\u6CA1\u80FD\u4ECE\u8BE5\u6587\u4EF6\u63CF\u8FF0\u7B26\u8BFB\u5230 token\u3002\u62D2\u7EDD\u964D\u7EA7\u5230\u78C1\u76D8 token\uFF08\u90A3\u4F1A\u5728\u672C\u673A\u7559\u4E0B\u4E00\u4EFD\u540C\u7528\u6237\u53EF\u8BFB\u7684\u51ED\u8BC1\uFF09\u3002";
874
+ console.error(`[agent-moss] ${error}`);
875
+ }
876
+ if (!token && !error && authMode() !== AUTH_MODE_OFF) token = ensureAutoToken();
877
+ const agents = parseAgentTokens(rawAgents);
878
+ tokenCache = { key, token, agents, error };
879
+ return [token, agents, error];
880
+ }
881
+ function agentTokens() {
882
+ return resolveAuth()[1];
883
+ }
884
+ function authConfigError() {
885
+ return resolveAuth()[2];
886
+ }
887
+ function failFastOnAuthError() {
888
+ const err = authConfigError();
889
+ if (err) {
890
+ console.error(`[agent-moss] \u9274\u6743\u914D\u7F6E\u9519\u8BEF\uFF0C\u62D2\u7EDD\u542F\u52A8: ${err}`);
891
+ process.exit(1);
892
+ }
893
+ }
894
+ function authEnabled() {
895
+ if (authMode() === AUTH_MODE_OFF) return false;
896
+ const [tok, agents] = resolveAuth();
897
+ return Boolean(tok) || Object.keys(agents).length > 0;
898
+ }
899
+ function bearer(headers) {
900
+ const get = (k) => headers instanceof Headers ? headers.get(k) ?? "" : headers[k] ?? "";
901
+ const v = get("authorization") || get("Authorization");
902
+ return v.toLowerCase().startsWith("bearer ") ? v.slice(7).trim() : "";
903
+ }
904
+ function constEq(a, b) {
905
+ const ba = Buffer.from(a, "utf-8");
906
+ const bb = Buffer.from(b, "utf-8");
907
+ if (ba.length !== bb.length) return false;
908
+ return timingSafeEqual(ba, bb);
909
+ }
910
+ function checkRequestAuth(headers, path6) {
911
+ if (authMode() === AUTH_MODE_OFF) return [true, ""];
912
+ const isHealth = HEALTH_PATHS.includes(path6.replace(/\/+$/, ""));
913
+ const [gt0, agents, err] = resolveAuth();
914
+ if (err) return isHealth ? [true, ""] : [false, ""];
915
+ if (!gt0 && Object.keys(agents).length === 0) return [true, ""];
916
+ if (isHealth) return [true, ""];
917
+ const tok = bearer(headers);
918
+ if (!tok) return [false, ""];
919
+ if (path6.startsWith("/console/api")) {
920
+ const consoleTok = env("AGENT_MOSS_CONSOLE_TOKEN");
921
+ if (consoleTok && constEq(tok, consoleTok)) return [true, "console"];
922
+ }
923
+ const gt = gt0;
924
+ if (gt && constEq(tok, gt)) return [true, "global"];
925
+ for (const [agent, expected] of Object.entries(agents)) {
926
+ if (constEq(tok, expected)) return [true, `agent:${agent}`];
927
+ }
928
+ return [false, ""];
929
+ }
930
+ function bindAgent(principal, agentId) {
931
+ const tokens = agentTokens();
932
+ if (Object.keys(tokens).length === 0) return [true, agentId];
933
+ if (!principal.startsWith("agent:")) return [true, agentId];
934
+ const bound = principal.slice(6);
935
+ if (!agentId) return [true, bound];
936
+ return [bound === agentId, bound];
937
+ }
938
+ function principalOf(headers, path6 = "") {
939
+ const [ok, principal] = checkRequestAuth(headers, path6);
940
+ return ok ? principal : "";
941
+ }
942
+ function principalHash(principal) {
943
+ if (!principal) return "";
944
+ return createHash2("sha256").update(principal, "utf-8").digest("hex").slice(0, 12);
945
+ }
946
+ function homeDir() {
947
+ try {
948
+ return homedir2();
949
+ } catch {
950
+ return tmpdir();
951
+ }
952
+ }
953
+ function logRoots() {
954
+ const raw = env("AGENT_MOSS_LOG_ROOTS");
955
+ if (raw) return splitList(raw);
956
+ return [.../* @__PURE__ */ new Set([homeDir(), "/tmp", tmpdir()])];
957
+ }
958
+ function reviewRoots() {
959
+ const roots = [join4(homeDir(), ".local", "share", "agent_gate")];
960
+ for (const name of ["AGENT_GATE_DB", "AGENT_GATE_JSON_PATH"]) {
961
+ const v = env(name);
962
+ if (v) roots.push(existsSync3(v) && lstatSync(v).isDirectory() ? v : dirname2(v));
963
+ }
964
+ roots.push(...splitList(env("AGENT_MOSS_REVIEW_PATHS")));
965
+ return [...new Set(roots)];
966
+ }
967
+ function realpathLoose(p) {
968
+ const abs = resolve2(p.replace(/^~(?=$|\/)/, homeDir()));
969
+ try {
970
+ return realpathSync(abs);
971
+ } catch {
972
+ }
973
+ const parts = [];
974
+ let cur = abs;
975
+ for (; ; ) {
976
+ const parent = dirname2(cur);
977
+ if (parent === cur) return null;
978
+ parts.unshift(basename(cur));
979
+ cur = parent;
980
+ try {
981
+ return join4(realpathSync(cur), ...parts);
982
+ } catch {
983
+ }
984
+ }
985
+ }
986
+ function pathWithin(p, roots) {
987
+ if (!p) return true;
988
+ const target = realpathLoose(p);
989
+ if (target === null) return false;
990
+ for (const root of roots) {
991
+ const rootReal = realpathLoose(root);
992
+ if (rootReal === null) continue;
993
+ const rel = relative(rootReal, target);
994
+ if (rel === "" || !rel.startsWith("..") && !isAbsolute(rel)) return true;
995
+ }
996
+ return false;
997
+ }
998
+ function ensurePrivateDir(dir) {
999
+ try {
1000
+ mkdirSync(dir, { recursive: true });
1001
+ if (lstatSync(dir).isSymbolicLink()) return false;
1002
+ if (typeof process.getuid === "function" && lstatSync(dir).uid !== process.getuid()) return false;
1003
+ chmodSync(dir, 448);
1004
+ return true;
1005
+ } catch {
1006
+ return false;
1007
+ }
1008
+ }
1009
+ function writePrivateText(file, text) {
1010
+ try {
1011
+ const flags = __require("fs").constants;
1012
+ let openFlags = flags.O_WRONLY | flags.O_CREAT | flags.O_TRUNC;
1013
+ if (flags.O_NOFOLLOW) openFlags |= flags.O_NOFOLLOW;
1014
+ const fd = openSync(file, openFlags, 384);
1015
+ try {
1016
+ writeSync(fd, text);
1017
+ } finally {
1018
+ closeSync(fd);
1019
+ }
1020
+ return true;
1021
+ } catch {
1022
+ return false;
1023
+ }
1024
+ }
1025
+ function perRequestLlmEnabled() {
1026
+ return TRUTHY2.includes(env("AGENT_MOSS_ALLOW_PER_REQUEST_LLM", "1").toLowerCase());
1027
+ }
1028
+ function validateLlmBaseUrl(url, allowPrivate) {
1029
+ if (!url) return [true, ""];
1030
+ let u;
1031
+ try {
1032
+ u = new URL(url);
1033
+ } catch {
1034
+ return [false, "baseUrl \u4E0D\u662F\u5408\u6CD5 URL"];
1035
+ }
1036
+ if (u.protocol !== "http:" && u.protocol !== "https:") {
1037
+ return [false, `baseUrl \u534F\u8BAE\u4E0D\u53D7\u652F\u6301: ${u.protocol}`];
1038
+ }
1039
+ const host = (u.hostname || "").toLowerCase();
1040
+ if (!host) return [false, "baseUrl \u7F3A\u5C11\u4E3B\u673A\u540D"];
1041
+ if (BLOCKED_HOSTS.includes(host)) return [false, `baseUrl \u6307\u5411\u4E91\u5143\u6570\u636E\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1042
+ if (/^169\.254\.\d{1,3}\.\d{1,3}$/.test(host)) {
1043
+ return [false, `baseUrl \u6307\u5411\u94FE\u8DEF\u672C\u5730\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1044
+ }
1045
+ const allowlist = splitList(env("AGENT_MOSS_LLM_HOST_ALLOWLIST")).map((h) => h.toLowerCase());
1046
+ if (allowlist.length && !allowlist.includes(host)) {
1047
+ return [false, `baseUrl \u4E3B\u673A\u4E0D\u5728\u5141\u8BB8\u5217\u8868: ${host}`];
1048
+ }
1049
+ if (allowPrivate === void 0) {
1050
+ allowPrivate = TRUTHY2.includes(env("AGENT_MOSS_TRUST_PRIVATE_LLM", "1").toLowerCase());
1051
+ }
1052
+ if (!allowPrivate) {
1053
+ if (host === "localhost" || host === "::1") return [false, "baseUrl \u6307\u5411\u672C\u673A\uFF0C\u5DF2\u62D2\u7EDD"];
1054
+ const m = host.match(/^(\d+)\.(\d+)\.(\d+)\.(\d+)$/);
1055
+ if (m) {
1056
+ const [a, b] = [Number(m[1]), Number(m[2])];
1057
+ if (a === 10 || a === 172 && b >= 16 && b <= 31 || a === 192 && b === 168 || a === 127 || a === 169 && b === 254) {
1058
+ return [false, `baseUrl \u6307\u5411\u5185\u7F51\u5730\u5740\uFF0C\u5DF2\u62D2\u7EDD: ${host}`];
1059
+ }
1060
+ }
1061
+ }
1062
+ return [true, ""];
1063
+ }
1064
+ function hardenExistingPaths() {
1065
+ try {
1066
+ const roots = [join4(homeDir(), ".config", "agentmoss"), join4(homeDir(), ".agentmoss")];
1067
+ const names = [
1068
+ "agent_moss_runtime.json",
1069
+ "config.json",
1070
+ "agent_moss_settings.json",
1071
+ "agent_moss_token_stats.json",
1072
+ "stateful_chains.db",
1073
+ "stateful_chains.db-wal",
1074
+ "stateful_chains.db-shm"
1075
+ ];
1076
+ for (const root of roots) {
1077
+ try {
1078
+ if (existsSync3(root) && !lstatSync(root).isSymbolicLink()) chmodSync(root, 448);
1079
+ } catch {
1080
+ }
1081
+ for (const n of names) {
1082
+ const p = join4(root, n);
1083
+ try {
1084
+ if (existsSync3(p) && !lstatSync(p).isSymbolicLink()) chmodSync(p, 384);
1085
+ } catch {
1086
+ }
1087
+ }
1088
+ }
1089
+ } catch {
1090
+ }
1091
+ }
1092
+ function chainDetailMode() {
1093
+ return (env("AGENT_MOSS_CHAIN_DETAIL", "redacted") || "redacted").toLowerCase();
1094
+ }
1095
+ function redactChain(chain, currentSessionId) {
1096
+ if (!chain || chain.length === 0) return [];
1097
+ if (chainDetailMode() === "full") return [...chain];
1098
+ return chain.map((step) => {
1099
+ if (Array.isArray(step) && step.length >= 3) {
1100
+ const [sid, seq, , ts] = step;
1101
+ if (String(sid) === String(currentSessionId)) return [...step];
1102
+ return ["<other-session>", seq, "<\u5DF2\u9690\u85CF>", ts ?? ""];
1103
+ }
1104
+ return step;
1105
+ });
1106
+ }
1107
+ function sessionSweepThreshold() {
1108
+ return envInt("AGENT_MOSS_SESSION_SWEEP_THRESHOLD", 50);
1109
+ }
1110
+ function sessionSweepWindow() {
1111
+ return envInt("AGENT_MOSS_SESSION_SWEEP_WINDOW_SEC", 300);
1112
+ }
1113
+ function sessionSweepAction() {
1114
+ return (env("AGENT_MOSS_SESSION_SWEEP_ACTION", "log") || "log").toLowerCase();
1115
+ }
1116
+ function noteSessionAccess(key, sessionId) {
1117
+ const limit = sessionSweepThreshold();
1118
+ if (limit <= 0 || !sessionId) return [0, false];
1119
+ const now = Date.now() / 1e3;
1120
+ const cutoff = now - sessionSweepWindow();
1121
+ let entries = (sessionWindow.get(key) ?? []).filter(([t]) => t >= cutoff);
1122
+ entries.push([now, sessionId]);
1123
+ if (entries.length > limit * 4) entries = entries.slice(-limit * 4);
1124
+ sessionWindow.set(key, entries);
1125
+ if (sessionWindow.size > 1e4) sessionWindow.clear();
1126
+ const distinct = new Set(entries.map(([, s]) => s)).size;
1127
+ return [distinct, distinct > limit];
1128
+ }
1129
+ function queueAbuseThreshold() {
1130
+ return envInt("AGENT_MOSS_QUEUE_ABUSE_THRESHOLD", 10);
1131
+ }
1132
+ function queueAbuseWindow() {
1133
+ return envInt("AGENT_MOSS_QUEUE_ABUSE_WINDOW_SEC", 60);
1134
+ }
1135
+ function queueAbuseAction() {
1136
+ return (env("AGENT_MOSS_QUEUE_ABUSE_ACTION", "log") || "log").toLowerCase();
1137
+ }
1138
+ function noteQueueReject(key) {
1139
+ const limit = queueAbuseThreshold();
1140
+ if (limit <= 0 || !key) return [0, false];
1141
+ const now = Date.now() / 1e3;
1142
+ const cutoff = now - queueAbuseWindow();
1143
+ let entries = (queueRejects.get(key) ?? []).filter((t) => t >= cutoff);
1144
+ entries.push(now);
1145
+ if (entries.length > limit * 4) entries = entries.slice(-limit * 4);
1146
+ queueRejects.set(key, entries);
1147
+ if (queueRejects.size > 1e4) queueRejects.clear();
1148
+ return [entries.length, entries.length > limit];
1149
+ }
1150
+ function socketMode() {
1151
+ const raw = env("AGENT_MOSS_SOCKET_MODE", "600");
1152
+ const mode = parseInt(raw, 8);
1153
+ if (!Number.isFinite(mode) || mode < 0 || mode > 511) {
1154
+ console.warn(`[agent-moss] AGENT_MOSS_SOCKET_MODE=${raw} \u975E\u6CD5\uFF0C\u56DE\u9000 600`);
1155
+ return 384;
1156
+ }
1157
+ return mode;
1158
+ }
1159
+ function errorDetailMode() {
1160
+ return (env("AGENT_MOSS_ERROR_DETAIL", "generic") || "generic").toLowerCase();
1161
+ }
1162
+ function maxBodyBytes() {
1163
+ return envInt("AGENT_MOSS_MAX_BODY_BYTES", 16 * 1024 * 1024);
1164
+ }
1165
+ function poolOf(path6) {
1166
+ if (path6 === "/api/v1/analyze" || path6.startsWith("/api/v1/analyze/")) return "biz";
1167
+ if (path6 === "/api/v1/review" || path6.startsWith("/api/v1/review/")) return "review";
1168
+ if (path6 === "/console" || path6.startsWith("/console/") || path6.startsWith("/api/v1/brain")) return "ops";
1169
+ if (path6 === "/api/v1/health") return "health";
1170
+ return "other";
1171
+ }
1172
+ function poolLimit(name) {
1173
+ let dflt = POOL_DEFAULTS[name] ?? 4;
1174
+ if (name === "biz") dflt = envInt("AGENT_MOSS_MAX_INFLIGHT", dflt);
1175
+ return envInt(`AGENT_MOSS_POOL_${name.toUpperCase()}`, dflt);
1176
+ }
1177
+ function unauthInflight() {
1178
+ return envInt("AGENT_MOSS_MAX_UNAUTH_INFLIGHT", 4);
1179
+ }
1180
+ function unauthPerIp() {
1181
+ return envInt("AGENT_MOSS_MAX_UNAUTH_PER_IP", 2);
1182
+ }
1183
+ function agentInflight() {
1184
+ return envInt("AGENT_MOSS_MAX_AGENT_INFLIGHT", 8);
1185
+ }
1186
+ function boundAgentOf(principal) {
1187
+ return principal.startsWith("agent:") ? principal.slice("agent:".length) : "";
1188
+ }
1189
+ function rateLimitQps() {
1190
+ return envInt("AGENT_MOSS_RATE_LIMIT_QPS", 100);
1191
+ }
1192
+ function maxConnections() {
1193
+ return envInt("AGENT_MOSS_MAX_CONNECTIONS", 128);
1194
+ }
1195
+ function keepAliveSec() {
1196
+ return envInt("AGENT_MOSS_KEEP_ALIVE_SEC", 5);
1197
+ }
1198
+ var TRUTHY2, HEALTH_PATHS, AUTH_MODE_AUTO, AUTH_MODE_OFF, tokenCache, fdTokenCache, BLOCKED_HOSTS, sessionWindow, queueRejects, POOL_DEFAULTS;
1199
+ var init_security_policy = __esm({
1200
+ "src/infra/security-policy.ts"() {
1201
+ "use strict";
1202
+ init_esm_shims();
1203
+ init_paths();
1204
+ TRUTHY2 = ["1", "true", "yes", "on"];
1205
+ HEALTH_PATHS = ["/api/v1/health"];
1206
+ AUTH_MODE_AUTO = "auto";
1207
+ AUTH_MODE_OFF = "off";
1208
+ tokenCache = null;
1209
+ fdTokenCache = /* @__PURE__ */ new Map();
1210
+ BLOCKED_HOSTS = ["169.254.169.254", "metadata.google.internal", "metadata.tencentyun.com"];
1211
+ sessionWindow = /* @__PURE__ */ new Map();
1212
+ queueRejects = /* @__PURE__ */ new Map();
1213
+ POOL_DEFAULTS = { biz: 16, review: 1, ops: 4, health: 4, other: 4 };
1214
+ }
1215
+ });
1216
+
719
1217
  // src/engine/shell-split.ts
720
1218
  function hasCommandSubstitution(text) {
721
1219
  return CMD_SUBSTITUTION.test(text);
@@ -1291,7 +1789,7 @@ function checkReadBeforeWrite(actionType, actionDetail, actionHistory, reason, c
1291
1789
  targetPaths.push(...segPathsFiltered);
1292
1790
  }
1293
1791
  }
1294
- const uniqueTargetPaths = [...new Set(targetPaths)];
1792
+ const uniqueTargetPaths = [...new Set(targetPaths)].filter((p) => !PSEUDO_FS_PREFIXES.some((pfx) => p.startsWith(pfx)));
1295
1793
  if (uniqueTargetPaths.length === 0) return null;
1296
1794
  const historyReadPaths = /* @__PURE__ */ new Set();
1297
1795
  for (const action of actionHistory) {
@@ -1470,7 +1968,7 @@ function checkIndirectFileAccess(actionType, actionDetail, cwd, osType) {
1470
1968
  violatedLayers: ["L2.1"]
1471
1969
  };
1472
1970
  }
1473
- var _LOGIC, _HEURISTIC, EXCLUDED_PREFIXES, TEMP_DIRS, ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW, _IFA, IFA_READ_KEYWORDS, IFA_WRITE_KEYWORDS, IFA_EXEC_KEYWORDS, IFA_OPEN_KEYWORDS;
1971
+ var _LOGIC, _HEURISTIC, EXCLUDED_PREFIXES, TEMP_DIRS, ROOT_SENSITIVE_PATHS, HOME_SENSITIVE_PATHS, CREDENTIAL_PATHS, ROOT_SENSITIVE_PATHS_WINDOWS, HOME_SENSITIVE_PATHS_WINDOWS, CREDENTIAL_PATHS_WINDOWS, ROOT_SENSITIVE_PATHS_MACOS, HOME_SENSITIVE_PATHS_MACOS, CREDENTIAL_PATHS_MACOS, ROOT_SENSITIVE_PATHS_HARMONY, HOME_SENSITIVE_PATHS_HARMONY, CREDENTIAL_PATHS_HARMONY, _scopeIndex, NON_WRITE_TOOLS, NON_WRITE_TOOL_PREFIXES, PSEUDO_FS_PREFIXES, WRITE_KEYWORDS, SED_INPLACE_RE, DEVNULL_REDIRECT_RE, CREATE_KEYWORDS, PASSWORD_MODIFY_PATTERNS, USER_DELETION_PATTERNS, INTENT_DEVIATION_PATTERNS, COMBO_STAGES, COMBO_CHAINS, INLINE_SCRIPT_RE, DIR_TRAVERSAL_KW, _IFA, IFA_READ_KEYWORDS, IFA_WRITE_KEYWORDS, IFA_EXEC_KEYWORDS, IFA_OPEN_KEYWORDS;
1474
1972
  var init_logic_rules = __esm({
1475
1973
  "src/engine/logic-rules.ts"() {
1476
1974
  "use strict";
@@ -1517,6 +2015,7 @@ var init_logic_rules = __esm({
1517
2015
  "cat"
1518
2016
  ]);
1519
2017
  NON_WRITE_TOOL_PREFIXES = _LOGIC.non_write_tool_prefixes ?? [];
2018
+ PSEUDO_FS_PREFIXES = ["/sys/", "/proc/", "/dev/", "/run/"];
1520
2019
  WRITE_KEYWORDS = ["write", "\u5199\u5165", "save", "\u4FDD\u5B58", "modify", "\u4FEE\u6539", "update", "\u66F4\u65B0", "delete", "\u5220\u9664", "remove", "\u79FB\u9664", "create", "\u521B\u5EFA", "append", "\u8FFD\u52A0", "truncate", "\u622A\u65AD", "overwrite", "\u8986\u76D6", "replace", "\u66FF\u6362", "edit"];
1521
2020
  SED_INPLACE_RE = /\bsed\s+(?:-\S+\s+)*(?:-[a-zA-Z]*i[a-zA-Z]*|--in-place)\b/i;
1522
2021
  DEVNULL_REDIRECT_RE = /(?:2|&|1)?\s*>\s*\/dev\/null\b/g;
@@ -1567,22 +2066,22 @@ var init_logic_rules = __esm({
1567
2066
  });
1568
2067
 
1569
2068
  // src/engine/asset-dir.ts
1570
- import { existsSync as existsSync4 } from "fs";
1571
- import { join as join4, resolve as resolve2 } from "path";
2069
+ import { existsSync as existsSync5 } from "fs";
2070
+ import { join as join5, resolve as resolve3 } from "path";
1572
2071
  function assetDirCandidates(moduleDir, name) {
1573
2072
  const root = getRulesRoot();
1574
2073
  if (root) {
1575
- const p = join4(root, name);
2074
+ const p = join5(root, name);
1576
2075
  return [p, p];
1577
2076
  }
1578
2077
  return [
1579
- resolve2(moduleDir, name),
1580
- resolve2(moduleDir, "../../..", "agent_moss", name)
2078
+ resolve3(moduleDir, name),
2079
+ resolve3(moduleDir, "../../..", "agent_moss", name)
1581
2080
  ];
1582
2081
  }
1583
2082
  function resolveAssetDir(moduleDir, name) {
1584
2083
  for (const dir of assetDirCandidates(moduleDir, name)) {
1585
- if (existsSync4(dir)) return dir;
2084
+ if (existsSync5(dir)) return dir;
1586
2085
  }
1587
2086
  return null;
1588
2087
  }
@@ -1595,8 +2094,8 @@ var init_asset_dir = __esm({
1595
2094
  });
1596
2095
 
1597
2096
  // src/engine/skill-loader.ts
1598
- import { readFileSync as readFileSync4, readdirSync as readdirSync3, existsSync as existsSync5 } from "fs";
1599
- import { resolve as resolve3, dirname as dirname2 } from "path";
2097
+ import { readFileSync as readFileSync5, readdirSync as readdirSync3, existsSync as existsSync6 } from "fs";
2098
+ import { resolve as resolve4, dirname as dirname3 } from "path";
1600
2099
  import { fileURLToPath as fileURLToPath3 } from "url";
1601
2100
  function loadAllSkills() {
1602
2101
  if (cachedSkills) return cachedSkills;
@@ -1610,13 +2109,13 @@ function loadAllSkills() {
1610
2109
  const files = readdirSync3(dir).filter((f) => f.endsWith(".md"));
1611
2110
  for (const f of files) {
1612
2111
  const name = f.replace(".md", "");
1613
- cachedSkills[name] = readFileSync4(resolve3(dir, f), "utf-8");
2112
+ cachedSkills[name] = readFileSync5(resolve4(dir, f), "utf-8");
1614
2113
  }
1615
2114
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedSkills).length} skills from ${dir}`);
1616
2115
  return cachedSkills;
1617
2116
  }
1618
2117
  function userSkillsDir() {
1619
- return resolve3(getDataRoot(), "agent_moss_skills");
2118
+ return resolve4(getDataRoot(), "agent_moss_skills");
1620
2119
  }
1621
2120
  function runtimeCustomSkillKeywords() {
1622
2121
  if (isRuntimeConfigDisabled()) return {};
@@ -1640,8 +2139,8 @@ function loadUserSkill(name) {
1640
2139
  if (isRuntimeConfigDisabled()) return "";
1641
2140
  if (userSkillCache[name] !== void 0) return userSkillCache[name];
1642
2141
  try {
1643
- const p = resolve3(userSkillsDir(), `${name}.md`);
1644
- userSkillCache[name] = existsSync5(p) ? readFileSync4(p, "utf-8") : "";
2142
+ const p = resolve4(userSkillsDir(), `${name}.md`);
2143
+ userSkillCache[name] = existsSync6(p) ? readFileSync5(p, "utf-8") : "";
1645
2144
  } catch {
1646
2145
  userSkillCache[name] = "";
1647
2146
  }
@@ -1710,7 +2209,7 @@ var init_skill_loader = __esm({
1710
2209
  init_asset_dir();
1711
2210
  init_runtime_config_bridge();
1712
2211
  __filename3 = fileURLToPath3(import.meta.url);
1713
- __dirname3 = dirname2(__filename3);
2212
+ __dirname3 = dirname3(__filename3);
1714
2213
  SKILL_KEYWORD_MAP = (() => {
1715
2214
  const map = {};
1716
2215
  const cats = loadL3();
@@ -1790,10 +2289,10 @@ __export(runtime_config_exports, {
1790
2289
  updateSkillCategoryEnabled: () => updateSkillCategoryEnabled,
1791
2290
  updateSkillEnabled: () => updateSkillEnabled
1792
2291
  });
1793
- import { readFileSync as readFileSync5, writeFileSync, existsSync as existsSync6, mkdirSync, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
1794
- import { join as join5 } from "path";
2292
+ import { readFileSync as readFileSync6, writeFileSync, existsSync as existsSync7, mkdirSync as mkdirSync2, unlinkSync, readdirSync as readdirSync4, statSync as statSync2 } from "fs";
2293
+ import { join as join6 } from "path";
1795
2294
  import { fileURLToPath as fileURLToPath4 } from "url";
1796
- import { dirname as dirname3 } from "path";
2295
+ import { dirname as dirname4 } from "path";
1797
2296
  function runtimeDir() {
1798
2297
  return getRuntimeConfigDir();
1799
2298
  }
@@ -1820,7 +2319,7 @@ function saveRuntimeConfig(config) {
1820
2319
  }
1821
2320
  const p = getRuntimeConfigPath();
1822
2321
  const dir = p.substring(0, p.lastIndexOf("/"));
1823
- if (dir && !existsSync6(dir)) mkdirSync(dir, { recursive: true });
2322
+ if (dir && !existsSync7(dir)) mkdirSync2(dir, { recursive: true });
1824
2323
  writeFileSync(p, JSON.stringify(config, null, 2), "utf-8");
1825
2324
  invalidateRuntimeConfigCache();
1826
2325
  }
@@ -1854,7 +2353,7 @@ function loadRuntimeConfig() {
1854
2353
  };
1855
2354
  }
1856
2355
  const p = getRuntimeConfigPath();
1857
- if (!existsSync6(p)) {
2356
+ if (!existsSync7(p)) {
1858
2357
  const seeded = mergeConfig({}, generateSourceDefaults());
1859
2358
  try {
1860
2359
  saveRuntimeConfig(seeded);
@@ -1868,7 +2367,7 @@ function loadRuntimeConfig() {
1868
2367
  if (runtimeConfigCache && runtimeConfigCache.key === cacheKey) {
1869
2368
  return runtimeConfigCache.value;
1870
2369
  }
1871
- const data = JSON.parse(readFileSync5(p, "utf-8"));
2370
+ const data = JSON.parse(readFileSync6(p, "utf-8"));
1872
2371
  const merged = mergeConfig(data, generateSourceDefaults());
1873
2372
  const result = {
1874
2373
  version: data.version || 1,
@@ -2016,7 +2515,7 @@ function hasRuntimeConfig() {
2016
2515
  if (cached2 !== void 0) return cached2;
2017
2516
  let ok = false;
2018
2517
  try {
2019
- JSON.parse(readFileSync5(p, "utf-8"));
2518
+ JSON.parse(readFileSync6(p, "utf-8"));
2020
2519
  ok = true;
2021
2520
  } catch {
2022
2521
  ok = false;
@@ -2131,7 +2630,7 @@ function _l2SensitivePathsToRules() {
2131
2630
  const all = [...root, ...home, ...credential];
2132
2631
  for (const sp of all) {
2133
2632
  const os4 = String(sp.os || "linux").toLowerCase();
2134
- const denyMode = _deriveDenyMode({
2633
+ const denyMode = sp.denyMode || _deriveDenyMode({
2135
2634
  deny_mode: sp.deny_mode,
2136
2635
  credential: sp.credential,
2137
2636
  read_only: sp.read_only
@@ -2237,8 +2736,8 @@ function _l3SkillsToRules() {
2237
2736
  function _scenarioRulesToDefaults() {
2238
2737
  const out = {};
2239
2738
  const _root = getRulesRoot();
2240
- const scenDir = _root ? join5(_root, "scenarios") : existsSync6(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync6(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2241
- if (!existsSync6(scenDir)) return out;
2739
+ const scenDir = _root ? join6(_root, "scenarios") : existsSync7(_SCENARIOS_DIR_PROD) ? _SCENARIOS_DIR_PROD : existsSync7(_SCENARIOS_DIR_DEV) ? _SCENARIOS_DIR_DEV : _SCENARIOS_DIR_PROD;
2740
+ if (!existsSync7(scenDir)) return out;
2242
2741
  let entries;
2243
2742
  try {
2244
2743
  entries = readdirSync4(scenDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2246,11 +2745,11 @@ function _scenarioRulesToDefaults() {
2246
2745
  return out;
2247
2746
  }
2248
2747
  for (const scenarioId of entries) {
2249
- const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join5(scenDir, scenarioId, "rules.json");
2250
- if (!existsSync6(rulesFile)) continue;
2748
+ const rulesFile = resolveScenarioRulesFile(scenarioId) ?? join6(scenDir, scenarioId, "rules.json");
2749
+ if (!existsSync7(rulesFile)) continue;
2251
2750
  let data;
2252
2751
  try {
2253
- data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2752
+ data = JSON.parse(readFileSync6(rulesFile, "utf-8"));
2254
2753
  } catch {
2255
2754
  continue;
2256
2755
  }
@@ -2287,8 +2786,8 @@ function _scenarioRulesToDefaults() {
2287
2786
  function _agentRulesToDefaults() {
2288
2787
  const out = {};
2289
2788
  const _root = getRulesRoot();
2290
- const agentsDir = _root ? join5(_root, "agents") : existsSync6(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync6(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2291
- if (!existsSync6(agentsDir)) return out;
2789
+ const agentsDir = _root ? join6(_root, "agents") : existsSync7(_AGENTS_DIR_PROD) ? _AGENTS_DIR_PROD : existsSync7(_AGENTS_DIR_DEV) ? _AGENTS_DIR_DEV : _AGENTS_DIR_PROD;
2790
+ if (!existsSync7(agentsDir)) return out;
2292
2791
  let entries;
2293
2792
  try {
2294
2793
  entries = readdirSync4(agentsDir, { withFileTypes: true }).filter((d) => d.isDirectory()).map((d) => d.name);
@@ -2296,11 +2795,11 @@ function _agentRulesToDefaults() {
2296
2795
  return out;
2297
2796
  }
2298
2797
  for (const agentId of entries) {
2299
- const rulesFile = join5(agentsDir, agentId, "rules.json");
2300
- if (!existsSync6(rulesFile)) continue;
2798
+ const rulesFile = join6(agentsDir, agentId, "rules.json");
2799
+ if (!existsSync7(rulesFile)) continue;
2301
2800
  let data;
2302
2801
  try {
2303
- data = JSON.parse(readFileSync5(rulesFile, "utf-8"));
2802
+ data = JSON.parse(readFileSync6(rulesFile, "utf-8"));
2304
2803
  } catch {
2305
2804
  continue;
2306
2805
  }
@@ -2908,12 +3407,12 @@ function addCustomSkill(skillId, category, keywords, content) {
2908
3407
  );
2909
3408
  }
2910
3409
  const runtime = loadRuntimeConfig();
2911
- const skillsDir = join5(getRuntimeConfigDir(), "agent_moss_skills");
3410
+ const skillsDir = join6(getRuntimeConfigDir(), "agent_moss_skills");
2912
3411
  try {
2913
- mkdirSync(skillsDir, { recursive: true });
3412
+ mkdirSync2(skillsDir, { recursive: true });
2914
3413
  } catch {
2915
3414
  }
2916
- writeFileSync(join5(skillsDir, `${skillId}.md`), content, "utf-8");
3415
+ writeFileSync(join6(skillsDir, `${skillId}.md`), content, "utf-8");
2917
3416
  const l3 = runtime.L3_skills || (runtime.L3_skills = {});
2918
3417
  const cat = l3[category] || (l3[category] = { category_enabled: true, category_desc: "", skills: [] });
2919
3418
  (cat.skills || (cat.skills = [])).push({ id: skillId, keywords, enabled: true, builtin: false });
@@ -2928,9 +3427,9 @@ function deleteCustomSkill(skillId, category) {
2928
3427
  if (idx >= 0) {
2929
3428
  if (skills[idx].builtin) return null;
2930
3429
  skills.splice(idx, 1);
2931
- const skillPath = join5(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
3430
+ const skillPath = join6(getRuntimeConfigDir(), "agent_moss_skills", `${skillId}.md`);
2932
3431
  try {
2933
- if (existsSync6(skillPath)) unlinkSync(skillPath);
3432
+ if (existsSync7(skillPath)) unlinkSync(skillPath);
2934
3433
  } catch {
2935
3434
  }
2936
3435
  saveRuntimeConfig(runtime);
@@ -3134,10 +3633,10 @@ var init_runtime_config = __esm({
3134
3633
  init_rules_loader();
3135
3634
  init_runtime_config_bridge();
3136
3635
  init_rules_mode();
3137
- __dirname4 = dirname3(fileURLToPath4(import.meta.url));
3636
+ __dirname4 = dirname4(fileURLToPath4(import.meta.url));
3138
3637
  ENV_RUNTIME_CONFIG_PATH = "AGENT_MOSS_RUNTIME_CONFIG_PATH";
3139
3638
  RUNTIME_CONFIG_DIR = runtimeDir();
3140
- RUNTIME_CONFIG_PATH = join5(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3639
+ RUNTIME_CONFIG_PATH = join6(RUNTIME_CONFIG_DIR, "agent_moss_runtime.json");
3141
3640
  LAYER_DISABLE_ENV = {
3142
3641
  L1_heuristic: "AGENT_MOSS_DISABLE_HEURISTIC",
3143
3642
  L2_logic_rules: "AGENT_MOSS_DISABLE_LOGIC_RULES",
@@ -3165,10 +3664,10 @@ var init_runtime_config = __esm({
3165
3664
  intent_consistency: ["intent_keywords", "reason"]
3166
3665
  };
3167
3666
  hasRuntimeConfigCache = /* @__PURE__ */ new Map();
3168
- _AGENTS_DIR_PROD = join5(__dirname4, "agents");
3169
- _AGENTS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "agents");
3170
- _SCENARIOS_DIR_PROD = join5(__dirname4, "scenarios");
3171
- _SCENARIOS_DIR_DEV = join5(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3667
+ _AGENTS_DIR_PROD = join6(__dirname4, "agents");
3668
+ _AGENTS_DIR_DEV = join6(__dirname4, "..", "..", "..", "agent_moss", "agents");
3669
+ _SCENARIOS_DIR_PROD = join6(__dirname4, "scenarios");
3670
+ _SCENARIOS_DIR_DEV = join6(__dirname4, "..", "..", "..", "agent_moss", "scenarios");
3172
3671
  _effectiveCache = /* @__PURE__ */ new WeakMap();
3173
3672
  SKILL_ID_RE = /^[A-Za-z0-9_-]+$/;
3174
3673
  registerRuntimeConfigBridge({
@@ -3506,18 +4005,23 @@ var init_config = __esm({
3506
4005
  });
3507
4006
 
3508
4007
  // src/agents/index.ts
3509
- import { readFileSync as readFileSync6, existsSync as existsSync7 } from "fs";
3510
- import { resolve as resolve4, dirname as dirname4, join as join6 } from "path";
4008
+ import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
4009
+ import { resolve as resolve5, dirname as dirname5, join as join7 } from "path";
3511
4010
  import { fileURLToPath as fileURLToPath6 } from "url";
3512
4011
  function resolveAgentsDir() {
3513
4012
  const root = getRulesRoot();
3514
- if (root) return join6(root, "agents");
3515
- if (existsSync7(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
3516
- if (existsSync7(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
4013
+ if (root) return join7(root, "agents");
4014
+ if (existsSync8(AGENTS_DIR_PROD)) return AGENTS_DIR_PROD;
4015
+ if (existsSync8(AGENTS_DIR_DEV_FALLBACK)) return AGENTS_DIR_DEV_FALLBACK;
3517
4016
  return AGENTS_DIR_PROD;
3518
4017
  }
3519
4018
  function getAgentExtensions(agentId) {
3520
4019
  if (!agentId) return null;
4020
+ if (!isSafeId(agentId)) {
4021
+ console.warn(`[AgentMoss agents] \u975E\u6CD5 agent_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${agentId.slice(0, 120)}`);
4022
+ return null;
4023
+ }
4024
+ if (cache.size >= CACHE_MAX) cache.clear();
3521
4025
  const fp = getRuleSourceFingerprint();
3522
4026
  const cached2 = cache.get(agentId);
3523
4027
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3530,13 +4034,13 @@ function getAgentExtensions(agentId) {
3530
4034
  }
3531
4035
  if (ext === null) {
3532
4036
  const agentsDir = resolveAgentsDir();
3533
- const rulesFile = resolve4(agentsDir, agentId, "rules.json");
3534
- if (!existsSync7(rulesFile)) {
4037
+ const rulesFile = resolve5(agentsDir, agentId, "rules.json");
4038
+ if (!existsSync8(rulesFile)) {
3535
4039
  cache.set(agentId, { fp, ext: null });
3536
4040
  return null;
3537
4041
  }
3538
4042
  try {
3539
- const raw = readFileSync6(rulesFile, "utf-8");
4043
+ const raw = readFileSync7(rulesFile, "utf-8");
3540
4044
  ext = JSON.parse(raw);
3541
4045
  if (!ext.agent_id) {
3542
4046
  console.warn(`[AgentMoss agents] rules.json \u7F3A\u5C11 agent_id \u5B57\u6BB5: ${rulesFile}`);
@@ -3570,7 +4074,7 @@ function mergeAgentExtensions(...exts) {
3570
4074
  extra_skills: nonNull.flatMap((e) => e.extra_skills || [])
3571
4075
  };
3572
4076
  }
3573
- var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache;
4077
+ var __filename4, __dirname5, AGENTS_DIR_PROD, AGENTS_DIR_DEV_FALLBACK, cache, CACHE_MAX;
3574
4078
  var init_agents = __esm({
3575
4079
  "src/agents/index.ts"() {
3576
4080
  "use strict";
@@ -3578,26 +4082,32 @@ var init_agents = __esm({
3578
4082
  init_paths();
3579
4083
  init_runtime_config();
3580
4084
  __filename4 = fileURLToPath6(import.meta.url);
3581
- __dirname5 = dirname4(__filename4);
3582
- AGENTS_DIR_PROD = resolve4(__dirname5, "agents");
3583
- AGENTS_DIR_DEV_FALLBACK = resolve4(__dirname5, "../../../agent_moss/agents");
4085
+ __dirname5 = dirname5(__filename4);
4086
+ AGENTS_DIR_PROD = resolve5(__dirname5, "agents");
4087
+ AGENTS_DIR_DEV_FALLBACK = resolve5(__dirname5, "../../../agent_moss/agents");
3584
4088
  cache = /* @__PURE__ */ new Map();
4089
+ CACHE_MAX = 512;
3585
4090
  }
3586
4091
  });
3587
4092
 
3588
4093
  // src/scenarios/index.ts
3589
- import { readFileSync as readFileSync7, existsSync as existsSync8 } from "fs";
3590
- import { resolve as resolve5, dirname as dirname5, join as join7 } from "path";
4094
+ import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4095
+ import { resolve as resolve6, dirname as dirname6, join as join8 } from "path";
3591
4096
  import { fileURLToPath as fileURLToPath7 } from "url";
3592
4097
  function resolveScenariosDir() {
3593
4098
  const root = getRulesRoot();
3594
- if (root) return join7(root, "scenarios");
3595
- if (existsSync8(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
3596
- if (existsSync8(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
4099
+ if (root) return join8(root, "scenarios");
4100
+ if (existsSync9(SCENARIOS_DIR_PROD)) return SCENARIOS_DIR_PROD;
4101
+ if (existsSync9(SCENARIOS_DIR_DEV_FALLBACK)) return SCENARIOS_DIR_DEV_FALLBACK;
3597
4102
  return SCENARIOS_DIR_PROD;
3598
4103
  }
3599
4104
  function getScenarioExtensions(scenarioId) {
3600
4105
  if (!scenarioId) return null;
4106
+ if (!isSafeId(scenarioId)) {
4107
+ console.warn(`[AgentMoss scenarios] \u975E\u6CD5 scenario_id \u5DF2\u5FFD\u7565\uFF08\u542B\u8DEF\u5F84\u5B57\u7B26\u6216\u8D85\u957F\uFF09: ${scenarioId.slice(0, 120)}`);
4108
+ return null;
4109
+ }
4110
+ if (cache2.size >= CACHE_MAX2) cache2.clear();
3601
4111
  const fp = getRuleSourceFingerprint();
3602
4112
  const cached2 = cache2.get(scenarioId);
3603
4113
  if (cached2 && cached2.fp === fp) return cached2.ext;
@@ -3616,19 +4126,19 @@ function getScenarioExtensions(scenarioId) {
3616
4126
  }
3617
4127
  }
3618
4128
  if (ext === null) {
3619
- const builtinFile = resolve5(resolveScenariosDir(), scenarioId, "rules.json");
4129
+ const builtinFile = resolve6(resolveScenariosDir(), scenarioId, "rules.json");
3620
4130
  const rulesFile = overrideHit ?? builtinFile;
3621
4131
  if (overrideHit) {
3622
4132
  console.warn(
3623
4133
  `[AgentMoss] \u26A0\uFE0F \u573A\u666F ${scenarioId} \u89C4\u5219\u88AB\u8986\u76D6\u76EE\u5F55\u8986\u76D6: ${overrideHit}\uFF08\u5185\u7F6E\u9ED8\u8BA4 ${builtinFile} \u88AB\u5FFD\u7565\uFF09\u3002\u8BE5\u6587\u4EF6\u7531\u90E8\u7F72\u4FA7\u81EA\u884C\u7EF4\u62A4\uFF0C\u5185\u7F6E\u573A\u666F\u89C4\u5219\u66F4\u65B0\u4E0D\u4F1A\u53CD\u6620\u5230\u5B83\u4E0A\u9762\u3002`
3624
4134
  );
3625
4135
  }
3626
- if (!existsSync8(rulesFile)) {
4136
+ if (!existsSync9(rulesFile)) {
3627
4137
  cache2.set(scenarioId, { fp, ext: null });
3628
4138
  return null;
3629
4139
  }
3630
4140
  try {
3631
- const raw = readFileSync7(rulesFile, "utf-8");
4141
+ const raw = readFileSync8(rulesFile, "utf-8");
3632
4142
  const data = JSON.parse(raw);
3633
4143
  const id = data.scenario_id || data.agent_id || "";
3634
4144
  if (!id) {
@@ -3667,7 +4177,7 @@ function getScenarioExtensionsMulti(scenarioSpec) {
3667
4177
  if (nonNull.length === 0) return null;
3668
4178
  return mergeAgentExtensions(...nonNull);
3669
4179
  }
3670
- var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2;
4180
+ var __filename5, __dirname6, SCENARIOS_DIR_PROD, SCENARIOS_DIR_DEV_FALLBACK, cache2, CACHE_MAX2;
3671
4181
  var init_scenarios = __esm({
3672
4182
  "src/scenarios/index.ts"() {
3673
4183
  "use strict";
@@ -3677,10 +4187,11 @@ var init_scenarios = __esm({
3677
4187
  init_runtime_config();
3678
4188
  init_rules_loader();
3679
4189
  __filename5 = fileURLToPath7(import.meta.url);
3680
- __dirname6 = dirname5(__filename5);
3681
- SCENARIOS_DIR_PROD = resolve5(__dirname6, "scenarios");
3682
- SCENARIOS_DIR_DEV_FALLBACK = resolve5(__dirname6, "../../../agent_moss/scenarios");
4190
+ __dirname6 = dirname6(__filename5);
4191
+ SCENARIOS_DIR_PROD = resolve6(__dirname6, "scenarios");
4192
+ SCENARIOS_DIR_DEV_FALLBACK = resolve6(__dirname6, "../../../agent_moss/scenarios");
3683
4193
  cache2 = /* @__PURE__ */ new Map();
4194
+ CACHE_MAX2 = 512;
3684
4195
  }
3685
4196
  });
3686
4197
 
@@ -4560,8 +5071,8 @@ var init_heuristic = __esm({
4560
5071
  });
4561
5072
 
4562
5073
  // src/engine/template-loader.ts
4563
- import { readFileSync as readFileSync8, existsSync as existsSync9 } from "fs";
4564
- import { resolve as resolve6, dirname as dirname6 } from "path";
5074
+ import { readFileSync as readFileSync9, existsSync as existsSync10 } from "fs";
5075
+ import { resolve as resolve7, dirname as dirname7 } from "path";
4565
5076
  import { fileURLToPath as fileURLToPath8 } from "url";
4566
5077
  function loadTemplates() {
4567
5078
  if (cachedTemplates) return cachedTemplates;
@@ -4573,10 +5084,10 @@ function loadTemplates() {
4573
5084
  return cachedTemplates;
4574
5085
  }
4575
5086
  for (const file of TEMPLATE_FILES) {
4576
- const filePath = resolve6(dir, file);
4577
- if (existsSync9(filePath)) {
5087
+ const filePath = resolve7(dir, file);
5088
+ if (existsSync10(filePath)) {
4578
5089
  const key = file.replace(".txt", "").replace("_template", "");
4579
- cachedTemplates[key] = readFileSync8(filePath, "utf-8");
5090
+ cachedTemplates[key] = readFileSync9(filePath, "utf-8");
4580
5091
  }
4581
5092
  }
4582
5093
  console.log(`[AgentMoss] Loaded ${Object.keys(cachedTemplates).length} templates from ${dir}`);
@@ -4589,15 +5100,15 @@ var init_template_loader = __esm({
4589
5100
  init_esm_shims();
4590
5101
  init_asset_dir();
4591
5102
  __filename6 = fileURLToPath8(import.meta.url);
4592
- __dirname7 = dirname6(__filename6);
5103
+ __dirname7 = dirname7(__filename6);
4593
5104
  TEMPLATE_FILES = ["prompt1_template.txt", "prompt2_template.txt", "security_judge_template.txt"];
4594
5105
  cachedTemplates = null;
4595
5106
  }
4596
5107
  });
4597
5108
 
4598
5109
  // src/engine/script-analyzer.ts
4599
- import { readFileSync as readFileSync9 } from "fs";
4600
- import { resolve as resolve7, join as join8 } from "path";
5110
+ import { readFileSync as readFileSync10 } from "fs";
5111
+ import { resolve as resolve8, join as join9 } from "path";
4601
5112
  function emptyResult() {
4602
5113
  return { scriptPath: "", scriptContent: "", matchedKeywords: [], lineCount: 0, isSuspicious: false, error: "", riskLevel: "", riskReason: "" };
4603
5114
  }
@@ -4650,8 +5161,8 @@ function assessKeywordCombinationRisk(matchedKeywords, scriptContent) {
4650
5161
  }
4651
5162
  function readScriptContent(scriptPath, maxLines = 500) {
4652
5163
  try {
4653
- const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join8(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve7(scriptPath);
4654
- const raw = readFileSync9(expandedPath, "utf-8");
5164
+ const expandedPath = scriptPath === "~" || scriptPath.startsWith("~/") ? join9(process.env.HOME || "/home/user", scriptPath.slice(1)) : resolve8(scriptPath);
5165
+ const raw = readFileSync10(expandedPath, "utf-8");
4655
5166
  const lines = raw.split("\n").slice(0, maxLines);
4656
5167
  return { content: lines.join("\n"), lineCount: lines.length, error: "" };
4657
5168
  } catch (e) {
@@ -4779,14 +5290,14 @@ function isSysArgv(node) {
4779
5290
  const prop = node.getChild("PropertyName");
4780
5291
  return prop !== null && srcSlice(prop) === "argv";
4781
5292
  }
4782
- function evalExpr(node, env) {
5293
+ function evalExpr(node, env2) {
4783
5294
  if (!node) return null;
4784
5295
  const name = node.name;
4785
5296
  if (name === "String") return stringValue(srcSlice(node));
4786
5297
  if (name === "Number") return srcSlice(node);
4787
5298
  if (name === "VariableName") {
4788
5299
  const id = srcSlice(node);
4789
- return id in env.vars ? env.vars[id] : null;
5300
+ return id in env2.vars ? env2.vars[id] : null;
4790
5301
  }
4791
5302
  if (name === "MemberExpression") {
4792
5303
  const bracket = node.getChild("[");
@@ -4796,13 +5307,13 @@ function evalExpr(node, env) {
4796
5307
  const idxNode = bracket.nextSibling;
4797
5308
  if (idxNode && idxNode.name === "Number") {
4798
5309
  const idx = parseInt(srcSlice(idxNode), 10);
4799
- if (idx >= 1 && idx <= env.argv.length) return env.argv[idx - 1];
5310
+ if (idx >= 1 && idx <= env2.argv.length) return env2.argv[idx - 1];
4800
5311
  return null;
4801
5312
  }
4802
5313
  }
4803
5314
  return null;
4804
5315
  }
4805
- if (isSysArgv(node)) return env.argv[0] ?? null;
5316
+ if (isSysArgv(node)) return env2.argv[0] ?? null;
4806
5317
  return null;
4807
5318
  }
4808
5319
  if (name === "CallExpression") {
@@ -4810,7 +5321,7 @@ function evalExpr(node, env) {
4810
5321
  if (func && func.name === "VariableName" && srcSlice(func) === "len") {
4811
5322
  const argList = node.getChild("ArgList");
4812
5323
  const inner = argList ? firstNamedChild(argList) : null;
4813
- if (inner && isSysArgv(inner)) return String(env.argv.length + 1);
5324
+ if (inner && isSysArgv(inner)) return String(env2.argv.length + 1);
4814
5325
  }
4815
5326
  return null;
4816
5327
  }
@@ -4822,16 +5333,16 @@ function evalExpr(node, env) {
4822
5333
  const condNode = ifKw.nextSibling;
4823
5334
  const orelseNode = elseKw.nextSibling;
4824
5335
  if (!thenNode || !condNode || !orelseNode) return null;
4825
- const cond = evalCondition(condNode, env);
4826
- if (cond === true) return evalExpr(thenNode, env);
4827
- if (cond === false) return evalExpr(orelseNode, env);
5336
+ const cond = evalCondition(condNode, env2);
5337
+ if (cond === true) return evalExpr(thenNode, env2);
5338
+ if (cond === false) return evalExpr(orelseNode, env2);
4828
5339
  return null;
4829
5340
  }
4830
5341
  if (name === "BinaryExpression") {
4831
5342
  const op = findOp(node);
4832
5343
  if (op === "+") {
4833
- const left = evalExpr(node.firstChild, env);
4834
- const right = evalExpr(lastNamedChild(node), env);
5344
+ const left = evalExpr(node.firstChild, env2);
5345
+ const right = evalExpr(lastNamedChild(node), env2);
4835
5346
  if (left !== null && right !== null) return left + right;
4836
5347
  }
4837
5348
  return null;
@@ -4864,7 +5375,7 @@ function findOp(node) {
4864
5375
  }
4865
5376
  return null;
4866
5377
  }
4867
- function evalCondition(node, env) {
5378
+ function evalCondition(node, env2) {
4868
5379
  if (!node) return null;
4869
5380
  const name = node.name;
4870
5381
  if (name === "Boolean") {
@@ -4875,7 +5386,7 @@ function evalCondition(node, env) {
4875
5386
  const op = findOp(node);
4876
5387
  if (op === "and" || op === "or") {
4877
5388
  const parts2 = namedChildren(node);
4878
- const vals = parts2.map((p) => evalCondition(p, env));
5389
+ const vals = parts2.map((p) => evalCondition(p, env2));
4879
5390
  if (op === "and") {
4880
5391
  if (vals.some((v) => v === false)) return false;
4881
5392
  if (vals.every((v) => v === true)) return true;
@@ -4889,13 +5400,13 @@ function evalCondition(node, env) {
4889
5400
  if (parts.length !== 3) return null;
4890
5401
  const [left, opNode, right] = parts;
4891
5402
  const opText = srcSlice(opNode).trim();
4892
- return evalCompare(left, opText, right, env);
5403
+ return evalCompare(left, opText, right, env2);
4893
5404
  }
4894
5405
  if (name === "UnaryExpression") {
4895
5406
  const kw = node.getChild("not");
4896
5407
  if (kw) {
4897
5408
  const operand = kw.nextSibling;
4898
- const v = evalCondition(operand, env);
5409
+ const v = evalCondition(operand, env2);
4899
5410
  return v === null ? null : !v;
4900
5411
  }
4901
5412
  return null;
@@ -4916,39 +5427,39 @@ function namedChildren(node) {
4916
5427
  }
4917
5428
  return out;
4918
5429
  }
4919
- function evalCompare(left, opText, right, env) {
5430
+ function evalCompare(left, opText, right, env2) {
4920
5431
  if (opText === "==" && left.name === "VariableName" && srcSlice(left) === "__name__") {
4921
- const rv = evalExpr(right, env);
5432
+ const rv = evalExpr(right, env2);
4922
5433
  if (rv !== null) return rv === "__main__";
4923
5434
  return null;
4924
5435
  }
4925
5436
  if (opText === "==" || opText === "!=") {
4926
- const lv = evalExpr(left, env);
4927
- const rv = evalExpr(right, env);
5437
+ const lv = evalExpr(left, env2);
5438
+ const rv = evalExpr(right, env2);
4928
5439
  if (lv !== null && rv !== null) {
4929
5440
  return opText === "==" ? lv === rv : lv !== rv;
4930
5441
  }
4931
5442
  return null;
4932
5443
  }
4933
5444
  if (opText === "in") {
4934
- const lv = evalExpr(left, env);
5445
+ const lv = evalExpr(left, env2);
4935
5446
  if (lv === null) return null;
4936
5447
  const rightName = right.name;
4937
5448
  if (rightName === "TupleExpression" || rightName === "ArrayExpression" || rightName === "SetExpression" || rightName === "ParenthesizedExpression") {
4938
5449
  const container = rightName === "ParenthesizedExpression" && right.firstChild && right.firstChild.name === "TupleExpression" ? right.firstChild : right;
4939
5450
  for (const elt of namedChildren(container)) {
4940
- const ev = evalExpr(elt, env);
5451
+ const ev = evalExpr(elt, env2);
4941
5452
  if (ev !== null && ev === lv) return true;
4942
5453
  }
4943
5454
  return false;
4944
5455
  }
4945
- const rv = evalExpr(right, env);
5456
+ const rv = evalExpr(right, env2);
4946
5457
  if (rv !== null) return rv.includes(lv);
4947
5458
  return null;
4948
5459
  }
4949
5460
  if (opText === ">" || opText === ">=" || opText === "<" || opText === "<=") {
4950
- const lv = evalExpr(left, env);
4951
- const rv = evalExpr(right, env);
5461
+ const lv = evalExpr(left, env2);
5462
+ const rv = evalExpr(right, env2);
4952
5463
  if (lv === null || rv === null) return null;
4953
5464
  const ln = parseInt(lv, 10);
4954
5465
  const rn = parseInt(rv, 10);
@@ -4960,71 +5471,71 @@ function evalCompare(left, opText, right, env) {
4960
5471
  }
4961
5472
  return null;
4962
5473
  }
4963
- function processStmts(stmts, env, lineStarts, lines, branches) {
4964
- for (const s of stmts) processStmt(s, env, lineStarts, lines, branches);
5474
+ function processStmts(stmts, env2, lineStarts, lines, branches) {
5475
+ for (const s of stmts) processStmt(s, env2, lineStarts, lines, branches);
4965
5476
  }
4966
- function invalidateEnvOnCalls(node, env) {
5477
+ function invalidateEnvOnCalls(node, env2) {
4967
5478
  const cursor = node.cursor();
4968
5479
  cursor.iterate((n) => {
4969
5480
  const nd = n.node;
4970
5481
  if (nd.name !== "CallExpression") return true;
4971
5482
  const func = nd.firstChild;
4972
5483
  if (func && func.name === "VariableName" && !PURE_BUILTINS.has(srcSlice(func))) {
4973
- for (const k of Object.keys(env.vars)) env.vars[k] = null;
5484
+ for (const k of Object.keys(env2.vars)) env2.vars[k] = null;
4974
5485
  return false;
4975
5486
  }
4976
5487
  return true;
4977
5488
  });
4978
5489
  }
4979
- function processStmt(stmt, env, lineStarts, lines, branches) {
5490
+ function processStmt(stmt, env2, lineStarts, lines, branches) {
4980
5491
  const name = stmt.name;
4981
5492
  if (name === "AssignStatement") {
4982
5493
  const target = stmt.firstChild;
4983
5494
  if (target && target.name === "VariableName") {
4984
5495
  const valNode = target.nextSibling ? target.nextSibling.nextSibling : null;
4985
- invalidateEnvOnCalls(stmt, env);
4986
- const val = evalExpr(valNode, env);
4987
- env.vars[srcSlice(target)] = val;
5496
+ invalidateEnvOnCalls(stmt, env2);
5497
+ const val = evalExpr(valNode, env2);
5498
+ env2.vars[srcSlice(target)] = val;
4988
5499
  }
4989
5500
  markNodeLines(stmt, lines, lineStarts);
4990
5501
  return;
4991
5502
  }
4992
5503
  if (name === "UpdateStatement") {
4993
5504
  const target = stmt.firstChild;
4994
- if (target && target.name === "VariableName") env.vars[srcSlice(target)] = null;
4995
- invalidateEnvOnCalls(stmt, env);
5505
+ if (target && target.name === "VariableName") env2.vars[srcSlice(target)] = null;
5506
+ invalidateEnvOnCalls(stmt, env2);
4996
5507
  markNodeLines(stmt, lines, lineStarts);
4997
5508
  return;
4998
5509
  }
4999
5510
  if (name === "IfStatement") {
5000
- processIf(stmt, env, lineStarts, lines, branches);
5511
+ processIf(stmt, env2, lineStarts, lines, branches);
5001
5512
  return;
5002
5513
  }
5003
5514
  if (name === "ForStatement" || name === "WhileStatement") {
5004
5515
  markNodeLines(stmt, lines, lineStarts);
5005
5516
  for (const body of stmt.getChildren("Body")) {
5006
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5517
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5007
5518
  }
5008
5519
  return;
5009
5520
  }
5010
5521
  if (name === "TryStatement") {
5011
5522
  markNodeLines(stmt, lines, lineStarts);
5012
5523
  for (const body of stmt.getChildren("Body")) {
5013
- processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5524
+ processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5014
5525
  }
5015
5526
  return;
5016
5527
  }
5017
5528
  if (name === "WithStatement") {
5018
5529
  markNodeLines(stmt, lines, lineStarts);
5019
5530
  const body = stmt.getChild("Body");
5020
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5531
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5021
5532
  return;
5022
5533
  }
5023
5534
  if (name === "MatchStatement") {
5024
5535
  markNodeLines(stmt, lines, lineStarts);
5025
5536
  for (const clause of stmt.getChildren("MatchClause")) {
5026
5537
  const body = clause.getChild("Body");
5027
- if (body) processStmts(bodyStmts(body), { argv: env.argv, vars: { ...env.vars } }, lineStarts, lines, branches);
5538
+ if (body) processStmts(bodyStmts(body), { argv: env2.argv, vars: { ...env2.vars } }, lineStarts, lines, branches);
5028
5539
  }
5029
5540
  return;
5030
5541
  }
@@ -5033,7 +5544,7 @@ function processStmt(stmt, env, lineStarts, lines, branches) {
5033
5544
  return;
5034
5545
  }
5035
5546
  if (name === "ExpressionStatement") {
5036
- invalidateEnvOnCalls(stmt, env);
5547
+ invalidateEnvOnCalls(stmt, env2);
5037
5548
  }
5038
5549
  markNodeLines(stmt, lines, lineStarts);
5039
5550
  }
@@ -5053,7 +5564,7 @@ function bodyStmts(body) {
5053
5564
  }
5054
5565
  return out;
5055
5566
  }
5056
- function processIf(stmt, env, lineStarts, lines, branches) {
5567
+ function processIf(stmt, env2, lineStarts, lines, branches) {
5057
5568
  const sections = [];
5058
5569
  let c = stmt.firstChild;
5059
5570
  while (c) {
@@ -5068,17 +5579,17 @@ function processIf(stmt, env, lineStarts, lines, branches) {
5068
5579
  }
5069
5580
  c = c.nextSibling;
5070
5581
  }
5071
- processIfSections(stmt, sections, 0, env, lineStarts, lines, branches);
5582
+ processIfSections(stmt, sections, 0, env2, lineStarts, lines, branches);
5072
5583
  }
5073
- function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branches) {
5584
+ function processIfSections(_stmt, sections, idx, env2, lineStarts, lines, branches) {
5074
5585
  if (idx >= sections.length) return;
5075
5586
  const s = sections[idx];
5076
5587
  if (s.cond) markNodeLines(s.cond, lines, lineStarts);
5077
5588
  if (s.isElse) {
5078
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5589
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5079
5590
  return;
5080
5591
  }
5081
- const cond = s.cond ? evalCondition(s.cond, env) : null;
5592
+ const cond = s.cond ? evalCondition(s.cond, env2) : null;
5082
5593
  branches.push({
5083
5594
  kind: "if",
5084
5595
  label: s.cond ? srcSlice(s.cond) : "",
@@ -5086,18 +5597,18 @@ function processIfSections(_stmt, sections, idx, env, lineStarts, lines, branche
5086
5597
  reachable: cond !== false
5087
5598
  });
5088
5599
  if (cond === true) {
5089
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5600
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5090
5601
  return;
5091
5602
  }
5092
5603
  if (cond === false) {
5093
- processIfSections(_stmt, sections, idx + 1, env, lineStarts, lines, branches);
5604
+ processIfSections(_stmt, sections, idx + 1, env2, lineStarts, lines, branches);
5094
5605
  return;
5095
5606
  }
5096
- if (s.body) processStmts(bodyStmts(s.body), env, lineStarts, lines, branches);
5607
+ if (s.body) processStmts(bodyStmts(s.body), env2, lineStarts, lines, branches);
5097
5608
  for (let j = idx + 1; j < sections.length; j++) {
5098
5609
  const sj = sections[j];
5099
5610
  if (sj.cond) markNodeLines(sj.cond, lines, lineStarts);
5100
- if (sj.body) processStmts(bodyStmts(sj.body), env, lineStarts, lines, branches);
5611
+ if (sj.body) processStmts(bodyStmts(sj.body), env2, lineStarts, lines, branches);
5101
5612
  }
5102
5613
  }
5103
5614
  function parseShellString(s) {
@@ -5273,7 +5784,7 @@ var init_python_backend = __esm({
5273
5784
  SOURCE = c;
5274
5785
  const sourceLines = c.split("\n");
5275
5786
  const lineStarts = buildLineStarts(c);
5276
- const env = makeEnv(entryArgs || []);
5787
+ const env2 = makeEnv(entryArgs || []);
5277
5788
  const lines = /* @__PURE__ */ new Set();
5278
5789
  const branches = [];
5279
5790
  if (sourceLines.length > MAX_LINES) {
@@ -5300,7 +5811,7 @@ var init_python_backend = __esm({
5300
5811
  };
5301
5812
  }
5302
5813
  const stmts = bodyStmts(tree);
5303
- processStmts(stmts, env, lineStarts, lines, branches);
5814
+ processStmts(stmts, env2, lineStarts, lines, branches);
5304
5815
  const reachable = reconstruct(sourceLines, lines);
5305
5816
  let calls = extractCallsFromTree(tree, lines);
5306
5817
  if (calls.length === 0) calls = extractCallsRegex(c);
@@ -5528,7 +6039,7 @@ function tokenize(content) {
5528
6039
  }
5529
6040
  return stmts;
5530
6041
  }
5531
- function evalExpr2(text, env) {
6042
+ function evalExpr2(text, env2) {
5532
6043
  const isSingleQuoted = text.length >= 2 && text[0] === "'" && text[text.length - 1] === "'";
5533
6044
  if (text.length >= 2 && (text[0] === '"' || text[0] === "'") && text[text.length - 1] === text[0]) {
5534
6045
  text = text.slice(1, -1);
@@ -5544,19 +6055,19 @@ function evalExpr2(text, env) {
5544
6055
  const dm = inner.match(/^([A-Za-z_][A-Za-z0-9_]*|\d+):?-(.*)$/s);
5545
6056
  if (dm) {
5546
6057
  const key = /^\d+$/.test(dm[1]) ? `$${dm[1]}` : dm[1];
5547
- const bound = env.get(key);
6058
+ const bound = env2.get(key);
5548
6059
  if (bound && !(bound.kind === "concrete" && !bound.value)) {
5549
6060
  result.push(bound);
5550
6061
  } else {
5551
- result.push(evalExpr2(dm[2], env));
6062
+ result.push(evalExpr2(dm[2], env2));
5552
6063
  }
5553
6064
  continue;
5554
6065
  }
5555
6066
  if (/^\d+$/.test(inner)) {
5556
- const v = env.get(`$${inner}`);
6067
+ const v = env2.get(`$${inner}`);
5557
6068
  result.push(v || svalSym(`$${inner}`));
5558
6069
  } else {
5559
- const v = env.get(inner);
6070
+ const v = env2.get(inner);
5560
6071
  result.push(v || svalSym(inner));
5561
6072
  }
5562
6073
  continue;
@@ -5565,7 +6076,7 @@ function evalExpr2(text, env) {
5565
6076
  if (bare) {
5566
6077
  const varName = bare[1];
5567
6078
  const key = /^\d+$/.test(varName) ? `$${varName}` : varName;
5568
- const v = env.get(key);
6079
+ const v = env2.get(key);
5569
6080
  result.push(v || svalSym(p));
5570
6081
  continue;
5571
6082
  }
@@ -5575,13 +6086,13 @@ function evalExpr2(text, env) {
5575
6086
  return fold(svalExpr(result));
5576
6087
  }
5577
6088
  function initEnv(entryArgs) {
5578
- const env = /* @__PURE__ */ new Map();
6089
+ const env2 = /* @__PURE__ */ new Map();
5579
6090
  if (entryArgs && entryArgs.length) {
5580
- entryArgs.forEach((a, idx) => env.set(`$${idx + 1}`, svalConc(a)));
5581
- env.set("$@", svalConc(entryArgs.join(" ")));
5582
- env.set("$*", svalConc(entryArgs.join(" ")));
6091
+ entryArgs.forEach((a, idx) => env2.set(`$${idx + 1}`, svalConc(a)));
6092
+ env2.set("$@", svalConc(entryArgs.join(" ")));
6093
+ env2.set("$*", svalConc(entryArgs.join(" ")));
5583
6094
  }
5584
- return env;
6095
+ return env2;
5585
6096
  }
5586
6097
  function globToRegex(pattern) {
5587
6098
  let out = "^";
@@ -5639,12 +6150,12 @@ function evalNumericCompare(op, left, right) {
5639
6150
  return null;
5640
6151
  }
5641
6152
  }
5642
- function resolveVarSide(tok, env) {
6153
+ function resolveVarSide(tok, env2) {
5643
6154
  if (!tok.startsWith("$")) return tok;
5644
- const ev = evalExpr2(tok, env);
6155
+ const ev = evalExpr2(tok, env2);
5645
6156
  return ev.kind === "concrete" ? ev.value : null;
5646
6157
  }
5647
- function evalCondition2(cond, env) {
6158
+ function evalCondition2(cond, env2) {
5648
6159
  let inner = cond.trim().replace(/^\[\[?|\]\]?$/g, "").trim();
5649
6160
  if (/^-[A-Za-z]\s/.test(inner) || inner.startsWith("-d") || inner.startsWith("-f")) return null;
5650
6161
  const innerQ = inner.replace(/"/g, "").replace(/'/g, "");
@@ -5654,8 +6165,8 @@ function evalCondition2(cond, env) {
5654
6165
  if (!opm) continue;
5655
6166
  const left2 = innerQ.slice(0, opm.index).trim();
5656
6167
  const right2 = innerQ.slice(opm.index + opm[0].length).trim();
5657
- const lv2 = resolveVarSide(left2, env);
5658
- const rv2 = resolveVarSide(right2, env);
6168
+ const lv2 = resolveVarSide(left2, env2);
6169
+ const rv2 = resolveVarSide(right2, env2);
5659
6170
  if (lv2 === null || rv2 === null) return null;
5660
6171
  return evalNumericCompare(op2, lv2, rv2);
5661
6172
  }
@@ -5666,7 +6177,7 @@ function evalCondition2(cond, env) {
5666
6177
  let right = sm[3].trim();
5667
6178
  if (op === "=~") {
5668
6179
  if (right.startsWith("/") && right.endsWith("/")) right = right.slice(1, -1);
5669
- const lv2 = resolveVarSide(left, env);
6180
+ const lv2 = resolveVarSide(left, env2);
5670
6181
  if (lv2 === null) return null;
5671
6182
  try {
5672
6183
  return new RegExp(right).test(lv2);
@@ -5674,8 +6185,8 @@ function evalCondition2(cond, env) {
5674
6185
  return null;
5675
6186
  }
5676
6187
  }
5677
- const lv = resolveVarSide(left, env);
5678
- const rv = resolveVarSide(right, env);
6188
+ const lv = resolveVarSide(left, env2);
6189
+ const rv = resolveVarSide(right, env2);
5679
6190
  if (lv === null || rv === null) return null;
5680
6191
  if (op === "!=") return lv !== rv;
5681
6192
  return lv === rv;
@@ -5685,7 +6196,7 @@ function symbolicProcess(content, entryArgs) {
5685
6196
  const stmts = tokenize(content);
5686
6197
  if (!stmts.some((s) => s.kind === "case" || s.kind === "if" || s.kind === "assign")) return null;
5687
6198
  if (stmts.some((s) => !s.modelable)) return null;
5688
- const env = initEnv(entryArgs);
6199
+ const env2 = initEnv(entryArgs);
5689
6200
  let linesBudget = 0;
5690
6201
  let branchesBudget = 0;
5691
6202
  let modelable = false;
@@ -5695,9 +6206,9 @@ function symbolicProcess(content, entryArgs) {
5695
6206
  if (linesBudget > MAX_LINES2) return null;
5696
6207
  if (s.kind === "assign") {
5697
6208
  modelable = true;
5698
- if (env.size >= MAX_VARS) return null;
5699
- const v = evalExpr2(s.rhs, env);
5700
- env.set(s.name, fold(v));
6209
+ if (env2.size >= MAX_VARS) return null;
6210
+ const v = evalExpr2(s.rhs, env2);
6211
+ env2.set(s.name, fold(v));
5701
6212
  out.push(s.line);
5702
6213
  continue;
5703
6214
  }
@@ -5705,7 +6216,7 @@ function symbolicProcess(content, entryArgs) {
5705
6216
  modelable = true;
5706
6217
  branchesBudget += s.branches.length;
5707
6218
  if (branchesBudget > MAX_BRANCHES) return null;
5708
- const selVal = fold(evalExpr2(s.selector, env));
6219
+ const selVal = fold(evalExpr2(s.selector, env2));
5709
6220
  if (selVal.kind !== "concrete") return null;
5710
6221
  const concrete = selVal.value || "";
5711
6222
  let selected = null;
@@ -5732,7 +6243,7 @@ esac`);
5732
6243
  modelable = true;
5733
6244
  branchesBudget++;
5734
6245
  if (branchesBudget > MAX_BRANCHES) return null;
5735
- const truth = evalCondition2(s.cond, env);
6246
+ const truth = evalCondition2(s.cond, env2);
5736
6247
  if (truth === null) return null;
5737
6248
  const chosen = truth ? s.thenBody : s.elseBody;
5738
6249
  if (chosen === null) return null;
@@ -6265,9 +6776,9 @@ var init_tracker = __esm({
6265
6776
  });
6266
6777
 
6267
6778
  // src/engine/script-content-analyzer.ts
6268
- import { statSync as statSync4, existsSync as existsSync11 } from "fs";
6269
- import { resolve as resolve9, join as join10, isAbsolute as isAbsolute2 } from "path";
6270
- import { homedir as homedir3 } from "os";
6779
+ import { statSync as statSync4, existsSync as existsSync12 } from "fs";
6780
+ import { resolve as resolve10, join as join11, isAbsolute as isAbsolute3 } from "path";
6781
+ import { homedir as homedir4 } from "os";
6271
6782
  function emptyChain() {
6272
6783
  return {
6273
6784
  nodes: [],
@@ -6378,7 +6889,7 @@ function extractInlineScriptPath(actionDetail) {
6378
6889
  if (looksLikeScriptPath(tok)) {
6379
6890
  try {
6380
6891
  const resolved = resolvePath2(tok.replace(/^['"]|['"]$/g, ""));
6381
- if (existsSync11(resolved)) {
6892
+ if (existsSync12(resolved)) {
6382
6893
  const stat = statSync4(resolved);
6383
6894
  if (stat.isFile()) return resolved;
6384
6895
  }
@@ -6392,7 +6903,7 @@ function extractInlineScriptPath(actionDetail) {
6392
6903
  const p = m[1];
6393
6904
  try {
6394
6905
  const resolved = resolvePath2(p);
6395
- if (existsSync11(resolved)) {
6906
+ if (existsSync12(resolved)) {
6396
6907
  const stat = statSync4(resolved);
6397
6908
  if (stat.isFile()) return resolved;
6398
6909
  }
@@ -6408,7 +6919,7 @@ function extractInlineScriptPath(actionDetail) {
6408
6919
  let resolved;
6409
6920
  try {
6410
6921
  resolved = resolvePath2(p);
6411
- if (existsSync11(resolved)) {
6922
+ if (existsSync12(resolved)) {
6412
6923
  const stat = statSync4(resolved);
6413
6924
  if (stat.isFile()) return resolved;
6414
6925
  }
@@ -6449,9 +6960,9 @@ function shlexSplit(s) {
6449
6960
  function resolvePath2(p) {
6450
6961
  let expanded = p;
6451
6962
  if (expanded.startsWith("~/") || expanded === "~") {
6452
- expanded = join10(homedir3(), expanded.slice(1));
6963
+ expanded = join11(homedir4(), expanded.slice(1));
6453
6964
  }
6454
- return resolve9(expanded);
6965
+ return resolve10(expanded);
6455
6966
  }
6456
6967
  function scanExtensionPatterns(ext, content) {
6457
6968
  const pats = ext?.extra_command_patterns;
@@ -6572,8 +7083,8 @@ function analyzeScriptContent2(input, ext) {
6572
7083
  resolvedPath = inline;
6573
7084
  isInline = true;
6574
7085
  }
6575
- if (resolvedPath && !isAbsolute2(resolvedPath) && !resolvedPath.startsWith("~/")) {
6576
- resolvedPath = join10(input.cwd || process.cwd(), resolvedPath);
7086
+ if (resolvedPath && !isAbsolute3(resolvedPath) && !resolvedPath.startsWith("~/")) {
7087
+ resolvedPath = join11(input.cwd || process.cwd(), resolvedPath);
6577
7088
  }
6578
7089
  const l1Adapter = (content) => {
6579
7090
  const j = scanDangerousCommandPatterns("bash", content, input.osType);
@@ -6699,16 +7210,16 @@ __export(token_stats_exports, {
6699
7210
  recordTokenUsage: () => recordTokenUsage,
6700
7211
  resetTokenStats: () => resetTokenStats
6701
7212
  });
6702
- import { readFileSync as readFileSync12, writeFileSync as writeFileSync2, existsSync as existsSync12, mkdirSync as mkdirSync2, unlinkSync as unlinkSync2 } from "fs";
7213
+ import { readFileSync as readFileSync13, writeFileSync as writeFileSync2, existsSync as existsSync13, mkdirSync as mkdirSync3, unlinkSync as unlinkSync2 } from "fs";
6703
7214
  function getStatsPath() {
6704
7215
  const envPath = process.env[ENV_STATS_PATH];
6705
7216
  if (envPath) return envPath;
6706
7217
  return getTokenStatsPath();
6707
7218
  }
6708
7219
  function loadAllRecords(statsPath) {
6709
- if (!existsSync12(statsPath)) return [];
7220
+ if (!existsSync13(statsPath)) return [];
6710
7221
  try {
6711
- const data = JSON.parse(readFileSync12(statsPath, "utf-8"));
7222
+ const data = JSON.parse(readFileSync13(statsPath, "utf-8"));
6712
7223
  if (Array.isArray(data)) return data;
6713
7224
  if (data && Array.isArray(data.records)) return data.records;
6714
7225
  return [];
@@ -6719,7 +7230,7 @@ function loadAllRecords(statsPath) {
6719
7230
  function saveRecords(statsPath, records) {
6720
7231
  try {
6721
7232
  const dir = statsPath.substring(0, statsPath.lastIndexOf("/"));
6722
- if (dir && !existsSync12(dir)) mkdirSync2(dir, { recursive: true });
7233
+ if (dir && !existsSync13(dir)) mkdirSync3(dir, { recursive: true });
6723
7234
  writeFileSync2(statsPath, JSON.stringify(records, null, 2), "utf-8");
6724
7235
  } catch (e) {
6725
7236
  }
@@ -6846,7 +7357,7 @@ function getTokenTrend(days = 0, startDate, endDate) {
6846
7357
  }
6847
7358
  function resetTokenStats() {
6848
7359
  const statsPath = getStatsPath();
6849
- if (existsSync12(statsPath)) {
7360
+ if (existsSync13(statsPath)) {
6850
7361
  try {
6851
7362
  unlinkSync2(statsPath);
6852
7363
  } catch {
@@ -6865,7 +7376,7 @@ var init_token_stats = __esm({
6865
7376
  });
6866
7377
 
6867
7378
  // src/engine/llm-analyzer.ts
6868
- import { appendFileSync } from "fs";
7379
+ import { openSync as openSync2, writeSync as writeSync2, closeSync as closeSync2, constants as fsConstants } from "fs";
6869
7380
  import OpenAI from "openai";
6870
7381
  async function runLLMAnalysis(input, previous) {
6871
7382
  if (!isLLMAvailable()) {
@@ -6881,7 +7392,15 @@ async function runLLMAnalysis(input, previous) {
6881
7392
  };
6882
7393
  }
6883
7394
  const globalConfig = getLLMConfig();
6884
- const config = input.llmConfig?.apiKey ? { ...globalConfig, ...input.llmConfig } : globalConfig;
7395
+ let perRequest = input.llmConfig;
7396
+ if (perRequest?.apiKey) {
7397
+ const [okUrl, why] = validateLlmBaseUrl(String(perRequest.baseUrl || ""));
7398
+ if (!okUrl) {
7399
+ console.warn("[AgentMoss] \u5FFD\u7565\u4E0D\u88AB\u5141\u8BB8\u7684 per-request llmConfig.baseUrl:", why);
7400
+ perRequest = { ...perRequest, baseUrl: void 0 };
7401
+ }
7402
+ }
7403
+ const config = perRequest?.apiKey ? { ...globalConfig, ...perRequest } : globalConfig;
6885
7404
  const rawDetail = input.aNext.actionDetail.replace(/^terminal-\w+:\s*command=([^,]+).*/, "$1").trim();
6886
7405
  const cacheKey = `${input.aNext.actionType}:${rawDetail || input.aNext.actionDetail}`;
6887
7406
  const cached2 = llmCache.get(cacheKey);
@@ -6910,6 +7429,10 @@ async function runLLMAnalysis(input, previous) {
6910
7429
  }
6911
7430
  function logPrompt(prompt, aNext, logPath) {
6912
7431
  if (!logPath) return;
7432
+ if (!pathWithin(logPath, logRoots())) {
7433
+ console.warn("[AgentMoss] \u5FFD\u7565\u8D8A\u754C\u7684 llm_log_path\uFF08\u4E0D\u5728\u5141\u8BB8\u6839\u76EE\u5F55\u5185\uFF09:", logPath);
7434
+ return;
7435
+ }
6913
7436
  try {
6914
7437
  const actionType = aNext.actionType || "unknown";
6915
7438
  const actionDetail = (aNext.actionDetail || "").slice(0, 100);
@@ -6917,7 +7440,14 @@ function logPrompt(prompt, aNext, logPath) {
6917
7440
  ${prompt}
6918
7441
  --- END PROMPT ---
6919
7442
  `;
6920
- appendFileSync(logPath, line, "utf-8");
7443
+ let flags = fsConstants.O_WRONLY | fsConstants.O_CREAT | fsConstants.O_APPEND;
7444
+ if (fsConstants.O_NOFOLLOW) flags |= fsConstants.O_NOFOLLOW;
7445
+ const fd = openSync2(logPath, flags, 384);
7446
+ try {
7447
+ writeSync2(fd, line);
7448
+ } finally {
7449
+ closeSync2(fd);
7450
+ }
6921
7451
  } catch (e) {
6922
7452
  console.warn("[AgentMoss] Failed to write LLM prompt to log:", e);
6923
7453
  }
@@ -7076,7 +7606,7 @@ async function analyzeWithLLM(prompt, config) {
7076
7606
  }
7077
7607
  if (attempt < maxRetries) {
7078
7608
  console.error(`[AgentMoss] LLM call failed (attempt ${attempt + 1}/${maxRetries + 1}), retrying in ${retryCfg.retryInterval}s:`, err.message);
7079
- await new Promise((resolve11) => setTimeout(resolve11, retryCfg.retryInterval * 1e3));
7609
+ await new Promise((resolve12) => setTimeout(resolve12, retryCfg.retryInterval * 1e3));
7080
7610
  } else {
7081
7611
  clearTimeout(totalTimeoutTimer);
7082
7612
  console.error("[AgentMoss] LLM call failed after all retries:", err.message);
@@ -7092,6 +7622,7 @@ var init_llm_analyzer = __esm({
7092
7622
  "src/engine/llm-analyzer.ts"() {
7093
7623
  "use strict";
7094
7624
  init_esm_shims();
7625
+ init_security_policy();
7095
7626
  init_config();
7096
7627
  init_skill_loader();
7097
7628
  init_template_loader();
@@ -7103,9 +7634,9 @@ var init_llm_analyzer = __esm({
7103
7634
  });
7104
7635
 
7105
7636
  // src/brain/rule-store.ts
7106
- import { existsSync as existsSync13, readFileSync as readFileSync13, writeFileSync as writeFileSync3, mkdirSync as mkdirSync3 } from "fs";
7107
- import { join as join11 } from "path";
7108
- import { homedir as homedir4 } from "os";
7637
+ import { existsSync as existsSync14, readFileSync as readFileSync14, writeFileSync as writeFileSync3, mkdirSync as mkdirSync4 } from "fs";
7638
+ import { join as join12 } from "path";
7639
+ import { homedir as homedir5 } from "os";
7109
7640
  function getRulesPath() {
7110
7641
  const envPath = process.env[ENV_RULES_PATH];
7111
7642
  if (envPath) return envPath;
@@ -7123,7 +7654,7 @@ var init_rule_store = __esm({
7123
7654
  "src/brain/rule-store.ts"() {
7124
7655
  "use strict";
7125
7656
  init_esm_shims();
7126
- DEFAULT_RULES_PATH = join11(homedir4(), ".agentmoss", "brain", "rules.json");
7657
+ DEFAULT_RULES_PATH = join12(homedir5(), ".agentmoss", "brain", "rules.json");
7127
7658
  ENV_RULES_PATH = "AGENT_MOSS_BRAIN_RULES_PATH";
7128
7659
  STATUS_DRAFT = "draft";
7129
7660
  STATUS_APPROVED = "approved";
@@ -7140,11 +7671,11 @@ var init_rule_store = __esm({
7140
7671
  this.data = this.load();
7141
7672
  }
7142
7673
  load() {
7143
- if (!existsSync13(this.path)) {
7674
+ if (!existsSync14(this.path)) {
7144
7675
  return { version: 1, rules: [], pattern_candidates: [] };
7145
7676
  }
7146
7677
  try {
7147
- const raw = JSON.parse(readFileSync13(this.path, "utf-8"));
7678
+ const raw = JSON.parse(readFileSync14(this.path, "utf-8"));
7148
7679
  return {
7149
7680
  version: raw.version || 1,
7150
7681
  rules: raw.rules || [],
@@ -7156,7 +7687,7 @@ var init_rule_store = __esm({
7156
7687
  }
7157
7688
  save() {
7158
7689
  const dir = this.path.substring(0, this.path.lastIndexOf("/"));
7159
- if (dir) mkdirSync3(dir, { recursive: true });
7690
+ if (dir) mkdirSync4(dir, { recursive: true });
7160
7691
  writeFileSync3(this.path, JSON.stringify(this.data, null, 2), "utf-8");
7161
7692
  }
7162
7693
  addRule(rule) {
@@ -7738,9 +8269,9 @@ var init_reviewer = __esm({
7738
8269
  });
7739
8270
 
7740
8271
  // src/brain/skill-manager.ts
7741
- import { existsSync as existsSync14, mkdirSync as mkdirSync4, readFileSync as readFileSync14, renameSync, writeFileSync as writeFileSync4 } from "fs";
7742
- import { homedir as homedir5 } from "os";
7743
- import { join as join12 } from "path";
8272
+ import { existsSync as existsSync15, mkdirSync as mkdirSync5, readFileSync as readFileSync15, renameSync, writeFileSync as writeFileSync4 } from "fs";
8273
+ import { homedir as homedir6 } from "os";
8274
+ import { join as join13 } from "path";
7744
8275
  import { randomUUID } from "crypto";
7745
8276
  function getMetaPath(override) {
7746
8277
  return override || process.env[ENV_SKILL_META_PATH] || DEFAULT_SKILL_META_PATH;
@@ -7750,14 +8281,14 @@ var init_skill_manager = __esm({
7750
8281
  "src/brain/skill-manager.ts"() {
7751
8282
  "use strict";
7752
8283
  init_esm_shims();
7753
- DEFAULT_SKILL_META_PATH = join12(homedir5(), ".agentmoss", "brain", "skill_meta.json");
8284
+ DEFAULT_SKILL_META_PATH = join13(homedir6(), ".agentmoss", "brain", "skill_meta.json");
7754
8285
  ENV_SKILL_META_PATH = "AGENT_MOSS_BRAIN_SKILL_META_PATH";
7755
8286
  BrainSkillManager = class {
7756
8287
  constructor(skillsDir, metaPath) {
7757
8288
  this.skillsDir = skillsDir;
7758
8289
  this.metaPath = getMetaPath(metaPath);
7759
8290
  try {
7760
- mkdirSync4(join12(this.metaPath, ".."), { recursive: true });
8291
+ mkdirSync5(join13(this.metaPath, ".."), { recursive: true });
7761
8292
  } catch {
7762
8293
  }
7763
8294
  this.load();
@@ -7767,9 +8298,9 @@ var init_skill_manager = __esm({
7767
8298
  skills = [];
7768
8299
  metaPath;
7769
8300
  load() {
7770
- if (!existsSync14(this.metaPath)) return;
8301
+ if (!existsSync15(this.metaPath)) return;
7771
8302
  try {
7772
- const data = JSON.parse(readFileSync14(this.metaPath, "utf-8"));
8303
+ const data = JSON.parse(readFileSync15(this.metaPath, "utf-8"));
7773
8304
  this.skills = data.skills || [];
7774
8305
  } catch {
7775
8306
  this.skills = [];
@@ -7792,7 +8323,7 @@ var init_skill_manager = __esm({
7792
8323
  let changed = false;
7793
8324
  for (const sk of this.skills) {
7794
8325
  if (sk.status !== "active") continue;
7795
- if (!existsSync14(join12(this.skillsDir, `${sk.name}.md`))) {
8326
+ if (!existsSync15(join13(this.skillsDir, `${sk.name}.md`))) {
7796
8327
  sk.status = "stale";
7797
8328
  changed = true;
7798
8329
  }
@@ -7803,9 +8334,9 @@ var init_skill_manager = __esm({
7803
8334
  addSkill(skill) {
7804
8335
  const id = skill.id || randomUUID();
7805
8336
  const name = skill.name || `brain_${id.slice(0, 8)}`;
7806
- if (!existsSync14(this.skillsDir)) return "";
7807
- const mdPath = join12(this.skillsDir, `${name}.md`);
7808
- if (existsSync14(mdPath)) return "";
8337
+ if (!existsSync15(this.skillsDir)) return "";
8338
+ const mdPath = join13(this.skillsDir, `${name}.md`);
8339
+ if (existsSync15(mdPath)) return "";
7809
8340
  try {
7810
8341
  writeFileSync4(mdPath, skill.content, "utf-8");
7811
8342
  } catch {
@@ -7854,12 +8385,12 @@ var init_skill_manager = __esm({
7854
8385
  const out = [];
7855
8386
  for (const sk of this.skills) {
7856
8387
  if (sk.origin_node !== "local" || sk.status !== "active") continue;
7857
- const mdPath = join12(this.skillsDir, `${sk.name}.md`);
7858
- if (!existsSync14(mdPath)) continue;
8388
+ const mdPath = join13(this.skillsDir, `${sk.name}.md`);
8389
+ if (!existsSync15(mdPath)) continue;
7859
8390
  out.push({
7860
8391
  name: sk.name,
7861
8392
  category: sk.category || "",
7862
- content: readFileSync14(mdPath, "utf-8"),
8393
+ content: readFileSync15(mdPath, "utf-8"),
7863
8394
  keywords: sk.keywords || []
7864
8395
  });
7865
8396
  }
@@ -7946,7 +8477,7 @@ var init_curator = __esm({
7946
8477
  });
7947
8478
 
7948
8479
  // src/brain/contributor.ts
7949
- import { dirname as dirname9 } from "path";
8480
+ import { dirname as dirname10 } from "path";
7950
8481
  import { fileURLToPath as fileURLToPath9 } from "url";
7951
8482
  var __dirname8;
7952
8483
  var init_contributor = __esm({
@@ -7956,7 +8487,7 @@ var init_contributor = __esm({
7956
8487
  init_rule_store();
7957
8488
  init_skill_manager();
7958
8489
  init_asset_dir();
7959
- __dirname8 = dirname9(fileURLToPath9(import.meta.url));
8490
+ __dirname8 = dirname10(fileURLToPath9(import.meta.url));
7960
8491
  }
7961
8492
  });
7962
8493
 
@@ -8324,7 +8855,7 @@ function markIntegrity(actionDetail) {
8324
8855
  }
8325
8856
  return { integrity: "medium", isLowSource: false };
8326
8857
  }
8327
- function initNode(sessionId, seq, actionType, actionDetail) {
8858
+ function initNode(sessionId, seq, actionType, actionDetail, timestamp) {
8328
8859
  const { integrity, isLowSource } = markIntegrity(actionDetail);
8329
8860
  return {
8330
8861
  sessionId,
@@ -8334,7 +8865,8 @@ function initNode(sessionId, seq, actionType, actionDetail) {
8334
8865
  stage: markStage(actionDetail),
8335
8866
  resources: extractResources(actionDetail),
8336
8867
  integrity,
8337
- isLowSource
8868
+ isLowSource,
8869
+ timestamp: timestamp || void 0
8338
8870
  };
8339
8871
  }
8340
8872
  function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
@@ -8344,13 +8876,21 @@ function buildSessionGraph(actionHistory, aNext, sessionId = "current") {
8344
8876
  const details = [];
8345
8877
  for (const a of actionHistory) {
8346
8878
  const ad = String(a.actionDetail || "");
8347
- if (ad) details.push({ actionType: String(a.actionType || ""), actionDetail: ad });
8879
+ if (ad) details.push({
8880
+ actionType: String(a.actionType || ""),
8881
+ actionDetail: ad,
8882
+ calledAt: a.calledAt || void 0
8883
+ });
8348
8884
  }
8349
8885
  if (aNext) {
8350
- details.push({ actionType: String(aNext.actionType || ""), actionDetail: String(aNext.actionDetail || "") });
8886
+ details.push({
8887
+ actionType: String(aNext.actionType || ""),
8888
+ actionDetail: String(aNext.actionDetail || ""),
8889
+ calledAt: aNext.calledAt || void 0
8890
+ });
8351
8891
  }
8352
8892
  details.forEach((d, seq) => {
8353
- const node = initNode(sessionId, seq, d.actionType, d.actionDetail);
8893
+ const node = initNode(sessionId, seq, d.actionType, d.actionDetail, d.calledAt);
8354
8894
  nodes.push(node);
8355
8895
  if (node.stage) stageFlags.add(node.stage);
8356
8896
  });
@@ -8376,7 +8916,8 @@ function buildMultiSessionGraph(sessions, aNext) {
8376
8916
  last.sessionId,
8377
8917
  last.nodes.length,
8378
8918
  String(aNext.actionType || ""),
8379
- String(aNext.actionDetail || "")
8919
+ String(aNext.actionDetail || ""),
8920
+ aNext.calledAt || void 0
8380
8921
  ));
8381
8922
  if (last.nodes.length > 1) {
8382
8923
  last.edges.push({ src: last.nodes.length - 2, dst: last.nodes.length - 1, kind: "next" });
@@ -8434,6 +8975,27 @@ var init_builder = __esm({
8434
8975
  });
8435
8976
 
8436
8977
  // src/engine/formal-engine/properties.ts
8978
+ function parseTs(ts) {
8979
+ if (!ts) return null;
8980
+ const t = Date.parse(String(ts).trim());
8981
+ return Number.isFinite(t) ? t : null;
8982
+ }
8983
+ function privescExfilWindowSec() {
8984
+ const raw = (process.env.AGENT_MOSS_PRIVESC_EXFIL_WINDOW_SEC || "").trim();
8985
+ if (!raw) return 1800;
8986
+ const n = parseInt(raw, 10);
8987
+ return Number.isFinite(n) ? Math.max(0, n) : 1800;
8988
+ }
8989
+ function privescInWindow(nodes, indexes, exfilIdx, windowSec) {
8990
+ const before = indexes.filter((i) => i < exfilIdx);
8991
+ if (windowSec <= 0) return before;
8992
+ const exfilTs = parseTs(nodes[exfilIdx].timestamp);
8993
+ if (exfilTs === null) return before;
8994
+ return before.filter((i) => {
8995
+ const ts = parseTs(nodes[i].timestamp);
8996
+ return ts === null || exfilTs - ts <= windowSec * 1e3;
8997
+ });
8998
+ }
8437
8999
  function loadCrossSessionChains() {
8438
9000
  if (CROSS_SESSION_CHAINS) return CROSS_SESSION_CHAINS;
8439
9001
  const combo = loadL2().combo_chains || {};
@@ -8625,6 +9187,7 @@ function checkNeverExfilAfterPrivesc(graph) {
8625
9187
  const nodes = allNodes(graph);
8626
9188
  if (nodes.length < 2) return violations;
8627
9189
  const witness = graph.witnessIdx;
9190
+ const windowSec = privescExfilWindowSec();
8628
9191
  if (witness !== void 0) {
8629
9192
  const evIdx = [];
8630
9193
  nodes.forEach((n, i) => {
@@ -8632,15 +9195,17 @@ function checkNeverExfilAfterPrivesc(graph) {
8632
9195
  });
8633
9196
  if (evIdx.length === 0) return violations;
8634
9197
  const firstEv = Math.min(...evIdx);
8635
- let pre = null;
8636
- for (let i = 0; i < firstEv; i++) {
8637
- if (isPrivescNode(nodes[i])) pre = nodes[i];
8638
- }
8639
- if (pre) {
9198
+ const privescIdx = [];
9199
+ nodes.forEach((n, i) => {
9200
+ if (isPrivescNode(n)) privescIdx.push(i);
9201
+ });
9202
+ const candidates = privescInWindow(nodes, privescIdx, firstEv, windowSec);
9203
+ if (candidates.length > 0) {
9204
+ const pre = nodes[candidates[candidates.length - 1]];
8640
9205
  const post = nodes[firstEv];
8641
9206
  return [{
8642
9207
  propertyId: "no-exfil-after-privesc",
8643
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
9208
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
8644
9209
  riskLevel: "critical",
8645
9210
  path: [
8646
9211
  { sessionId: pre.sessionId, seq: pre.seq, detail: pre.actionDetail.slice(0, 60) },
@@ -8653,18 +9218,18 @@ function checkNeverExfilAfterPrivesc(graph) {
8653
9218
  for (let i = 0; i < nodes.length; i++) {
8654
9219
  if (!isPrivescNode(nodes[i])) continue;
8655
9220
  for (let j = i + 1; j < nodes.length; j++) {
8656
- if (isExfilNode(nodes[j])) {
8657
- violations.push({
8658
- propertyId: "no-exfil-after-privesc",
8659
- reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\xAC\u25C7EXFIL)",
8660
- riskLevel: "critical",
8661
- path: [
8662
- { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
8663
- { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
8664
- ]
8665
- });
8666
- return violations;
8667
- }
9221
+ if (!isExfilNode(nodes[j])) continue;
9222
+ if (privescInWindow(nodes, [i], j, windowSec).length === 0) continue;
9223
+ violations.push({
9224
+ propertyId: "no-exfil-after-privesc",
9225
+ reason: "\u63D0\u6743\u540E\u51FA\u73B0\u5916\u4F20\uFF1A\u63D0\u6743(\u524D) \u2192 \u5916\u4F20(\u540E)\uFF0C\u8FDD\u53CD \u25A1(PRIVESC\u2192\u25C7EXFIL) \u7A97\u53E3\u7EA6\u675F",
9226
+ riskLevel: "critical",
9227
+ path: [
9228
+ { sessionId: nodes[i].sessionId, seq: nodes[i].seq, detail: nodes[i].actionDetail.slice(0, 60) },
9229
+ { sessionId: nodes[j].sessionId, seq: nodes[j].seq, detail: nodes[j].actionDetail.slice(0, 60) }
9230
+ ]
9231
+ });
9232
+ return violations;
8668
9233
  }
8669
9234
  }
8670
9235
  return violations;
@@ -8827,6 +9392,10 @@ function checkCrossSessionReachability(graph) {
8827
9392
  }
8828
9393
  return violations;
8829
9394
  }
9395
+ function isDataSourceNode(n) {
9396
+ if (n.stage === "CRED" || n.isLowSource) return true;
9397
+ return DATA_SOURCE_READ_RE.test(n.actionDetail);
9398
+ }
8830
9399
  function checkSensitiveDataAggregation(graph) {
8831
9400
  const violations = [];
8832
9401
  const nodes = allNodes(graph);
@@ -8846,6 +9415,7 @@ function checkSensitiveDataAggregation(graph) {
8846
9415
  for (const r of n.resources) {
8847
9416
  for (const j of resIndex.get(r) || []) {
8848
9417
  if (j === exfilIdx) continue;
9418
+ if (!isDataSourceNode(nodes[j])) continue;
8849
9419
  srcs.add(`${nodes[j].sessionId}#${nodes[j].seq}`);
8850
9420
  }
8851
9421
  }
@@ -8868,7 +9438,7 @@ function checkSensitiveDataAggregation(graph) {
8868
9438
  }
8869
9439
  return violations;
8870
9440
  }
8871
- var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, FORMAL_RULES, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, ALL_PROPERTIES;
9441
+ var CROSS_SESSION_CHAINS, CRED_FILE_RE, EXFIL_RE, EBPF_NET_RE, REMOTE_RE, EXFIL_ACTION_RE, CRED_STRICT_RE, INLINE_SHELL_RE, FORMAL_RULES, PKG_MGR, SHELLS, TOOL_REGISTRY, COMPUTE_RE, STRONG_COMPUTE_TYPES, DL_OR_LOW_RE, DATA_SOURCE_READ_RE, ALL_PROPERTIES;
8872
9442
  var init_properties = __esm({
8873
9443
  "src/engine/formal-engine/properties.ts"() {
8874
9444
  "use strict";
@@ -8892,6 +9462,7 @@ var init_properties = __esm({
8892
9462
  COMPUTE_RE = /subprocess|os\.system|shell\s*=\s*True|\beval\(|\bexec\(|bash\s+[^\s]|sh\s+-c|python3?\s+|node\s+|\btimeout\s+|nohup\s+|\b\.\/[\w./-]+/i;
8893
9463
  STRONG_COMPUTE_TYPES = new Set(FORMAL_RULES.compute_action_types ?? []);
8894
9464
  DL_OR_LOW_RE = /curl\s+.*-o\b|wget\s+.*\s+-O\b|curl\s+.*\|\s*(bash|sh|python)|wget\s+.*\|\s*(bash|sh|python)|git\s+clone|docker\s+pull|pip\s+install|npm\s+install|base64\s+-d/i;
9465
+ DATA_SOURCE_READ_RE = /\b(?:cat|head|tail|less|more|sed|awk|grep|strings|base64|xxd|od|dd)\b|\b(?:open|read_text|read_bytes)\s*\(/i;
8895
9466
  ALL_PROPERTIES = [
8896
9467
  { id: "cross-session-chain", check: checkCrossSessionChain, shortName: "P1" },
8897
9468
  { id: "sensitive-data-flow", check: checkSensitiveDataFlow, shortName: "P2" },
@@ -9414,10 +9985,10 @@ var init_stateful = __esm({
9414
9985
  import * as fs4 from "fs";
9415
9986
  import * as os2 from "os";
9416
9987
  import * as path4 from "path";
9417
- import { createHash as createHash2 } from "crypto";
9988
+ import { createHash as createHash3 } from "crypto";
9418
9989
  function defaultStateDbPath() {
9419
9990
  const dir = process.env.AGENT_MOSS_STATE_DIR || path4.join(os2.homedir(), ".agentmoss");
9420
- const tag = createHash2("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9991
+ const tag = createHash3("sha1").update("agentmoss-stateful").digest("hex").slice(0, 8);
9421
9992
  return path4.join(dir, `stateful_chains_${tag}.json`);
9422
9993
  }
9423
9994
  var ChainStateStore;
@@ -9425,6 +9996,7 @@ var init_chain_state = __esm({
9425
9996
  "src/engine/formal-engine/chain_state.ts"() {
9426
9997
  "use strict";
9427
9998
  init_esm_shims();
9999
+ init_security_policy();
9428
10000
  init_stateful();
9429
10001
  ChainStateStore = class {
9430
10002
  filePath;
@@ -9451,8 +10023,8 @@ var init_chain_state = __esm({
9451
10023
  this._timer = setTimeout(() => {
9452
10024
  this._timer = null;
9453
10025
  try {
9454
- fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
9455
- fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
10026
+ ensurePrivateDir(path4.dirname(this.filePath));
10027
+ writePrivateText(this.filePath, JSON.stringify(this.data, null, 2));
9456
10028
  } catch {
9457
10029
  }
9458
10030
  }, this._flushDelayMs);
@@ -9464,8 +10036,8 @@ var init_chain_state = __esm({
9464
10036
  this._timer = null;
9465
10037
  }
9466
10038
  try {
9467
- fs4.mkdirSync(path4.dirname(this.filePath), { recursive: true });
9468
- fs4.writeFileSync(this.filePath, JSON.stringify(this.data, null, 2), "utf-8");
10039
+ ensurePrivateDir(path4.dirname(this.filePath));
10040
+ writePrivateText(this.filePath, JSON.stringify(this.data, null, 2));
9469
10041
  } catch {
9470
10042
  }
9471
10043
  }
@@ -10130,13 +10702,16 @@ function enforceRulesContract() {
10130
10702
  }
10131
10703
 
10132
10704
  // src/server.ts
10705
+ init_security_policy();
10133
10706
  import { cors } from "hono/cors";
10134
10707
  import { logger } from "hono/logger";
10135
10708
  import net from "net";
10136
- import { existsSync as existsSync20 } from "fs";
10709
+ import { existsSync as existsSync21 } from "fs";
10137
10710
 
10138
10711
  // src/routes.ts
10139
10712
  init_esm_shims();
10713
+ init_security_policy();
10714
+ import { randomBytes as randomBytes2 } from "crypto";
10140
10715
 
10141
10716
  // src/models.ts
10142
10717
  init_esm_shims();
@@ -10156,7 +10731,10 @@ var ActionItemSchema = z.object({
10156
10731
  // 工具返回给 agent 的内容。仅 L2 的授权(consent)判定用:判断「用户是否已授权」
10157
10732
  // 看的是 ask_user_question 的**回答**,不是提问正文。历史 bug:模型里没有这个字段、
10158
10733
  // 路由也没透传,TS 的授权判定退化成「提问正文非空」,等于问过任何问题即已授权。
10159
- output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)")
10734
+ output: z.string().optional().describe("Tool result / user answer (needed by L2 consent checks)"),
10735
+ // 动作真实发生时间(可选)。供 P4「提权→外传」时间窗口判定;缺省时该性质
10736
+ // 退回不设窗口的旧行为。与 Python ActionItem.called_at 对齐。
10737
+ called_at: z.string().optional().describe("Action timestamp (optional)")
10160
10738
  });
10161
10739
  var PromptTurnSchema = z.object({
10162
10740
  text: z.string().default("").describe("User prompt text at this turn"),
@@ -10222,23 +10800,39 @@ var ReviewResponseSchema = z.object({
10222
10800
 
10223
10801
  // src/engine/analyzer.ts
10224
10802
  init_esm_shims();
10803
+ init_security_policy();
10225
10804
  init_coordinator();
10226
- import { existsSync as existsSync17, mkdirSync as mkdirSync7, readFileSync as readFileSync17, writeFileSync as writeFileSync7 } from "fs";
10227
- import { tmpdir as tmpdir2 } from "os";
10228
- import { join as join15, resolve as resolve10 } from "path";
10805
+ import { existsSync as existsSync18, readFileSync as readFileSync18 } from "fs";
10806
+ import { tmpdir as tmpdir3 } from "os";
10807
+ import { join as join16, resolve as resolve11 } from "path";
10229
10808
 
10230
10809
  // src/infra/policy-cache.ts
10231
10810
  init_esm_shims();
10811
+ init_security_policy();
10812
+ init_paths();
10232
10813
  init_config();
10233
- import { createHash as createHash3 } from "crypto";
10234
- import { existsSync as existsSync16, mkdirSync as mkdirSync6, readdirSync as readdirSync5, readFileSync as readFileSync16, rmdirSync, unlinkSync as unlinkSync3, writeFileSync as writeFileSync6 } from "fs";
10235
- import { tmpdir } from "os";
10236
- import { join as join14 } from "path";
10237
- var CACHE_DIR = join14(tmpdir(), "agent_moss_policy_cache");
10814
+ import { createHash as createHash4 } from "crypto";
10815
+ import { existsSync as existsSync17, lstatSync as lstatSync2, readdirSync as readdirSync5, readFileSync as readFileSync17, rmdirSync, unlinkSync as unlinkSync3 } from "fs";
10816
+ import { tmpdir as tmpdir2 } from "os";
10817
+ import { join as join15 } from "path";
10818
+ var CACHE_DIR = join15(tmpdir2(), "agent_moss_policy_cache");
10238
10819
  var cacheDirOverride = null;
10239
10820
  function cacheDir() {
10240
10821
  return cacheDirOverride ?? CACHE_DIR;
10241
10822
  }
10823
+ function safeCacheDir() {
10824
+ const dir = cacheDir();
10825
+ if (ensurePrivateDir(dir)) return dir;
10826
+ try {
10827
+ const fallback = join15(getDataRoot(), "policy_cache");
10828
+ if (ensurePrivateDir(fallback)) {
10829
+ console.warn(`[AgentMoss] \u7F13\u5B58\u76EE\u5F55 ${dir} \u4E0D\u5B89\u5168\uFF08\u88AB\u5176\u4ED6\u7528\u6237\u62E5\u6709\u6216\u4E3A\u7B26\u53F7\u94FE\u63A5\uFF09\uFF0C\u5DF2\u56DE\u9000\u5230 ${fallback}`);
10830
+ return fallback;
10831
+ }
10832
+ } catch {
10833
+ }
10834
+ return dir;
10835
+ }
10242
10836
  var PolicyCache = class {
10243
10837
  // Map 的插入序即 LRU 序(get 时重新 set 到末尾)
10244
10838
  constructor(options) {
@@ -10250,9 +10844,9 @@ var PolicyCache = class {
10250
10844
  return `${sessionId}::${promptSession}`;
10251
10845
  }
10252
10846
  cacheFilePath(sessionId, promptSession) {
10253
- const promptHash = createHash3("md5").update(promptSession).digest("hex").slice(0, 12);
10254
- const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10255
- return join14(cacheDir(), safeSessionId, `${promptHash}.toml`);
10847
+ const promptHash = createHash4("md5").update(promptSession).digest("hex").slice(0, 12);
10848
+ const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10849
+ return join15(safeCacheDir(), safeSessionId, `${promptHash}.toml`);
10256
10850
  }
10257
10851
  /** 取缓存:内存 → 文件;未命中返回 null。 */
10258
10852
  get(sessionId, promptSession) {
@@ -10265,9 +10859,9 @@ var PolicyCache = class {
10265
10859
  return hit;
10266
10860
  }
10267
10861
  const file = this.cacheFilePath(sessionId, promptSession);
10268
- if (existsSync16(file)) {
10862
+ if (existsSync17(file) && !lstatSync2(file).isSymbolicLink()) {
10269
10863
  try {
10270
- const policy = readFileSync16(file, "utf-8");
10864
+ const policy = readFileSync17(file, "utf-8");
10271
10865
  this.remember(key, policy);
10272
10866
  return policy;
10273
10867
  } catch {
@@ -10281,8 +10875,10 @@ var PolicyCache = class {
10281
10875
  this.remember(this.cacheKey(sessionId, promptSession), policy);
10282
10876
  const file = this.cacheFilePath(sessionId, promptSession);
10283
10877
  try {
10284
- mkdirSync6(join14(cacheDir(), sessionId.replace(/\//g, "_").replace(/\\/g, "_")), { recursive: true });
10285
- writeFileSync6(file, policy, "utf-8");
10878
+ const dir = safeCacheDir();
10879
+ const sub = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10880
+ if (!ensurePrivateDir(join15(dir, sub))) return;
10881
+ writePrivateText(file, policy);
10286
10882
  } catch {
10287
10883
  }
10288
10884
  }
@@ -10306,13 +10902,13 @@ var PolicyCache = class {
10306
10902
  for (const k of [...this.memory.keys()]) {
10307
10903
  if (k.startsWith(`${sessionId}::`)) this.memory.delete(k);
10308
10904
  }
10309
- const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10310
- const dir = join14(cacheDir(), safeSessionId);
10905
+ const safeSessionId = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
10906
+ const dir = join15(cacheDir(), safeSessionId);
10311
10907
  try {
10312
- if (existsSync16(dir)) {
10908
+ if (existsSync17(dir)) {
10313
10909
  for (const f of readdirSync5(dir)) {
10314
10910
  try {
10315
- unlinkSync3(join14(dir, f));
10911
+ unlinkSync3(join15(dir, f));
10316
10912
  } catch {
10317
10913
  }
10318
10914
  }
@@ -10324,11 +10920,11 @@ var PolicyCache = class {
10324
10920
  clear() {
10325
10921
  this.memory.clear();
10326
10922
  try {
10327
- if (existsSync16(cacheDir())) {
10923
+ if (existsSync17(cacheDir())) {
10328
10924
  for (const sessionDir of readdirSync5(cacheDir())) {
10329
- const dir = join14(cacheDir(), sessionDir);
10925
+ const dir = join15(cacheDir(), sessionDir);
10330
10926
  try {
10331
- for (const f of readdirSync5(dir)) unlinkSync3(join14(dir, f));
10927
+ for (const f of readdirSync5(dir)) unlinkSync3(join15(dir, f));
10332
10928
  rmdirSync(dir);
10333
10929
  } catch {
10334
10930
  }
@@ -10415,7 +11011,7 @@ ${safe(policy, 2e3)}`);
10415
11011
 
10416
11012
  // src/engine/analyzer.ts
10417
11013
  import { parse as parseToml } from "smol-toml";
10418
- var POLICY_OUTPUT_DIR = join15(tmpdir2(), "agent_moss");
11014
+ var POLICY_OUTPUT_DIR = join16(tmpdir3(), "agent_moss");
10419
11015
  var policyOutputDirOverride = null;
10420
11016
  function policyOutputDir() {
10421
11017
  return policyOutputDirOverride ?? POLICY_OUTPUT_DIR;
@@ -10449,10 +11045,13 @@ function savePolicyToFile(sessionId, policyToml) {
10449
11045
  if (!isPolicySaveEnabled()) return null;
10450
11046
  try {
10451
11047
  const outDir = policyOutputDir();
10452
- mkdirSync7(outDir, { recursive: true });
10453
- const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_");
10454
- const filepath = join15(outDir, `${safeName}.toml`);
10455
- writeFileSync7(filepath, policyToml, "utf-8");
11048
+ if (!ensurePrivateDir(outDir)) {
11049
+ console.warn(`[AgentMoss] Policy \u8F93\u51FA\u76EE\u5F55 ${outDir} \u4E0D\u5B89\u5168\uFF0C\u8DF3\u8FC7\u843D\u76D8\uFF08\u4E0D\u5F71\u54CD\u5224\u5B9A\uFF09`);
11050
+ return null;
11051
+ }
11052
+ const safeName = sessionId.replace(/\//g, "_").replace(/\\/g, "_").replace(/\.\./g, "__");
11053
+ const filepath = join16(outDir, `${safeName}.toml`);
11054
+ writePrivateText(filepath, policyToml);
10456
11055
  return filepath;
10457
11056
  } catch (err) {
10458
11057
  console.warn(`[AgentMoss] \u7B56\u7565\u6587\u4EF6\u843D\u76D8\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u5224\u5B9A\uFF09: ${policyOutputDir()}: ${String(err)}`);
@@ -10489,12 +11088,12 @@ function validateToml(tomlStr) {
10489
11088
  function loadPolicyMappingDoc() {
10490
11089
  const dir = resolveAssetDir(__dirname, "templates");
10491
11090
  if (dir) {
10492
- const p = resolve10(dir, "policy_mapping.md");
10493
- if (existsSync17(p)) return readFileSync17(p, "utf-8");
11091
+ const p = resolve11(dir, "policy_mapping.md");
11092
+ if (existsSync18(p)) return readFileSync18(p, "utf-8");
10494
11093
  }
10495
11094
  for (const cand of assetDirCandidates(__dirname, "templates")) {
10496
- const p = resolve10(cand, "policy_mapping.md");
10497
- if (existsSync17(p)) return readFileSync17(p, "utf-8");
11095
+ const p = resolve11(cand, "policy_mapping.md");
11096
+ if (existsSync18(p)) return readFileSync18(p, "utf-8");
10498
11097
  }
10499
11098
  return "";
10500
11099
  }
@@ -11013,9 +11612,9 @@ function normalizeObservablePayload(body) {
11013
11612
  init_rule_store();
11014
11613
  init_skill_manager();
11015
11614
  init_asset_dir();
11016
- import { join as join17, dirname as dirname11 } from "path";
11615
+ import { join as join18, dirname as dirname12 } from "path";
11017
11616
  import { fileURLToPath as fileURLToPath10 } from "url";
11018
- var __dirname9 = dirname11(fileURLToPath10(import.meta.url));
11617
+ var __dirname9 = dirname12(fileURLToPath10(import.meta.url));
11019
11618
  async function healthRoute(c) {
11020
11619
  return c.json(HealthResponseSchema.parse({
11021
11620
  instance: process.env.AGENT_MOSS_INSTANCE || ""
@@ -11030,7 +11629,23 @@ async function analyzeRoute(c) {
11030
11629
  if (!osType) {
11031
11630
  osType = process.platform === "win32" ? "windows" : "linux";
11032
11631
  }
11033
- const agentId = req.agent_id || req.metadata?.agent_type || "";
11632
+ const _principal = principalOf(c.req.raw.headers, new URL(c.req.url).pathname);
11633
+ const [_boundOk, agentId] = bindAgent(_principal, req.agent_id || req.metadata?.agent_type || "");
11634
+ if (!_boundOk) {
11635
+ return c.json({ detail: `agent_id \u4E0E\u6240\u6301\u51ED\u8BC1\u4E0D\u5339\u914D: ${agentId}` }, 403);
11636
+ }
11637
+ const _sweepKey = _principal || `ip:${c.req.header("x-forwarded-for") || "local"}`;
11638
+ const [_sweepN, _sweepHit] = noteSessionAccess(_sweepKey, req.session_id);
11639
+ if (_sweepHit) {
11640
+ if (sessionSweepAction() === "deny") {
11641
+ return c.json({
11642
+ detail: `\u77ED\u65F6\u95F4\u5185\u8BBF\u95EE\u4E86 ${_sweepN} \u4E2A\u4E0D\u540C\u4F1A\u8BDD\uFF0C\u8D85\u51FA\u9608\u503C ${sessionSweepThreshold()}\uFF08\u7591\u4F3C\u5192\u5145/\u626B\u63CF\uFF09`
11643
+ }, 429);
11644
+ }
11645
+ console.warn(
11646
+ `[AgentMoss] \u26A0\uFE0F \u8DE8\u4F1A\u8BDD\u6279\u91CF\u8BBF\u95EE: key=${_sweepKey} \u7A97\u53E3\u5185 ${_sweepN} \u4E2A\u4E0D\u540C session\uFF08\u9608\u503C ${sessionSweepThreshold()}\uFF09`
11647
+ );
11648
+ }
11034
11649
  const result = await analyze({
11035
11650
  sessionId: req.session_id,
11036
11651
  promptSession: req.prompt_session,
@@ -11041,14 +11656,16 @@ async function analyzeRoute(c) {
11041
11656
  actionHistory: req.action_history.map((h) => ({
11042
11657
  actionType: h.action_type,
11043
11658
  actionDetail: h.action_detail,
11044
- output: h.output
11659
+ output: h.output,
11660
+ calledAt: h.called_at
11045
11661
  })),
11046
11662
  sessionHistory: (req.session_history || []).map((s) => ({
11047
11663
  sessionId: s.session_id,
11048
11664
  actionHistory: (s.action_history || []).map((h) => ({
11049
11665
  actionType: h.action_type,
11050
11666
  actionDetail: h.action_detail,
11051
- output: h.output
11667
+ output: h.output,
11668
+ calledAt: h.called_at
11052
11669
  }))
11053
11670
  })),
11054
11671
  // prompt_history 必须透传:L2 意图一致性(logic-rules.ts 的
@@ -11075,7 +11692,8 @@ async function analyzeRoute(c) {
11075
11692
  // agent_id:AgentGate V1 normalizer 历史上把 agent_type 塞 metadata,
11076
11693
  // 顶层为空时兜底(OpenDesk 的 agent-moss 需 agent_id 加载专属规则)。
11077
11694
  agentId,
11078
- llmConfig: req.metadata?.llm_config,
11695
+ // 严格模式:完全不接受 per-request LLM 配置(默认接受,保持原行为)
11696
+ llmConfig: perRequestLlmEnabled() ? req.metadata?.llm_config : void 0,
11079
11697
  llmLogPath: req.metadata?.llm_log_path
11080
11698
  });
11081
11699
  const durationMs = performance.now() - t0;
@@ -11123,7 +11741,8 @@ async function analyzeRoute(c) {
11123
11741
  violated_policy: "",
11124
11742
  confidence: 90,
11125
11743
  analysis_duration_ms: Math.round((performance.now() - t0) * 10) / 10,
11126
- violation_chain: judgment.violation_chain
11744
+ // 跨会话节点脱敏(时间线按 agent_id 共享,原样返回等于泄露别人的命令原文)
11745
+ violation_chain: redactChain(judgment.violation_chain, req.session_id)
11127
11746
  })
11128
11747
  );
11129
11748
  }
@@ -11147,14 +11766,19 @@ async function analyzeRoute(c) {
11147
11766
  })
11148
11767
  );
11149
11768
  } catch (err) {
11150
- console.error("[AgentMoss] Analysis error:", err);
11151
11769
  if (err instanceof SyntaxError) {
11152
11770
  return c.json({ detail: err.message, error: "Invalid JSON" }, 422);
11153
11771
  }
11154
11772
  if (err?.issues) {
11155
11773
  return c.json({ detail: err.issues, error: "Validation error" }, 422);
11156
11774
  }
11157
- return c.json({ detail: String(err), error: "Internal error" }, 500);
11775
+ if (errorDetailMode() === "full") {
11776
+ console.error("[AgentMoss] Analysis error:", err);
11777
+ return c.json({ detail: String(err), error: "Internal error" }, 500);
11778
+ }
11779
+ const ref = randomBytes2(4).toString("hex");
11780
+ console.error(`[AgentMoss] Analysis internal error (ref=${ref}):`, err);
11781
+ return c.json({ detail: `Internal server error (ref: ${ref})`, error: "Internal error" }, 500);
11158
11782
  }
11159
11783
  }
11160
11784
  var reviewStore = null;
@@ -11193,6 +11817,11 @@ async function reviewRoute(c) {
11193
11817
  if (!tracker) {
11194
11818
  tracker = new StatefulChainTracker(agentId);
11195
11819
  }
11820
+ if (req.db_path && !pathWithin(req.db_path, reviewRoots())) {
11821
+ return c.json({
11822
+ detail: "db_path \u4E0D\u5728\u5141\u8BB8\u8BFB\u53D6\u7684\u76EE\u5F55\u5185\uFF1B\u6807\u51C6 AgentGate \u90E8\u7F72\u65E0\u9700\u914D\u7F6E\uFF0C\u81EA\u5B9A\u4E49\u8DEF\u5F84\u8BF7\u52A0\u5165 AGENT_MOSS_REVIEW_PATHS"
11823
+ }, 403);
11824
+ }
11196
11825
  let sessions = [];
11197
11826
  if (req.db_path) {
11198
11827
  sessions = loadAgentGateSessions(req.db_path);
@@ -11256,15 +11885,20 @@ async function reviewRoute(c) {
11256
11885
  discovered_patterns: discovered
11257
11886
  }));
11258
11887
  } catch (err) {
11259
- console.error("[AgentMoss] Review error:", err);
11260
- return c.json({ error: "Internal error", detail: String(err) }, 500);
11888
+ if (errorDetailMode() === "full") {
11889
+ console.error("[AgentMoss] Review error:", err);
11890
+ return c.json({ error: "Internal error", detail: String(err) }, 500);
11891
+ }
11892
+ const ref = randomBytes2(4).toString("hex");
11893
+ console.error(`[AgentMoss] Review internal error (ref=${ref}):`, err);
11894
+ return c.json({ error: "Internal error", detail: `Internal server error (ref: ${ref})` }, 500);
11261
11895
  }
11262
11896
  }
11263
11897
  function getBrainRuleStore() {
11264
11898
  return new BrainRuleStore();
11265
11899
  }
11266
11900
  function getBrainSkillManager() {
11267
- const dir = resolveAssetDir(__dirname9, "skills") ?? join17(__dirname9, "..", "..", "agent_moss", "skills");
11901
+ const dir = resolveAssetDir(__dirname9, "skills") ?? join18(__dirname9, "..", "..", "agent_moss", "skills");
11268
11902
  return new BrainSkillManager(dir);
11269
11903
  }
11270
11904
  async function brainRulesRoute(c) {
@@ -11367,11 +12001,12 @@ init_rules_loader();
11367
12001
  init_asset_dir();
11368
12002
  init_rules_mode();
11369
12003
  import { Hono } from "hono";
11370
- import { readFileSync as readFileSync19, existsSync as existsSync19, unlinkSync as unlinkSync4 } from "fs";
11371
- import { join as join18, dirname as dirname12 } from "path";
12004
+ import { timingSafeEqual as timingSafeEqual2 } from "crypto";
12005
+ import { readFileSync as readFileSync20, existsSync as existsSync20, unlinkSync as unlinkSync4 } from "fs";
12006
+ import { join as join19, dirname as dirname13 } from "path";
11372
12007
  import { fileURLToPath as fileURLToPath11 } from "url";
11373
- var __dirname10 = dirname12(fileURLToPath11(import.meta.url));
11374
- var STATIC_DIR = join18(__dirname10, "static");
12008
+ var __dirname10 = dirname13(fileURLToPath11(import.meta.url));
12009
+ var STATIC_DIR = join19(__dirname10, "static");
11375
12010
  var CONSOLE_TOKEN_ENV = "AGENT_MOSS_CONSOLE_TOKEN";
11376
12011
  function getConsoleToken() {
11377
12012
  return process.env[CONSOLE_TOKEN_ENV] || "";
@@ -11380,7 +12015,11 @@ function verifyAuth(req) {
11380
12015
  const token = getConsoleToken();
11381
12016
  if (!token) return true;
11382
12017
  const auth = req.headers.get("Authorization") || "";
11383
- if (auth.startsWith("Bearer ")) return auth.slice(7) === token;
12018
+ if (auth.startsWith("Bearer ")) {
12019
+ const a = Buffer.from(auth.slice(7), "utf-8");
12020
+ const b = Buffer.from(token, "utf-8");
12021
+ return a.length === b.length && timingSafeEqual2(a, b);
12022
+ }
11384
12023
  return false;
11385
12024
  }
11386
12025
  function authMiddleware() {
@@ -11409,9 +12048,9 @@ function createConsoleRouter() {
11409
12048
  console2.use("/api/*", authMiddleware());
11410
12049
  console2.use("/api/*", builtinOnlyReadOnlyMiddleware());
11411
12050
  const serveIndex = (c) => {
11412
- const indexFile = join18(STATIC_DIR, "index.html");
11413
- if (existsSync19(indexFile)) {
11414
- return c.html(readFileSync19(indexFile, "utf-8"));
12051
+ const indexFile = join19(STATIC_DIR, "index.html");
12052
+ if (existsSync20(indexFile)) {
12053
+ return c.html(readFileSync20(indexFile, "utf-8"));
11415
12054
  }
11416
12055
  return c.html("<h1>AgentMoss Policy Console</h1><p>Frontend not built. Use /console/api/* endpoints.</p>");
11417
12056
  };
@@ -11606,15 +12245,15 @@ function createConsoleRouter() {
11606
12245
  if (!/^[A-Za-z0-9_-]+$/.test(skillId)) {
11607
12246
  return c.json({ detail: "\u975E\u6CD5 skill id" }, 400);
11608
12247
  }
11609
- const userPath = join18(dirname12(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
12248
+ const userPath = join19(dirname13(getRuntimeConfigPath()), "agent_moss_skills", `${skillId}.md`);
11610
12249
  const builtinDir = resolveAssetDir(__dirname10, "skills");
11611
12250
  const candidates = [
11612
12251
  ...isRuntimeConfigDisabled() ? [] : [userPath],
11613
- builtinDir ? join18(builtinDir, `${skillId}.md`) : ""
12252
+ builtinDir ? join19(builtinDir, `${skillId}.md`) : ""
11614
12253
  ].filter(Boolean);
11615
12254
  for (const p of candidates) {
11616
- if (existsSync19(p)) {
11617
- return c.json({ skill_id: skillId, path: p, content: readFileSync19(p, "utf-8") });
12255
+ if (existsSync20(p)) {
12256
+ return c.json({ skill_id: skillId, path: p, content: readFileSync20(p, "utf-8") });
11618
12257
  }
11619
12258
  }
11620
12259
  return c.json({ detail: `skill ${skillId} \u4E0D\u5B58\u5728` }, 404);
@@ -11711,7 +12350,7 @@ function createConsoleRouter() {
11711
12350
  const defaults = generateSourceDefaults();
11712
12351
  try {
11713
12352
  const p = getRuntimeConfigPath();
11714
- if (existsSync19(p)) unlinkSync4(p);
12353
+ if (existsSync20(p)) unlinkSync4(p);
11715
12354
  } catch {
11716
12355
  }
11717
12356
  return c.json(withL22View(defaults));
@@ -11768,7 +12407,7 @@ function logDeploymentSelfCheck() {
11768
12407
  console.log("\u5C42\u7EA7\u5F00\u5173: " + layerKeys.map((k) => `${k}=${isLayerEnabled(k) ? "on" : "OFF"}`).join(" "));
11769
12408
  if (!builtinOnly) {
11770
12409
  try {
11771
- const dirs = rulesDirs().filter((d) => existsSync20(d));
12410
+ const dirs = rulesDirs().filter((d) => existsSync21(d));
11772
12411
  if (dirs.length > 0) {
11773
12412
  console.log(`\u89C4\u5219\u76EE\u5F55: ${dirs.join(", ")}\uFF08\u53EA\u8BFB\u5176\u4E2D ${OVERRIDE_FILENAME} / ${EXTRA_FILENAME}\uFF09`);
11774
12413
  }
@@ -11806,11 +12445,134 @@ function logDeploymentSelfCheck() {
11806
12445
  console.error(`\u90E8\u7F72\u81EA\u68C0\u5931\u8D25\uFF08\u4E0D\u5F71\u54CD\u670D\u52A1\uFF09: ${exc instanceof Error ? exc.message : String(exc)}`);
11807
12446
  }
11808
12447
  }
12448
+ var PooledLimiter = class {
12449
+ counts = /* @__PURE__ */ new Map();
12450
+ tryAcquire(key, limit) {
12451
+ if (limit <= 0) return true;
12452
+ const cur = this.counts.get(key) || 0;
12453
+ if (cur >= limit) return false;
12454
+ this.counts.set(key, cur + 1);
12455
+ return true;
12456
+ }
12457
+ release(key) {
12458
+ const cur = this.counts.get(key) || 0;
12459
+ if (cur <= 1) this.counts.delete(key);
12460
+ else this.counts.set(key, cur - 1);
12461
+ }
12462
+ };
12463
+ var RateLimiter = class {
12464
+ constructor(qps, burst) {
12465
+ this.qps = qps;
12466
+ this.burst = burst;
12467
+ }
12468
+ qps;
12469
+ burst;
12470
+ buckets = /* @__PURE__ */ new Map();
12471
+ get enabled() {
12472
+ return this.qps > 0;
12473
+ }
12474
+ allow(key) {
12475
+ if (!this.enabled) return true;
12476
+ const burst = this.burst ?? Math.max(this.qps, 1) * 2;
12477
+ const now = Date.now() / 1e3;
12478
+ const [tokens, last] = this.buckets.get(key) ?? [burst, now];
12479
+ const next = Math.min(burst, tokens + (now - last) * this.qps);
12480
+ if (next < 1) {
12481
+ this.buckets.set(key, [next, now]);
12482
+ return false;
12483
+ }
12484
+ this.buckets.set(key, [next - 1, now]);
12485
+ if (this.buckets.size > 1e4) this.buckets.clear();
12486
+ return true;
12487
+ }
12488
+ };
11809
12489
  function createApp() {
11810
12490
  enforceRulesContract();
12491
+ hardenExistingPaths();
11811
12492
  const app = new Hono2();
11812
- app.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"], allowHeaders: ["*"] }));
12493
+ app.use("*", cors({
12494
+ origin: (o) => allowedOrigins().includes(o) ? o : null,
12495
+ allowMethods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
12496
+ allowHeaders: ["*"]
12497
+ }));
11813
12498
  app.use("*", logger());
12499
+ const pools = new PooledLimiter();
12500
+ const limiter = new RateLimiter(rateLimitQps());
12501
+ app.use("*", async (c, next) => {
12502
+ const path6 = new URL(c.req.url).pathname;
12503
+ const method = c.req.method.toUpperCase();
12504
+ if (method === "OPTIONS") return next();
12505
+ const maxBody = maxBodyBytes();
12506
+ const cl = c.req.header("content-length");
12507
+ if (cl && maxBody > 0) {
12508
+ const n = Number.parseInt(cl, 10);
12509
+ if (Number.isFinite(n) && n > maxBody) {
12510
+ return c.json({ detail: `\u8BF7\u6C42\u4F53\u8D85\u8FC7\u4E0A\u9650 ${maxBody} \u5B57\u8282` }, 413);
12511
+ }
12512
+ }
12513
+ if (originCheckEnabled()) {
12514
+ const origin = c.req.header("origin") || "";
12515
+ if (origin && !originAllowed(origin, c.req.header("host") || "")) {
12516
+ console.warn(`[AgentMoss] \u5DF2\u62E6\u622A\u8DE8\u6E90\u8BF7\u6C42: origin=${origin} path=${path6}`);
12517
+ return c.json({ detail: `\u8DE8\u6E90\u6765\u6E90\u672A\u88AB\u5141\u8BB8: ${origin}` }, 403);
12518
+ }
12519
+ }
12520
+ const [okAuth, principal] = checkRequestAuth(c.req.raw.headers, path6);
12521
+ const isPublicPage = !path6.startsWith("/console/api") && (path6 === "/console" || path6.startsWith("/console/"));
12522
+ if (!okAuth && !isPublicPage) {
12523
+ return c.json({ detail: "Unauthorized: invalid or missing Bearer token" }, 401);
12524
+ }
12525
+ if (limiter.enabled && path6.startsWith("/api/")) {
12526
+ const key = principal ? "p:" + principalHash(principal) : "c:" + (c.req.header("x-forwarded-for") || "local");
12527
+ if (!limiter.allow(key)) {
12528
+ return c.json({ detail: "\u8BF7\u6C42\u8FC7\u4E8E\u9891\u7E41\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5" }, 429);
12529
+ }
12530
+ }
12531
+ const ip = c.req.header("x-forwarded-for") || "local";
12532
+ const keys = [];
12533
+ const take = (key, limit) => {
12534
+ if (pools.tryAcquire(key, limit)) {
12535
+ keys.push(key);
12536
+ return true;
12537
+ }
12538
+ return false;
12539
+ };
12540
+ const busy = (detail, status = 503) => {
12541
+ c.header("Retry-After", "1");
12542
+ c.header("Connection", "close");
12543
+ return c.json({ detail }, status);
12544
+ };
12545
+ try {
12546
+ if (!principal && authEnabled()) {
12547
+ if (!take("unauth:total", unauthInflight())) {
12548
+ return busy("\u672A\u8BA4\u8BC1\u8BF7\u6C42\u5E76\u53D1\u8D85\u9650\uFF0C\u8BF7\u6301\u51ED\u8BC1\u8BBF\u95EE");
12549
+ }
12550
+ if (!take(`unauth:ip:${ip}`, unauthPerIp())) {
12551
+ return busy("\u8BE5\u6765\u6E90\u7684\u672A\u8BA4\u8BC1\u8BF7\u6C42\u5E76\u53D1\u8D85\u9650");
12552
+ }
12553
+ } else {
12554
+ const bound = boundAgentOf(principal);
12555
+ if (bound && !take(`agent:${bound}`, agentInflight())) {
12556
+ return busy(`agent '${bound}' \u7684\u5E76\u53D1\u8D85\u9650\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5`, 429);
12557
+ }
12558
+ }
12559
+ const pool = poolOf(path6);
12560
+ if (!take(`pool:${pool}`, poolLimit(pool))) {
12561
+ const qkey = principal ? "p:" + principalHash(principal) : "c:local";
12562
+ const [qn, abusive] = noteQueueReject(qkey);
12563
+ if (abusive && queueAbuseAction() === "deny") {
12564
+ return busy("\u5E76\u53D1\u62D2\u7EDD\u8FC7\u4E8E\u9891\u7E41\uFF0C\u7591\u4F3C\u6076\u610F\u8BF7\u6C42", 429);
12565
+ }
12566
+ if (abusive) {
12567
+ console.warn(`[AgentMoss] \u26A0\uFE0F \u7591\u4F3C\u6076\u610F\u5E76\u53D1\u5360\u7528: key=${qkey} \u7A97\u53E3\u5185\u88AB\u62D2 ${qn} \u6B21`);
12568
+ }
12569
+ return busy("\u670D\u52A1\u7E41\u5FD9\uFF08\u5E76\u53D1\u5DF2\u6EE1\uFF09\uFF0C\u8BF7\u7A0D\u540E\u91CD\u8BD5");
12570
+ }
12571
+ return await next();
12572
+ } finally {
12573
+ while (keys.length) pools.release(keys.pop());
12574
+ }
12575
+ });
11814
12576
  app.get("/api/v1/health", healthRoute);
11815
12577
  app.post("/api/v1/analyze", analyzeRoute);
11816
12578
  app.post("/api/v1/review", reviewRoute);
@@ -11828,12 +12590,12 @@ function createApp() {
11828
12590
  }
11829
12591
  async function findFreePort(startPort, host = "127.0.0.1", scan = 100) {
11830
12592
  for (let port = startPort; port < startPort + scan; port++) {
11831
- const free = await new Promise((resolve11) => {
12593
+ const free = await new Promise((resolve12) => {
11832
12594
  const server = net.createServer();
11833
12595
  server.listen(port, host, () => {
11834
- server.close(() => resolve11(true));
12596
+ server.close(() => resolve12(true));
11835
12597
  });
11836
- server.on("error", () => resolve11(false));
12598
+ server.on("error", () => resolve12(false));
11837
12599
  });
11838
12600
  if (free) {
11839
12601
  if (port !== startPort) {
@@ -11876,6 +12638,7 @@ async function warmUpEngine() {
11876
12638
  }
11877
12639
  }
11878
12640
  async function startServer(host = "127.0.0.1", port = 9090) {
12641
+ failFastOnAuthError();
11879
12642
  const actualPort = await findFreePort(port, host);
11880
12643
  const app = createApp();
11881
12644
  return {
@@ -11885,7 +12648,12 @@ async function startServer(host = "127.0.0.1", port = 9090) {
11885
12648
  logDeploymentSelfCheck();
11886
12649
  await warmUpEngine();
11887
12650
  const { serve: honoServe } = await import("@hono/node-server");
11888
- honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
12651
+ const srv = honoServe({ fetch: app.fetch, hostname: host, port: actualPort });
12652
+ try {
12653
+ srv.maxConnections = maxConnections();
12654
+ srv.keepAliveTimeout = keepAliveSec() * 1e3;
12655
+ } catch {
12656
+ }
11889
12657
  }
11890
12658
  };
11891
12659
  }
@@ -11896,28 +12664,29 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
11896
12664
  socketPath,
11897
12665
  close: async () => {
11898
12666
  if (!server) return;
11899
- await new Promise((resolve11) => {
11900
- server.close(() => resolve11());
12667
+ await new Promise((resolve12) => {
12668
+ server.close(() => resolve12());
11901
12669
  });
11902
12670
  server = null;
11903
12671
  },
11904
12672
  serve: async () => {
11905
- const { mkdirSync: mkdirSync8, existsSync: existsSync21, unlinkSync: unlinkSync5 } = await import("fs");
11906
- const { dirname: dirname13 } = await import("path");
11907
- const dir = dirname13(socketPath);
12673
+ failFastOnAuthError();
12674
+ const { mkdirSync: mkdirSync8, existsSync: existsSync22, unlinkSync: unlinkSync5 } = await import("fs");
12675
+ const { dirname: dirname14 } = await import("path");
12676
+ const dir = dirname14(socketPath);
11908
12677
  if (dir) {
11909
12678
  try {
11910
12679
  mkdirSync8(dir, { recursive: true });
11911
12680
  } catch {
11912
12681
  }
11913
12682
  }
11914
- if (existsSync21(socketPath)) {
12683
+ if (existsSync22(socketPath)) {
11915
12684
  const { connect } = await import("net");
11916
- const inUse = await new Promise((resolve11) => {
12685
+ const inUse = await new Promise((resolve12) => {
11917
12686
  const probe = connect(socketPath);
11918
12687
  const done = (v) => {
11919
12688
  probe.destroy();
11920
- resolve11(v);
12689
+ resolve12(v);
11921
12690
  };
11922
12691
  probe.once("connect", () => done(true));
11923
12692
  probe.once("error", () => done(false));
@@ -11940,10 +12709,22 @@ async function startUnixSocketServer(socketPath = "/var/run/agent_moss/agent_mos
11940
12709
  const { getRequestListener } = await import("@hono/node-server");
11941
12710
  const { createServer } = await import("http");
11942
12711
  server = createServer(getRequestListener(app.fetch));
11943
- await new Promise((resolve11, reject) => {
12712
+ try {
12713
+ server.maxConnections = maxConnections();
12714
+ server.keepAliveTimeout = keepAliveSec() * 1e3;
12715
+ } catch {
12716
+ }
12717
+ await new Promise((resolve12, reject) => {
11944
12718
  server.once("error", reject);
11945
- server.listen(socketPath, () => resolve11());
12719
+ server.listen(socketPath, () => resolve12());
11946
12720
  });
12721
+ try {
12722
+ const { chmodSync: chmodSync2 } = await import("fs");
12723
+ chmodSync2(socketPath, socketMode());
12724
+ console.log(`[AgentMoss] socket \u6743\u9650: 0${socketMode().toString(8)}\uFF08AGENT_MOSS_SOCKET_MODE\uFF09`);
12725
+ } catch (e) {
12726
+ console.warn(`[AgentMoss] \u8BBE\u7F6E socket \u6743\u9650\u5931\u8D25: ${String(e)}`);
12727
+ }
11947
12728
  console.log(`AgentMoss HTTP server on unix socket: ${socketPath}`);
11948
12729
  }
11949
12730
  };