@jungjaehoon/mama-os 0.13.3 → 0.14.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/agent/agent-loop.d.ts +1 -1
- package/dist/agent/agent-loop.d.ts.map +1 -1
- package/dist/agent/agent-loop.js +9 -2
- package/dist/agent/agent-loop.js.map +1 -1
- package/dist/agent/codex-mcp-process.d.ts +1 -0
- package/dist/agent/codex-mcp-process.d.ts.map +1 -1
- package/dist/agent/codex-mcp-process.js +50 -1
- package/dist/agent/codex-mcp-process.js.map +1 -1
- package/dist/agent/gateway-tool-executor.d.ts.map +1 -1
- package/dist/agent/gateway-tool-executor.js +101 -1
- package/dist/agent/gateway-tool-executor.js.map +1 -1
- package/dist/agent/types.d.ts +1 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/api/auth-middleware.d.ts +46 -0
- package/dist/api/auth-middleware.d.ts.map +1 -0
- package/dist/api/auth-middleware.js +206 -0
- package/dist/api/auth-middleware.js.map +1 -0
- package/dist/api/cron-handler.d.ts.map +1 -1
- package/dist/api/cron-handler.js +10 -0
- package/dist/api/cron-handler.js.map +1 -1
- package/dist/api/graph-api.d.ts.map +1 -1
- package/dist/api/graph-api.js +53 -57
- package/dist/api/graph-api.js.map +1 -1
- package/dist/api/index.d.ts +2 -2
- package/dist/api/index.d.ts.map +1 -1
- package/dist/api/index.js +32 -1
- package/dist/api/index.js.map +1 -1
- package/dist/api/token-handler.d.ts +4 -4
- package/dist/api/token-handler.d.ts.map +1 -1
- package/dist/api/token-handler.js.map +1 -1
- package/dist/auth/oauth-manager.d.ts.map +1 -1
- package/dist/auth/oauth-manager.js +16 -1
- package/dist/auth/oauth-manager.js.map +1 -1
- package/dist/cli/commands/start.d.ts.map +1 -1
- package/dist/cli/commands/start.js +244 -27
- package/dist/cli/commands/start.js.map +1 -1
- package/dist/gateways/attachment-utils.d.ts.map +1 -1
- package/dist/gateways/attachment-utils.js +292 -4
- package/dist/gateways/attachment-utils.js.map +1 -1
- package/dist/gateways/channel-history.d.ts +3 -3
- package/dist/gateways/channel-history.d.ts.map +1 -1
- package/dist/gateways/channel-history.js +38 -2
- package/dist/gateways/channel-history.js.map +1 -1
- package/dist/gateways/plugin-loader.d.ts.map +1 -1
- package/dist/gateways/plugin-loader.js +12 -0
- package/dist/gateways/plugin-loader.js.map +1 -1
- package/dist/gateways/session-store.d.ts +2 -2
- package/dist/gateways/session-store.d.ts.map +1 -1
- package/dist/gateways/session-store.js.map +1 -1
- package/dist/gateways/slack.d.ts.map +1 -1
- package/dist/gateways/slack.js +0 -2
- package/dist/gateways/slack.js.map +1 -1
- package/dist/multi-agent/swarm/swarm-db.d.ts +12 -12
- package/dist/multi-agent/swarm/swarm-db.d.ts.map +1 -1
- package/dist/multi-agent/swarm/swarm-db.js +2 -2
- package/dist/multi-agent/swarm/swarm-db.js.map +1 -1
- package/dist/multi-agent/swarm/swarm-manager.d.ts +2 -2
- package/dist/multi-agent/swarm/swarm-manager.d.ts.map +1 -1
- package/dist/multi-agent/swarm/swarm-manager.js.map +1 -1
- package/dist/multi-agent/swarm/swarm-task-runner.d.ts.map +1 -1
- package/dist/multi-agent/swarm/swarm-task-runner.js.map +1 -1
- package/dist/observability/metrics-store.d.ts.map +1 -1
- package/dist/observability/metrics-store.js +2 -2
- package/dist/observability/metrics-store.js.map +1 -1
- package/dist/scheduler/cron-scheduler.d.ts.map +1 -1
- package/dist/scheduler/cron-scheduler.js +7 -0
- package/dist/scheduler/cron-scheduler.js.map +1 -1
- package/dist/scheduler/heartbeat.d.ts +1 -0
- package/dist/scheduler/heartbeat.d.ts.map +1 -1
- package/dist/scheduler/heartbeat.js +9 -1
- package/dist/scheduler/heartbeat.js.map +1 -1
- package/dist/scheduler/schedule-store.d.ts +2 -2
- package/dist/scheduler/schedule-store.d.ts.map +1 -1
- package/dist/scheduler/schedule-store.js.map +1 -1
- package/dist/security/security-monitor.d.ts +34 -0
- package/dist/security/security-monitor.d.ts.map +1 -0
- package/dist/security/security-monitor.js +662 -0
- package/dist/security/security-monitor.js.map +1 -0
- package/dist/security/trusted-proxy.d.ts +9 -0
- package/dist/security/trusted-proxy.d.ts.map +1 -0
- package/dist/security/trusted-proxy.js +36 -0
- package/dist/security/trusted-proxy.js.map +1 -0
- package/dist/sqlite.d.ts +29 -0
- package/dist/sqlite.d.ts.map +1 -0
- package/dist/sqlite.js +124 -0
- package/dist/sqlite.js.map +1 -0
- package/package.json +5 -6
|
@@ -14,6 +14,7 @@ const promises_1 = require("node:fs/promises");
|
|
|
14
14
|
const node_fs_1 = require("node:fs");
|
|
15
15
|
const node_os_1 = require("node:os");
|
|
16
16
|
const node_path_1 = require("node:path");
|
|
17
|
+
const node_crypto_1 = require("node:crypto");
|
|
17
18
|
const types_js_1 = require("./types.js");
|
|
18
19
|
/**
|
|
19
20
|
* Anthropic OAuth constants (from OpenClaw analysis)
|
|
@@ -223,7 +224,21 @@ class OAuthManager {
|
|
|
223
224
|
refreshToken: token.refreshToken,
|
|
224
225
|
expiresAt: token.expiresAt,
|
|
225
226
|
};
|
|
226
|
-
|
|
227
|
+
// Atomic write: write to temp file, then rename to prevent corruption on crash
|
|
228
|
+
const tmpPath = `${this.credentialsPath}.${(0, node_crypto_1.randomBytes)(4).toString('hex')}.tmp`;
|
|
229
|
+
await (0, promises_1.writeFile)(tmpPath, JSON.stringify(data, null, 2), 'utf-8');
|
|
230
|
+
try {
|
|
231
|
+
await (0, promises_1.rename)(tmpPath, this.credentialsPath);
|
|
232
|
+
}
|
|
233
|
+
catch (renameError) {
|
|
234
|
+
try {
|
|
235
|
+
await (0, promises_1.unlink)(tmpPath);
|
|
236
|
+
}
|
|
237
|
+
catch {
|
|
238
|
+
// Ignore cleanup failure and rethrow original rename error below.
|
|
239
|
+
}
|
|
240
|
+
throw renameError;
|
|
241
|
+
}
|
|
227
242
|
}
|
|
228
243
|
catch (error) {
|
|
229
244
|
if (error instanceof types_js_1.OAuthError) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"oauth-manager.js","sourceRoot":"","sources":["../../src/auth/oauth-manager.ts"],"names":[],"mappings":";AAAA;;;;;;;;GAQG;;;AAEH,+
|
|
1
|
+
{"version":3,"file":"oauth-manager.js","sourceRoot":"","sources":["../../src/auth/oauth-manager.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;
|
|
1
|
+
{"version":3,"file":"start.d.ts","sourceRoot":"","sources":["../../../src/cli/commands/start.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAwBH,OAAO,EACL,UAAU,EAKX,MAAM,6BAA6B,CAAC;AAihBrC;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,wBAAsB,YAAY,CAAC,OAAO,GAAE,YAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAwG5E;AAuQD;;GAEG;AACH,wBAAsB,YAAY,CAChC,MAAM,EAAE,OAAO,CAAC,UAAU,CAAC,OAAO,UAAU,CAAC,CAAC,EAC9C,OAAO,GAAE;IAAE,WAAW,CAAC,EAAE,OAAO,CAAA;CAAO,GACtC,OAAO,CAAC,IAAI,CAAC,CAkjEf"}
|
|
@@ -47,7 +47,6 @@ const node_child_process_1 = require("node:child_process");
|
|
|
47
47
|
const node_events_1 = require("node:events");
|
|
48
48
|
const node_fs_1 = require("node:fs");
|
|
49
49
|
const node_os_1 = require("node:os");
|
|
50
|
-
const better_sqlite3_1 = __importDefault(require("better-sqlite3"));
|
|
51
50
|
const express_1 = __importDefault(require("express"));
|
|
52
51
|
const node_path_1 = __importStar(require("node:path"));
|
|
53
52
|
const ws_1 = require("ws");
|
|
@@ -67,6 +66,8 @@ const metrics_cleanup_js_1 = require("../../observability/metrics-cleanup.js");
|
|
|
67
66
|
const health_score_js_1 = require("../../observability/health-score.js");
|
|
68
67
|
const health_check_js_1 = require("../../observability/health-check.js");
|
|
69
68
|
const upload_handler_js_1 = require("../../api/upload-handler.js");
|
|
69
|
+
const auth_middleware_js_1 = require("../../api/auth-middleware.js");
|
|
70
|
+
const security_monitor_js_1 = require("../../security/security-monitor.js");
|
|
70
71
|
const setup_websocket_js_1 = require("../../setup/setup-websocket.js");
|
|
71
72
|
// Onboarding state imports removed — onboarding is handled by Setup Wizard only
|
|
72
73
|
const graph_api_js_1 = require("../../api/graph-api.js");
|
|
@@ -76,12 +77,40 @@ const { DebugLogger } = debugLogger;
|
|
|
76
77
|
const startLogger = new DebugLogger('start');
|
|
77
78
|
const skill_registry_js_1 = require("../../skills/skill-registry.js");
|
|
78
79
|
const node_http_1 = __importDefault(require("node:http"));
|
|
80
|
+
const sqlite_js_1 = __importDefault(require("../../sqlite.js"));
|
|
79
81
|
// Port configuration — single source of truth
|
|
80
82
|
/** Public-facing API server port (REST API, Viewer UI, Setup Wizard) */
|
|
81
83
|
const API_PORT = 3847;
|
|
82
84
|
/** Internal embedding server port (model inference, mobile chat, graph) */
|
|
83
85
|
const EMBEDDING_PORT = 3849;
|
|
86
|
+
function parseSecurityAlertTargets(config) {
|
|
87
|
+
const rawTargets = process.env.MAMA_SECURITY_ALERT_CHANNELS;
|
|
88
|
+
if (rawTargets && rawTargets.trim()) {
|
|
89
|
+
return rawTargets
|
|
90
|
+
.split(',')
|
|
91
|
+
.map((entry) => entry.trim())
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.map((entry) => {
|
|
94
|
+
const [gateway, channelId] = entry.split(':', 2);
|
|
95
|
+
if ((gateway === 'discord' || gateway === 'slack') && channelId) {
|
|
96
|
+
return { gateway, channelId };
|
|
97
|
+
}
|
|
98
|
+
return null;
|
|
99
|
+
})
|
|
100
|
+
.filter((target) => target !== null);
|
|
101
|
+
}
|
|
102
|
+
if (config.discord?.default_channel_id) {
|
|
103
|
+
return [{ gateway: 'discord', channelId: config.discord.default_channel_id }];
|
|
104
|
+
}
|
|
105
|
+
const slackConfig = config.slack;
|
|
106
|
+
const slackDefaultChannel = slackConfig?.default_channel || slackConfig?.default_channel_id;
|
|
107
|
+
if (slackDefaultChannel) {
|
|
108
|
+
return [{ gateway: 'slack', channelId: slackDefaultChannel }];
|
|
109
|
+
}
|
|
110
|
+
return [];
|
|
111
|
+
}
|
|
84
112
|
let embeddingServer = null;
|
|
113
|
+
let embeddingShutdownToken = null;
|
|
85
114
|
function normalizeDiscordGuilds(raw) {
|
|
86
115
|
// Reject arrays - they pass typeof 'object' check but get coerced to numeric keys
|
|
87
116
|
if (!raw || typeof raw !== 'object' || Array.isArray(raw)) {
|
|
@@ -164,7 +193,7 @@ async function waitForPortAvailable(port, maxWaitMs = 5000) {
|
|
|
164
193
|
* SECURITY P1: Validates health response before reuse
|
|
165
194
|
* SECURITY P1: Uses port polling instead of fixed timeout
|
|
166
195
|
*/
|
|
167
|
-
async function checkAndTakeoverExistingServer(port) {
|
|
196
|
+
async function checkAndTakeoverExistingServer(port, shutdownToken) {
|
|
168
197
|
const targetModel = (0, config_loader_1.getModelName)();
|
|
169
198
|
const targetDim = (0, config_loader_1.getEmbeddingDim)();
|
|
170
199
|
return new Promise((resolve) => {
|
|
@@ -214,9 +243,14 @@ async function checkAndTakeoverExistingServer(port) {
|
|
|
214
243
|
timeout: 2000,
|
|
215
244
|
// SECURITY P1: Pass shutdown token
|
|
216
245
|
headers: {
|
|
217
|
-
'X-Shutdown-Token': process.env.MAMA_SHUTDOWN_TOKEN || '',
|
|
246
|
+
'X-Shutdown-Token': shutdownToken || process.env.MAMA_SHUTDOWN_TOKEN || '',
|
|
218
247
|
},
|
|
219
|
-
}, async () => {
|
|
248
|
+
}, async (shutdownRes) => {
|
|
249
|
+
if ((shutdownRes.statusCode ?? 0) < 200 || (shutdownRes.statusCode ?? 0) >= 300) {
|
|
250
|
+
console.warn(`[EmbeddingServer] Takeover shutdown rejected with HTTP ${shutdownRes.statusCode ?? 0}`);
|
|
251
|
+
resolve(false);
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
220
254
|
console.log('[EmbeddingServer] MCP server shutdown requested');
|
|
221
255
|
// SECURITY P1: Use port polling instead of fixed timeout
|
|
222
256
|
const portAvailable = await waitForPortAvailable(port, 10000);
|
|
@@ -260,14 +294,18 @@ sessionStore,
|
|
|
260
294
|
graphHandler) {
|
|
261
295
|
const port = EMBEDDING_PORT;
|
|
262
296
|
try {
|
|
297
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
298
|
+
const embeddingServerModule = require('@jungjaehoon/mama-core/embedding-server');
|
|
299
|
+
embeddingShutdownToken =
|
|
300
|
+
typeof embeddingServerModule.SHUTDOWN_TOKEN === 'string'
|
|
301
|
+
? embeddingServerModule.SHUTDOWN_TOKEN
|
|
302
|
+
: null;
|
|
263
303
|
// Check if server already running
|
|
264
|
-
const existingHasChat = await checkAndTakeoverExistingServer(port);
|
|
304
|
+
const existingHasChat = await checkAndTakeoverExistingServer(port, embeddingShutdownToken);
|
|
265
305
|
if (existingHasChat) {
|
|
266
306
|
// Another Standalone is running with chat, no need to start
|
|
267
307
|
return;
|
|
268
308
|
}
|
|
269
|
-
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
270
|
-
const embeddingServerModule = require('@jungjaehoon/mama-core/embedding-server');
|
|
271
309
|
embeddingServer = await embeddingServerModule.startEmbeddingServer(port, {
|
|
272
310
|
messageRouter,
|
|
273
311
|
sessionStore,
|
|
@@ -782,7 +820,7 @@ async function runAgentLoop(config, options = {}) {
|
|
|
782
820
|
const oauthManager = new index_js_1.OAuthManager();
|
|
783
821
|
// Initialize database for session storage
|
|
784
822
|
const dbPath = (0, config_manager_js_1.expandPath)(config.database.path).replace('mama-memory.db', 'mama-sessions.db');
|
|
785
|
-
const db = new
|
|
823
|
+
const db = new sqlite_js_1.default(dbPath);
|
|
786
824
|
// Initialize metrics store (respects config.metrics.enabled)
|
|
787
825
|
const metricsEnabled = config.metrics?.enabled !== false;
|
|
788
826
|
let metricsStore = null;
|
|
@@ -928,9 +966,8 @@ async function runAgentLoop(config, options = {}) {
|
|
|
928
966
|
// - config.yaml roles.definitions.*.allowedTools / blockedTools / allowedPaths
|
|
929
967
|
// - Multi-agent ToolPermissionManager (tier-based tool access)
|
|
930
968
|
// - Source-based role mapping (viewer=os_agent, discord=chat_bot, etc.)
|
|
931
|
-
//
|
|
932
|
-
//
|
|
933
|
-
// DO NOT add env-var gates here — MAMA manages its own security via config.yaml roles.
|
|
969
|
+
// Headless daemon — no TTY for interactive permission prompts.
|
|
970
|
+
// Security is enforced at the API/network layer (auth-middleware), not Claude CLI permissions.
|
|
934
971
|
dangerouslySkipPermissions: config.multi_agent?.dangerouslySkipPermissions ?? true,
|
|
935
972
|
sessionKey: 'default', // Will be updated per message
|
|
936
973
|
systemPrompt: systemPrompt + (osCapabilities ? '\n\n---\n\n' + osCapabilities : ''),
|
|
@@ -1330,6 +1367,40 @@ async function runAgentLoop(config, options = {}) {
|
|
|
1330
1367
|
if (slackGateway) {
|
|
1331
1368
|
healthCheckService.addGateway('slack', slackGateway);
|
|
1332
1369
|
}
|
|
1370
|
+
const securityAlertTargets = parseSecurityAlertTargets(config).filter((target) => {
|
|
1371
|
+
if (target.gateway === 'discord') {
|
|
1372
|
+
return !!discordGateway;
|
|
1373
|
+
}
|
|
1374
|
+
return !!slackGateway;
|
|
1375
|
+
});
|
|
1376
|
+
if (securityAlertTargets.length > 0) {
|
|
1377
|
+
(0, security_monitor_js_1.setSecurityAlertSender)(async (event) => {
|
|
1378
|
+
const message = (0, security_monitor_js_1.formatSecurityAlert)(event);
|
|
1379
|
+
const results = await Promise.allSettled(securityAlertTargets.map(async (target) => {
|
|
1380
|
+
if (target.gateway === 'discord' && discordGateway) {
|
|
1381
|
+
await discordGateway.sendMessage(target.channelId, message);
|
|
1382
|
+
return;
|
|
1383
|
+
}
|
|
1384
|
+
if (target.gateway === 'slack' && slackGateway) {
|
|
1385
|
+
await slackGateway.sendMessage(target.channelId, message);
|
|
1386
|
+
}
|
|
1387
|
+
}));
|
|
1388
|
+
results.forEach((result, index) => {
|
|
1389
|
+
if (result.status === 'rejected') {
|
|
1390
|
+
const target = securityAlertTargets[index];
|
|
1391
|
+
startLogger.warn('[SECURITY] Failed to deliver security alert to target', {
|
|
1392
|
+
gateway: target?.gateway || 'unknown',
|
|
1393
|
+
channelId: target?.channelId || 'unknown',
|
|
1394
|
+
error: result.reason instanceof Error ? result.reason.message : String(result.reason),
|
|
1395
|
+
});
|
|
1396
|
+
}
|
|
1397
|
+
});
|
|
1398
|
+
});
|
|
1399
|
+
}
|
|
1400
|
+
else {
|
|
1401
|
+
(0, security_monitor_js_1.setSecurityAlertSender)(null);
|
|
1402
|
+
startLogger.warn('[SECURITY] No active security alert target configured. Set MAMA_SECURITY_ALERT_CHANNELS or configure an active Discord/Slack default channel.');
|
|
1403
|
+
}
|
|
1333
1404
|
// Wire cron results directly to gateways (bypasses OS agent entirely)
|
|
1334
1405
|
// Instantiated for side effects: subscribes to cronEmitter events
|
|
1335
1406
|
new index_js_4.CronResultRouter({
|
|
@@ -1612,7 +1683,7 @@ async function runAgentLoop(config, options = {}) {
|
|
|
1612
1683
|
}
|
|
1613
1684
|
});
|
|
1614
1685
|
// Add Discord message sending endpoint
|
|
1615
|
-
apiServer.app.post('/api/discord/send', async (req, res) => {
|
|
1686
|
+
apiServer.app.post('/api/discord/send', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1616
1687
|
try {
|
|
1617
1688
|
const { channelId, message } = req.body;
|
|
1618
1689
|
if (!channelId || !message) {
|
|
@@ -1634,7 +1705,7 @@ async function runAgentLoop(config, options = {}) {
|
|
|
1634
1705
|
}
|
|
1635
1706
|
});
|
|
1636
1707
|
// Add Slack message/file sending endpoint
|
|
1637
|
-
apiServer.app.post('/api/slack/send', async (req, res) => {
|
|
1708
|
+
apiServer.app.post('/api/slack/send', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1638
1709
|
try {
|
|
1639
1710
|
const { channelId, message, filePath, caption } = req.body;
|
|
1640
1711
|
if (!channelId || (!message && !filePath)) {
|
|
@@ -1698,7 +1769,7 @@ async function runAgentLoop(config, options = {}) {
|
|
|
1698
1769
|
}
|
|
1699
1770
|
});
|
|
1700
1771
|
// Add Discord cron job endpoint (run prompt and send result to Discord)
|
|
1701
|
-
apiServer.app.post('/api/discord/cron', async (req, res) => {
|
|
1772
|
+
apiServer.app.post('/api/discord/cron', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1702
1773
|
try {
|
|
1703
1774
|
const { channelId, prompt } = req.body;
|
|
1704
1775
|
if (!channelId || !prompt) {
|
|
@@ -1720,7 +1791,7 @@ async function runAgentLoop(config, options = {}) {
|
|
|
1720
1791
|
}
|
|
1721
1792
|
});
|
|
1722
1793
|
// Report endpoint - collect data and generate report (OpenClaw migration)
|
|
1723
|
-
apiServer.app.post('/api/report', async (req, res) => {
|
|
1794
|
+
apiServer.app.post('/api/report', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1724
1795
|
const { exec } = await import('child_process');
|
|
1725
1796
|
const { promisify } = await import('util');
|
|
1726
1797
|
const execAsync = promisify(exec);
|
|
@@ -1793,7 +1864,7 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
1793
1864
|
}
|
|
1794
1865
|
});
|
|
1795
1866
|
// Screenshot endpoint - take HTML screenshot and send to Discord
|
|
1796
|
-
apiServer.app.post('/api/screenshot', async (req, res) => {
|
|
1867
|
+
apiServer.app.post('/api/screenshot', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1797
1868
|
const { spawn } = await import('child_process');
|
|
1798
1869
|
const path = await import('path');
|
|
1799
1870
|
try {
|
|
@@ -1881,7 +1952,7 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
1881
1952
|
});
|
|
1882
1953
|
// Send image endpoint
|
|
1883
1954
|
// SECURITY P0: Path traversal prevention with 4-layer validation
|
|
1884
|
-
apiServer.app.post('/api/discord/image', async (req, res) => {
|
|
1955
|
+
apiServer.app.post('/api/discord/image', auth_middleware_js_1.requireAuth, async (req, res) => {
|
|
1885
1956
|
const path = await import('path');
|
|
1886
1957
|
const fs = await import('fs/promises');
|
|
1887
1958
|
try {
|
|
@@ -1956,6 +2027,20 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
1956
2027
|
});
|
|
1957
2028
|
// Upload/download media endpoints
|
|
1958
2029
|
apiServer.app.use('/api', (0, upload_handler_js_1.createUploadRouter)());
|
|
2030
|
+
// Auth gate for /graph/* write endpoints (not covered by /api middleware)
|
|
2031
|
+
apiServer.app.use('/graph', (req, res, next) => {
|
|
2032
|
+
const isRead = req.method === 'GET' || req.method === 'HEAD';
|
|
2033
|
+
if (!isRead && !(0, auth_middleware_js_1.isAuthenticated)(req)) {
|
|
2034
|
+
(0, auth_middleware_js_1.logUnauthorizedAttempt)(req);
|
|
2035
|
+
res.status(401).json({
|
|
2036
|
+
error: true,
|
|
2037
|
+
code: 'UNAUTHORIZED',
|
|
2038
|
+
message: 'Authentication required.',
|
|
2039
|
+
});
|
|
2040
|
+
return;
|
|
2041
|
+
}
|
|
2042
|
+
next();
|
|
2043
|
+
});
|
|
1959
2044
|
apiServer.app.use(async (req, res, next) => {
|
|
1960
2045
|
const handled = await graphHandler(req, res);
|
|
1961
2046
|
if (!handled)
|
|
@@ -2145,8 +2230,8 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2145
2230
|
res.json([]);
|
|
2146
2231
|
}
|
|
2147
2232
|
});
|
|
2148
|
-
apiServer.app.delete('/api/playgrounds/:slug', (req, res) => {
|
|
2149
|
-
const
|
|
2233
|
+
apiServer.app.delete('/api/playgrounds/:slug', auth_middleware_js_1.requireAuth, (req, res) => {
|
|
2234
|
+
const slug = req.params.slug;
|
|
2150
2235
|
if (!slug || /[^a-z0-9-]/.test(slug)) {
|
|
2151
2236
|
res.status(400).json({ error: 'Invalid slug' });
|
|
2152
2237
|
return;
|
|
@@ -2252,7 +2337,62 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2252
2337
|
// Handle ALL WebSocket upgrades manually
|
|
2253
2338
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
2254
2339
|
apiServer.server.on('upgrade', (request, socket, head) => {
|
|
2255
|
-
|
|
2340
|
+
let url;
|
|
2341
|
+
try {
|
|
2342
|
+
url = new URL(request.url || '', `http://${request.headers.host || 'localhost'}`);
|
|
2343
|
+
}
|
|
2344
|
+
catch (error) {
|
|
2345
|
+
startLogger.warn('[SECURITY] Malformed WebSocket upgrade URL rejected', {
|
|
2346
|
+
rawUrl: request.url || null,
|
|
2347
|
+
error: error instanceof Error ? error.message : String(error),
|
|
2348
|
+
});
|
|
2349
|
+
socket.destroy();
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2352
|
+
// WebSocket auth: require token for non-localhost connections.
|
|
2353
|
+
// Browsers cannot set Authorization headers on WebSocket upgrades,
|
|
2354
|
+
// so we allow query-string token auth for this path only.
|
|
2355
|
+
const adminToken = process.env.MAMA_AUTH_TOKEN || process.env.MAMA_SERVER_TOKEN;
|
|
2356
|
+
const context = (0, auth_middleware_js_1.getSecurityLogContext)(request);
|
|
2357
|
+
const isTrustedLocalUpgrade = (0, auth_middleware_js_1.isLocalRequest)(request) && !context.viaTunnel;
|
|
2358
|
+
if (adminToken && !(0, auth_middleware_js_1.isAuthenticated)(request, { allowQueryToken: true })) {
|
|
2359
|
+
const details = { hasQueryToken: url.searchParams.has('token') };
|
|
2360
|
+
startLogger.warn('[SECURITY] Unauthorized WebSocket upgrade blocked', {
|
|
2361
|
+
...context,
|
|
2362
|
+
...details,
|
|
2363
|
+
path: url.pathname,
|
|
2364
|
+
});
|
|
2365
|
+
(0, security_monitor_js_1.recordSecurityEvent)({
|
|
2366
|
+
type: 'unauthorized_websocket_upgrade',
|
|
2367
|
+
severity: 'warn',
|
|
2368
|
+
message: 'Unauthorized WebSocket upgrade blocked',
|
|
2369
|
+
...context,
|
|
2370
|
+
path: url.pathname,
|
|
2371
|
+
details,
|
|
2372
|
+
});
|
|
2373
|
+
socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');
|
|
2374
|
+
socket.destroy();
|
|
2375
|
+
return;
|
|
2376
|
+
}
|
|
2377
|
+
if (!adminToken && !isTrustedLocalUpgrade) {
|
|
2378
|
+
const details = { hasQueryToken: url.searchParams.has('token') };
|
|
2379
|
+
startLogger.warn('[SECURITY] Accepting non-localhost WebSocket upgrade without auth token configured', {
|
|
2380
|
+
...context,
|
|
2381
|
+
...details,
|
|
2382
|
+
path: url.pathname,
|
|
2383
|
+
});
|
|
2384
|
+
(0, security_monitor_js_1.recordSecurityEvent)({
|
|
2385
|
+
type: 'unprotected_websocket_upgrade',
|
|
2386
|
+
severity: 'critical',
|
|
2387
|
+
message: 'Non-localhost WebSocket upgrade accepted without auth token configured',
|
|
2388
|
+
...context,
|
|
2389
|
+
path: url.pathname,
|
|
2390
|
+
details,
|
|
2391
|
+
});
|
|
2392
|
+
socket.write('HTTP/1.1 403 Forbidden\r\n\r\n');
|
|
2393
|
+
socket.destroy();
|
|
2394
|
+
return;
|
|
2395
|
+
}
|
|
2256
2396
|
if (url.pathname === '/setup-ws') {
|
|
2257
2397
|
// Handle setup WebSocket locally
|
|
2258
2398
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -2299,6 +2439,7 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2299
2439
|
gateways.push(apiServer);
|
|
2300
2440
|
// Handle graceful shutdown with timeout
|
|
2301
2441
|
let shuttingDown = false;
|
|
2442
|
+
let keepAliveInterval = null;
|
|
2302
2443
|
const shutdown = async () => {
|
|
2303
2444
|
if (shuttingDown)
|
|
2304
2445
|
return; // Prevent double shutdown
|
|
@@ -2311,12 +2452,45 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2311
2452
|
if (healthWarningInterval) {
|
|
2312
2453
|
clearInterval(healthWarningInterval);
|
|
2313
2454
|
}
|
|
2455
|
+
if (keepAliveInterval) {
|
|
2456
|
+
clearInterval(keepAliveInterval);
|
|
2457
|
+
keepAliveInterval = null;
|
|
2458
|
+
}
|
|
2459
|
+
const getBlockingHandleNames = () => {
|
|
2460
|
+
const processWithHandles = process;
|
|
2461
|
+
const getActiveHandles = processWithHandles._getActiveHandles;
|
|
2462
|
+
if (typeof getActiveHandles !== 'function') {
|
|
2463
|
+
return ['unknown'];
|
|
2464
|
+
}
|
|
2465
|
+
const handles = getActiveHandles.call(processWithHandles);
|
|
2466
|
+
const ignoredHandles = new Set(['WriteStream', 'ReadStream', 'TTY', 'TTYWrap']);
|
|
2467
|
+
return (handles
|
|
2468
|
+
?.map((handle) => handle.constructor?.name)
|
|
2469
|
+
.filter((name) => typeof name === 'string' && !ignoredHandles.has(name)) ?? []);
|
|
2470
|
+
};
|
|
2471
|
+
const getActiveRequestNames = () => {
|
|
2472
|
+
const processWithRequests = process;
|
|
2473
|
+
const getActiveRequests = processWithRequests._getActiveRequests;
|
|
2474
|
+
if (typeof getActiveRequests !== 'function') {
|
|
2475
|
+
return ['unknown'];
|
|
2476
|
+
}
|
|
2477
|
+
const requests = getActiveRequests.call(processWithRequests);
|
|
2478
|
+
return (requests
|
|
2479
|
+
?.map((request) => request.constructor?.name)
|
|
2480
|
+
.filter((name) => typeof name === 'string') ?? []);
|
|
2481
|
+
};
|
|
2314
2482
|
// Force exit after 5 seconds if graceful shutdown hangs
|
|
2315
2483
|
// exit(0) = intentional stop; systemd Restart=on-failure should NOT restart
|
|
2316
|
-
setTimeout(() => {
|
|
2484
|
+
const forceExitTimer = setTimeout(() => {
|
|
2485
|
+
const blockingHandles = getBlockingHandleNames();
|
|
2486
|
+
const activeRequests = getActiveRequestNames();
|
|
2487
|
+
if (blockingHandles.length === 0 && activeRequests.length === 0) {
|
|
2488
|
+
return;
|
|
2489
|
+
}
|
|
2317
2490
|
console.error('[MAMA] Graceful shutdown timed out, forcing exit');
|
|
2318
|
-
process.
|
|
2491
|
+
process.kill(process.pid, 'SIGKILL');
|
|
2319
2492
|
}, 5000);
|
|
2493
|
+
forceExitTimer.unref();
|
|
2320
2494
|
try {
|
|
2321
2495
|
// Stop schedulers and cron worker first
|
|
2322
2496
|
scheduler.shutdown();
|
|
@@ -2325,8 +2499,38 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2325
2499
|
tokenKeepAlive.stop();
|
|
2326
2500
|
// Close embedding server (port 3849) - drain connections first
|
|
2327
2501
|
if (embeddingServer) {
|
|
2328
|
-
|
|
2329
|
-
|
|
2502
|
+
await new Promise((resolve) => {
|
|
2503
|
+
const shutdownReq = node_http_1.default.request({
|
|
2504
|
+
hostname: '127.0.0.1',
|
|
2505
|
+
port: EMBEDDING_PORT,
|
|
2506
|
+
path: '/shutdown',
|
|
2507
|
+
method: 'POST',
|
|
2508
|
+
timeout: 2000,
|
|
2509
|
+
headers: {
|
|
2510
|
+
'X-Shutdown-Token': embeddingShutdownToken || process.env.MAMA_SHUTDOWN_TOKEN || '',
|
|
2511
|
+
},
|
|
2512
|
+
}, async (response) => {
|
|
2513
|
+
const statusCode = response.statusCode ?? 0;
|
|
2514
|
+
const released = await waitForPortAvailable(EMBEDDING_PORT, 5000);
|
|
2515
|
+
if (statusCode >= 200 && statusCode < 300 && released) {
|
|
2516
|
+
resolve();
|
|
2517
|
+
return;
|
|
2518
|
+
}
|
|
2519
|
+
startLogger.warn(`[EmbeddingServer] Shutdown endpoint did not fully stop server (status=${statusCode}, released=${released})`);
|
|
2520
|
+
if (embeddingServer) {
|
|
2521
|
+
embeddingServer.close(() => resolve());
|
|
2522
|
+
return;
|
|
2523
|
+
}
|
|
2524
|
+
resolve();
|
|
2525
|
+
});
|
|
2526
|
+
shutdownReq.on('error', () => resolve());
|
|
2527
|
+
shutdownReq.on('timeout', () => {
|
|
2528
|
+
shutdownReq.destroy();
|
|
2529
|
+
resolve();
|
|
2530
|
+
});
|
|
2531
|
+
shutdownReq.end();
|
|
2532
|
+
});
|
|
2533
|
+
embeddingServer = null;
|
|
2330
2534
|
}
|
|
2331
2535
|
// Stop all gateways with per-gateway 2s timeout
|
|
2332
2536
|
const withTimeout = (p, ms) => Promise.race([p, new Promise((r) => setTimeout(r, ms))]);
|
|
@@ -2334,21 +2538,34 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2334
2538
|
// Stop plugin gateways
|
|
2335
2539
|
await withTimeout(pluginLoader.stopAll().catch(() => { }), 1000);
|
|
2336
2540
|
// Stop agent loop
|
|
2337
|
-
agentLoop.stop();
|
|
2541
|
+
await agentLoop.stop();
|
|
2338
2542
|
// Release all CLI sessions
|
|
2339
2543
|
(0, session_pool_js_1.getSessionPool)().dispose();
|
|
2340
2544
|
// Close session database
|
|
2341
2545
|
sessionStore.close();
|
|
2342
2546
|
// Stop metrics cleanup
|
|
2343
2547
|
metricsCleanup?.stop();
|
|
2548
|
+
metricsStore?.close();
|
|
2549
|
+
db.close();
|
|
2344
2550
|
const { deletePid } = await import('../utils/pid-manager.js');
|
|
2345
2551
|
await deletePid();
|
|
2552
|
+
const blockingHandles = getBlockingHandleNames();
|
|
2553
|
+
const activeRequests = getActiveRequestNames();
|
|
2554
|
+
if (blockingHandles.length === 0 && activeRequests.length === 0) {
|
|
2555
|
+
clearTimeout(forceExitTimer);
|
|
2556
|
+
}
|
|
2557
|
+
else if (blockingHandles.length === 0 &&
|
|
2558
|
+
activeRequests.length > 0 &&
|
|
2559
|
+
activeRequests.every((name) => name === 'FSReqPromise')) {
|
|
2560
|
+
process.kill(process.pid, 'SIGKILL');
|
|
2561
|
+
}
|
|
2346
2562
|
}
|
|
2347
2563
|
catch (error) {
|
|
2348
2564
|
// Best effort cleanup
|
|
2349
2565
|
console.warn('[MAMA] Cleanup error during shutdown:', error);
|
|
2350
2566
|
}
|
|
2351
|
-
process.
|
|
2567
|
+
process.exitCode = 0;
|
|
2568
|
+
return;
|
|
2352
2569
|
};
|
|
2353
2570
|
process.on('SIGINT', shutdown);
|
|
2354
2571
|
process.on('SIGTERM', shutdown);
|
|
@@ -2368,7 +2585,7 @@ Keep the report under 2000 characters as it will be sent to Discord.`;
|
|
|
2368
2585
|
console.log('MAMA agent is waiting...\n');
|
|
2369
2586
|
// Keep process alive using setInterval
|
|
2370
2587
|
// This ensures the Node.js event loop stays active
|
|
2371
|
-
setInterval(() => {
|
|
2588
|
+
keepAliveInterval = setInterval(() => {
|
|
2372
2589
|
// Heartbeat - keeps the process running
|
|
2373
2590
|
}, 30000); // Every 30 seconds
|
|
2374
2591
|
}
|