watchmyagents 0.8.0 → 0.8.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/package.json +2 -4
- package/scripts/service.js +31 -7
- package/scripts/shield.js +6 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "watchmyagents",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Security observability + real-time policy enforcement for AI agents. Local-first NDJSON capture with a continuous Watch daemon that auto-uploads anonymized signals, Shield CLI that blocks policy violations live (with policies pulled from Fortress cloud), anonymizer producing signals-only payloads, bidirectional sync with WatchMyAgents Fortress, and one-command install as an always-on launchd/systemd service — closing the recursive Watch→Guardian→Shield security loop.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"files": [
|
|
@@ -35,9 +35,7 @@
|
|
|
35
35
|
"node": ">=18.0.0"
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {},
|
|
38
|
-
"devDependencies": {
|
|
39
|
-
"@anthropic-ai/sdk": "^0.42.0"
|
|
40
|
-
},
|
|
38
|
+
"devDependencies": {},
|
|
41
39
|
"keywords": [
|
|
42
40
|
"ai",
|
|
43
41
|
"agents",
|
package/scripts/service.js
CHANGED
|
@@ -94,11 +94,15 @@ function launcherPath(label) { return join(CONFIG_DIR, `${label}.launcher.sh`);
|
|
|
94
94
|
|
|
95
95
|
function writeLauncher(label, scriptPath, args) {
|
|
96
96
|
const argLine = args.map(sh).join(' ');
|
|
97
|
+
// Load secrets with a read-loop, NOT '. file' / source. Sourcing would
|
|
98
|
+
// shell-evaluate each value, so a secret like FOO=https://x/$(cmd) would
|
|
99
|
+
// execute cmd at launch. A read-loop assigns the value literally — the
|
|
100
|
+
// content is never re-scanned for command substitution.
|
|
97
101
|
const body = `#!/bin/sh
|
|
98
|
-
# Generated by wma-service. Loads secrets
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
+
# Generated by wma-service. Loads secrets WITHOUT shell-evaluating their values.
|
|
103
|
+
while IFS='=' read -r __k __v; do
|
|
104
|
+
[ -n "$__k" ] && export "$__k=$__v"
|
|
105
|
+
done < ${sh(ENV_FILE)}
|
|
102
106
|
exec ${sh(NODE)} ${sh(scriptPath)} ${argLine}
|
|
103
107
|
`;
|
|
104
108
|
const p = launcherPath(label);
|
|
@@ -147,15 +151,35 @@ function launchctl(args, { ignoreError = false } = {}) {
|
|
|
147
151
|
}
|
|
148
152
|
}
|
|
149
153
|
|
|
154
|
+
// Synchronous sleep (installer CLI — blocking is fine). Used to let launchd's
|
|
155
|
+
// asynchronous bootout finish before we bootstrap again.
|
|
156
|
+
function syncSleep(ms) {
|
|
157
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
|
|
158
|
+
}
|
|
159
|
+
function macLoaded(label) {
|
|
160
|
+
try { execFileSync('launchctl', ['print', `gui/${UID}/${label}`], { stdio: 'pipe' }); return true; }
|
|
161
|
+
catch { return false; }
|
|
162
|
+
}
|
|
163
|
+
|
|
150
164
|
function macLoad(label, plist) {
|
|
151
165
|
const domain = `gui/${UID}`;
|
|
152
|
-
|
|
153
|
-
|
|
166
|
+
// bootout is async: on reinstall, bootstrapping again before the old instance
|
|
167
|
+
// is gone races and silently fails (symptom: reinstall = dead services).
|
|
168
|
+
// Wait for the prior instance to disappear, then retry bootstrap.
|
|
169
|
+
if (macLoaded(label)) {
|
|
170
|
+
launchctl(['bootout', `${domain}/${label}`], { ignoreError: true });
|
|
171
|
+
for (let i = 0; i < 20 && macLoaded(label); i++) syncSleep(150);
|
|
172
|
+
}
|
|
173
|
+
let ok = false;
|
|
174
|
+
for (let attempt = 0; attempt < 5 && !ok; attempt++) {
|
|
175
|
+
ok = launchctl(['bootstrap', domain, plist], { ignoreError: attempt < 4 });
|
|
176
|
+
if (!ok) syncSleep(250);
|
|
177
|
+
}
|
|
154
178
|
launchctl(['enable', `${domain}/${label}`], { ignoreError: true });
|
|
155
179
|
if (ok) info(`loaded ${label} (launchd) — running now + at every login`);
|
|
156
180
|
else {
|
|
157
181
|
warn(`could not auto-load ${label}. Load it manually:`);
|
|
158
|
-
process.stdout.write(` launchctl bootstrap gui/${UID} ${plist}\n`);
|
|
182
|
+
process.stdout.write(` launchctl bootout gui/${UID}/${label} 2>/dev/null; launchctl bootstrap gui/${UID} ${plist}\n`);
|
|
159
183
|
}
|
|
160
184
|
}
|
|
161
185
|
|
package/scripts/shield.js
CHANGED
|
@@ -36,7 +36,7 @@ import { DecisionLogger } from '../src/shield/decisions.js';
|
|
|
36
36
|
import { listSessions } from '../src/sources/anthropic-managed.js';
|
|
37
37
|
import { FortressPolicySource, postDecision } from '../src/shield/sources/fortress.js';
|
|
38
38
|
import { resolveFortressBase } from '../src/fortress/url.js';
|
|
39
|
-
import { isValidAgentId } from '../src/validate.js';
|
|
39
|
+
import { isValidAgentId, isValidSessionId } from '../src/validate.js';
|
|
40
40
|
|
|
41
41
|
function parseArgs(argv) {
|
|
42
42
|
const out = {};
|
|
@@ -429,6 +429,11 @@ async function main() {
|
|
|
429
429
|
if (!isValidAgentId(agentId)) {
|
|
430
430
|
die(`error: --agent-id has invalid format (expected "agent_" + alphanumeric, got "${agentId}")`);
|
|
431
431
|
}
|
|
432
|
+
// --session-id ends up in the Anthropic SSE URL path (src/shield/stream.js).
|
|
433
|
+
// Validate the same way wma-fetch does so a crafted value can't tamper the URL.
|
|
434
|
+
if (singleSessionId && !isValidSessionId(singleSessionId)) {
|
|
435
|
+
die(`error: --session-id has invalid format (expected "sesn_" + alphanumeric, got "${singleSessionId}")`);
|
|
436
|
+
}
|
|
432
437
|
|
|
433
438
|
// Policies source: --policies-source fortress | local (default infers from --policy)
|
|
434
439
|
let ruleset; // for 'local' mode: static; for 'fortress': initial snapshot
|