a55951f315
- Replace Python-based sanitizer in deploy script with lightweight jq/alpine - Add sync-agents-sanitized.mjs for on-demand and watch-mode sync - Add AgentConfigPath to appsettings.json (explicit default) - Extend /health/live endpoint to report agent count from sanitized config - Document architecture in docs/agent-identity-architecture.md - No openclaw.json secrets ever reach Nexus API containers Verification: - curl /api/v1/agents → 9 agents, zero secrets in response - agents-sanitized.json contains only 'agents' key, no gateway/auth - All C# code paths read from agents-sanitized.json (AgentConfigPath) - Bridge controller resolves agent IDs via AgentService.GetAllowedAgentIdsAsync()
100 lines
2.7 KiB
JavaScript
100 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* sync-agents-sanitized.mjs
|
|
*
|
|
* Keeps agents-sanitized.json in sync with openclaw.json.
|
|
* Strips all secrets (gateway, channels, auth, tools, plugins, etc.)
|
|
* and only writes the "agents" key.
|
|
*
|
|
* Modes:
|
|
* --once Run once and exit
|
|
* --watch Watch openclaw.json and re-generate on changes (default)
|
|
*
|
|
* Usage:
|
|
* node sync-agents-sanitized.mjs --once
|
|
* node sync-agents-sanitized.mjs --watch
|
|
*
|
|
* Paths (defaults, override with OPENCLAW_CONFIG and SANITIZED_OUTPUT env vars):
|
|
* Source: /home/node/.openclaw/openclaw.json
|
|
* Output: /home/node/.openclaw/agents-sanitized.json
|
|
*/
|
|
|
|
import { watch } from 'node:fs';
|
|
import { readFile, writeFile } from 'node:fs/promises';
|
|
|
|
const SRC = process.env.OPENCLAW_CONFIG || '/home/node/.openclaw/openclaw.json';
|
|
const OUT = process.env.SANITIZED_OUTPUT || '/home/node/.openclaw/agents-sanitized.json';
|
|
|
|
let running = true;
|
|
let debounceTimer = null;
|
|
const DEBOUNCE_MS = 500;
|
|
|
|
function log(msg) {
|
|
const ts = new Date().toISOString();
|
|
process.stderr.write(`[agents-sanitized ${ts}] ${msg}\n`);
|
|
}
|
|
|
|
async function generate() {
|
|
try {
|
|
const raw = await readFile(SRC, 'utf-8');
|
|
const data = JSON.parse(raw);
|
|
|
|
const agents = data?.agents;
|
|
if (!agents) {
|
|
log(`ERROR: "agents" key not found in ${SRC}`);
|
|
return false;
|
|
}
|
|
|
|
const sanitized = { agents };
|
|
const json = JSON.stringify(sanitized, null, 2) + '\n';
|
|
await writeFile(OUT, json, 'utf-8');
|
|
|
|
const agentCount = agents?.list?.length ?? 0;
|
|
log(`Generated ${OUT} with ${agentCount} agents (${json.length} bytes)`);
|
|
return true;
|
|
} catch (err) {
|
|
log(`ERROR: ${err.message}`);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const mode = process.argv.includes('--once') ? 'once' : 'watch';
|
|
log(`Starting in ${mode} mode`);
|
|
log(` Source: ${SRC}`);
|
|
log(` Output: ${OUT}`);
|
|
|
|
// Initial generation
|
|
const ok = await generate();
|
|
if (mode === 'once') {
|
|
process.exit(ok ? 0 : 1);
|
|
}
|
|
|
|
// Watch mode
|
|
log('Watching for changes...');
|
|
watch(SRC, (eventType) => {
|
|
if (eventType !== 'change') return;
|
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
debounceTimer = setTimeout(async () => {
|
|
log(`Detected change in ${SRC}`);
|
|
await generate();
|
|
}, DEBOUNCE_MS);
|
|
});
|
|
|
|
// Keep process alive
|
|
process.on('SIGINT', () => { running = false; process.exit(0); });
|
|
process.on('SIGTERM', () => { running = false; process.exit(0); });
|
|
|
|
// Periodic check every 5 minutes as fallback
|
|
setInterval(async () => {
|
|
const now = Date.now();
|
|
log('Periodic re-sync check');
|
|
await generate();
|
|
}, 5 * 60 * 1000);
|
|
}
|
|
|
|
main().catch((err) => {
|
|
log(`FATAL: ${err.message}`);
|
|
process.exit(1);
|
|
});
|