micro-models-agent 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,41 @@
1
1
  import { existsSync, readFileSync, unlinkSync, writeFileSync, mkdirSync } from 'fs';
2
2
  import { join, dirname } from 'path';
3
3
  import { DEFAULTS } from './defaults';
4
+ import { DEFAULT_SECURITY_CONFIG } from './security';
4
5
  import { t } from '../i18n/index';
5
6
  import { MigrationDetector } from '../migration/detect';
6
7
  import { BackupManager } from '../migration/backup';
7
8
  import { validateExpertConfig } from './experts';
8
9
  import { ConfigEncryptor } from '../modules/security/encryption';
10
+ /**
11
+ * Restore RegExp instances in dangerousPatterns that were serialized as {}
12
+ * (pre-0.8.0 configs) or as {__regex, source, flags} (new format).
13
+ * Falls back to default patterns for any entry that is not a real RegExp.
14
+ */
15
+ function restoreDangerousPatterns(patterns, defaults) {
16
+ const fallback = (Array.isArray(defaults) ? defaults : []);
17
+ if (!Array.isArray(patterns) || patterns.length === 0) {
18
+ return fallback;
19
+ }
20
+ return patterns.map((p, i) => {
21
+ if (p instanceof RegExp)
22
+ return p;
23
+ // If the pattern was serialized as {__regex, source, flags}, revive it
24
+ if (p && typeof p === "object") {
25
+ const { source, flags } = p;
26
+ if (source && typeof source === "string") {
27
+ try {
28
+ return new RegExp(source, flags || "");
29
+ }
30
+ catch {
31
+ // fall through
32
+ }
33
+ }
34
+ }
35
+ // Corrupted entry (e.g. serialized as {} pre-0.8.0): use default by index
36
+ return fallback[i] || fallback[0] || p;
37
+ });
38
+ }
9
39
  function deepMerge(target, source) {
10
40
  const result = { ...target };
11
41
  for (const key of Object.keys(source)) {
@@ -22,10 +52,37 @@ function deepMerge(target, source) {
22
52
  }
23
53
  return result;
24
54
  }
55
+ /**
56
+ * JSON replacer that serializes RegExp objects as {__regex, source, flags}
57
+ * so they survive JSON.stringify/parse round-trips.
58
+ */
59
+ function regexReplacer(_key, value) {
60
+ if (value instanceof RegExp) {
61
+ return { __regex: true, source: value.source, flags: value.flags };
62
+ }
63
+ return value;
64
+ }
65
+ /**
66
+ * JSON reviver that restores RegExp objects serialized by regexReplacer.
67
+ */
68
+ function regexReviver(_key, value) {
69
+ if (value &&
70
+ typeof value === "object" &&
71
+ value.__regex === true) {
72
+ const { source, flags } = value;
73
+ try {
74
+ return new RegExp(source, flags);
75
+ }
76
+ catch {
77
+ return value;
78
+ }
79
+ }
80
+ return value;
81
+ }
25
82
  function loadJSON(path) {
26
83
  try {
27
84
  if (existsSync(path)) {
28
- return JSON.parse(readFileSync(path, 'utf-8'));
85
+ return JSON.parse(readFileSync(path, 'utf-8'), regexReviver);
29
86
  }
30
87
  }
31
88
  catch { /* ignore malformed files */ }
@@ -84,6 +141,11 @@ export function loadConfig(options) {
84
141
  if (projectData) {
85
142
  config = deepMerge(config, projectData);
86
143
  }
144
+ // Restore RegExp patterns in contentScan that may have been serialized
145
+ // as {} in pre-0.8.0 config files, or merged from user config.
146
+ if (config.security?.contentScan?.dangerousPatterns) {
147
+ config.security.contentScan.dangerousPatterns = restoreDangerousPatterns(config.security.contentScan.dangerousPatterns, DEFAULT_SECURITY_CONFIG.contentScan.dangerousPatterns);
148
+ }
87
149
  config = applyEnvVars(config);
88
150
  // Decrypt sensitive fields in the loaded config
89
151
  try {
@@ -98,6 +160,7 @@ export function loadConfig(options) {
98
160
  // If decryption fails, log a warning but continue with the config
99
161
  console.warn(t('config.decryption_warning', { error: e.message }));
100
162
  }
163
+ // Update global audit notifier with config (done in bootstrap.ts)
101
164
  return config;
102
165
  }
103
166
  export function validateConfig(config, allToolTags) {
@@ -113,11 +176,11 @@ export function saveConfig(config, configPath) {
113
176
  try {
114
177
  const encryptor = new ConfigEncryptor();
115
178
  const encryptedConfig = encryptor.encrypt({ ...config });
116
- writeFileSync(configPath, JSON.stringify(encryptedConfig, null, 2), 'utf-8');
179
+ writeFileSync(configPath, JSON.stringify(encryptedConfig, regexReplacer, 2), 'utf-8');
117
180
  }
118
181
  catch (e) {
119
182
  // If encryption fails, save without encryption
120
183
  console.warn(t('config.encryption_warning', { error: e.message }));
121
- writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');
184
+ writeFileSync(configPath, JSON.stringify(config, regexReplacer, 2), 'utf-8');
122
185
  }
123
186
  }
@@ -90,7 +90,7 @@ export const DEFAULT_SECURITY_CONFIG = {
90
90
  },
91
91
  network: {
92
92
  // Domains that are always denied
93
- deniedDomains: [],
93
+ deniedDomains: ["localhost", "127.0.0.1", "::1"],
94
94
  // If allowedDomains is non-empty, only these domains are allowed
95
95
  allowedDomains: [],
96
96
  // Timeout for network requests (ms)
@@ -64,6 +64,16 @@ export async function bootstrap(configDir, projectDir, noAgentsMd, exitOnComplet
64
64
  : join(process.cwd(), ".mmrc");
65
65
  const config = loadConfig({ configDir: dir, projectConfigPath });
66
66
  setLocale(config.locale);
67
+ // Update global audit notifier with config
68
+ try {
69
+ const { globalAuditNotifier } = await import("../modules/security/audit-notifier");
70
+ if (config.security?.auditNotifier) {
71
+ globalAuditNotifier.updateConfig(config.security.auditNotifier);
72
+ }
73
+ }
74
+ catch {
75
+ // Ignore if audit notifier is not available
76
+ }
67
77
  const logger = new Logger(config.logLevel);
68
78
  logger.setLogDir(join(dir, "logs"));
69
79
  logger.debug("MMA bootstrap", {