@goplus/agentguard 1.1.10 → 1.1.13

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.
Files changed (55) hide show
  1. package/README.md +6 -4
  2. package/dist/adapters/common.d.ts.map +1 -1
  3. package/dist/adapters/common.js +3 -1
  4. package/dist/adapters/common.js.map +1 -1
  5. package/dist/adapters/openclaw-plugin.d.ts.map +1 -1
  6. package/dist/adapters/openclaw-plugin.js +17 -3
  7. package/dist/adapters/openclaw-plugin.js.map +1 -1
  8. package/dist/cli.js +109 -5
  9. package/dist/cli.js.map +1 -1
  10. package/dist/config.d.ts +3 -1
  11. package/dist/config.d.ts.map +1 -1
  12. package/dist/config.js +12 -0
  13. package/dist/config.js.map +1 -1
  14. package/dist/feed/cron.d.ts +1 -0
  15. package/dist/feed/cron.d.ts.map +1 -1
  16. package/dist/feed/cron.js +366 -50
  17. package/dist/feed/cron.js.map +1 -1
  18. package/dist/installers.js +110 -16
  19. package/dist/installers.js.map +1 -1
  20. package/dist/postinstall.js +24 -4
  21. package/dist/postinstall.js.map +1 -1
  22. package/dist/registry/storage.d.ts.map +1 -1
  23. package/dist/registry/storage.js +5 -1
  24. package/dist/registry/storage.js.map +1 -1
  25. package/dist/tests/cli-init.test.js +90 -0
  26. package/dist/tests/cli-init.test.js.map +1 -1
  27. package/dist/tests/cli-subscribe.test.js +33 -0
  28. package/dist/tests/cli-subscribe.test.js.map +1 -1
  29. package/dist/tests/feed-cron.test.js +205 -15
  30. package/dist/tests/feed-cron.test.js.map +1 -1
  31. package/dist/tests/installer.test.js +26 -4
  32. package/dist/tests/installer.test.js.map +1 -1
  33. package/dist/tests/integration.test.js +9 -5
  34. package/dist/tests/integration.test.js.map +1 -1
  35. package/dist/tests/postinstall.test.js +8 -4
  36. package/dist/tests/postinstall.test.js.map +1 -1
  37. package/dist/tests/setup-script.test.d.ts +2 -0
  38. package/dist/tests/setup-script.test.d.ts.map +1 -0
  39. package/dist/tests/setup-script.test.js +63 -0
  40. package/dist/tests/setup-script.test.js.map +1 -0
  41. package/dist/tests/smoke.test.js +88 -1
  42. package/dist/tests/smoke.test.js.map +1 -1
  43. package/docs/codex.md +1 -1
  44. package/docs/hermes.md +3 -3
  45. package/package.json +1 -1
  46. package/skills/agentguard/SKILL.md +404 -199
  47. package/skills/agentguard/hermes-hooks.yaml +2 -2
  48. package/skills/agentguard/scan-rules.md +13 -2
  49. package/skills/agentguard/scripts/{action-cli.ts → action-cli.js} +13 -18
  50. package/skills/agentguard/scripts/auto-scan.js +3 -1
  51. package/skills/agentguard/scripts/checkup-score.js +369 -0
  52. package/skills/agentguard/scripts/hermes-hook.js +103 -16
  53. package/skills/agentguard/scripts/scan-to-sarif.js +195 -0
  54. package/skills/agentguard/scripts/{trust-cli.ts → trust-cli.js} +12 -16
  55. package/skills/agentguard/suppress.example.yaml +67 -0
@@ -30,8 +30,8 @@ filesystem-access:
30
30
  access: read-write
31
31
  reason: "Read/write audit log (audit.jsonl) and protection level config (config.json)"
32
32
  user-invocable: true
33
- allowed-tools: Read, Write, Grep, Glob, Bash(node *trust-cli.ts *) Bash(node *action-cli.ts *) Bash(*checkup-report.js) Bash(echo *checkup-report.js) Bash(cat *checkup-report.js) Bash(agentguard *) Bash(openclaw *) Bash(ss *) Bash(lsof *) Bash(ufw *) Bash(iptables *) Bash(crontab *) Bash(systemctl list-timers *) Bash(find *) Bash(stat *) Bash(env) Bash(sha256sum *) Bash(node *) Bash(cd *)
34
- argument-hint: "[scan|action|patrol|subscribe|trust|report|config|checkup|cli] [args...]"
33
+ allowed-tools: Read, Write, Grep, Glob, Bash(node *trust-cli.js *) Bash(node *action-cli.js *) Bash(*checkup-report.js) Bash(*checkup-score.js) Bash(*scan-to-sarif.js) Bash(echo *checkup-report.js) Bash(cat *checkup-report.js) Bash(agentguard *) Bash(openclaw *) Bash(ss *) Bash(lsof *) Bash(ufw *) Bash(iptables *) Bash(crontab *) Bash(systemctl list-timers *) Bash(find *) Bash(stat *) Bash(env) Bash(sha256sum *) Bash(node *) Bash(cd *)
34
+ argument-hint: "[scan|action|patrol|subscribe|trust|report|config|checkup|cli] [args...] [--format sarif|json] [--output <file>]"
35
35
  ---
36
36
 
37
37
  # GoPlus AgentGuard — AI Agent Security Framework
@@ -56,17 +56,17 @@ cd ~/.openclaw/skills/agentguard && node scripts/checkup-report.js
56
56
  ### Post-Install Guidance
57
57
 
58
58
  When AgentGuard has just been installed, or the user asks what to do next after
59
- installing AgentGuard, recommend these steps in this order instead of deriving a
60
- longer list from every supported command:
59
+ installing AgentGuard, recommend the required setup step below instead of
60
+ deriving a longer list from every supported command:
61
61
 
62
62
  ```bash
63
- agentguard init --agent <agent>
64
- agentguard connect
65
- agentguard checkup
63
+ agentguard init --agent auto
66
64
  ```
67
65
 
68
- Use the current agent host for `<agent>` when it is known; otherwise leave
69
- `<agent>` as a placeholder.
66
+ Use a specific agent such as `hermes`, `qclaw`, `openclaw`, `claude-code`, or
67
+ `codex` only when the current host is known and the user wants targeted setup.
68
+ Treat `agentguard connect` and `agentguard checkup` as optional follow-ups, not
69
+ required post-install steps.
70
70
 
71
71
  Parse `$ARGUMENTS` to determine the subcommand:
72
72
 
@@ -75,8 +75,8 @@ Parse `$ARGUMENTS` to determine the subcommand:
75
75
  - **`scan <path>`** — Scan a skill or codebase for security risks
76
76
  - **`action <description>`** — Evaluate whether a runtime action is safe
77
77
  - **`patrol [run|setup|status]`** — Daily security patrol for OpenClaw environments
78
+ - **`trust <lookup|attest|revoke|list|seed> [args]`** — Manage skill trust levels
78
79
  - **`subscribe [args...]`** — Pull AgentGuard Cloud threat-feed advisories, self-check local skills, and optionally install the OpenClaw 15-minute conditional notification cron
79
- - **`trust <lookup|attest|revoke|list> [args]`** — Manage skill trust levels
80
80
  - **`report`** — View recent security events from the audit log
81
81
  - **`config <strict|balanced|permissive>`** — Set protection level
82
82
  - **`checkup`** — Run a comprehensive agent health checkup and generate a visual HTML report
@@ -105,7 +105,7 @@ Supported CLI commands and options:
105
105
  | `agentguard policy show` | `--json` | Shows the cached effective runtime policy, or the bundled default policy when no cache exists |
106
106
  | `agentguard doctor` | none | Checks local setup and Cloud reachability when connected |
107
107
  | `agentguard protect` | `--agent <agent>`, `--action-type <type>`, `--tool-name <name>`, `--session-id <id>`, `--decision-mode <local-first|cloud>`, `--json` | Evaluates one runtime action from stdin or hook environment |
108
- | `agentguard subscribe` | `--since <iso>`, `--json`, `--quiet`, `--no-report`, `--cron <expr>`, `--cron-target <auto|openclaw|qclaw|hermes|system>`, `--cron-name <name>`, `--force`, `--cron-run` | Pulls Cloud threat advisories and optionally self-checks local skills |
108
+ | `agentguard subscribe` | `--since <iso>`, `--json`, `--quiet`, `--no-report`, `--cron <expr>`, `--cron-target <auto|openclaw|qclaw|hermes|system>`, `--cron-name <name>`, `--force`, `--cron-run`, `--cron-notify-run` | Pulls Cloud threat advisories and optionally self-checks local skills |
109
109
  | `agentguard checkup` | `--json` | Runs the local agent health checkup |
110
110
  | `agentguard checkup --against-advisory <id>` | `--json` | CLI threat-feed self-check for one advisory; this is a targeted mode, not the default health-check workflow |
111
111
 
@@ -120,8 +120,10 @@ If the user writes `/agentguard checkup --against-advisory <id>`, use the CLI co
120
120
  Help the user configure AgentGuard runtime protection for Hermes Agent.
121
121
 
122
122
  Hermes does **not** load hooks from `SKILL.md` automatically. Hermes shell hooks
123
- must be present in `~/.hermes/config.yaml`. This skill ships the hook runner at
124
- `scripts/hermes-hook.js` and a copyable template at `hermes-hooks.yaml`.
123
+ must be present in `~/.hermes/config.yaml`; `agentguard init --agent hermes`
124
+ now installs the skill and merges the AgentGuard hook entries automatically.
125
+ This skill ships the hook runner at `scripts/hermes-hook.js` and a copyable
126
+ template at `hermes-hooks.yaml`.
125
127
 
126
128
  ### What the Hermes hook protects
127
129
 
@@ -130,11 +132,13 @@ must be present in `~/.hermes/config.yaml`. This skill ships the hook runner at
130
132
  | `pre_tool_call` | `terminal`, `execute_code` | `exec_command` |
131
133
  | `pre_tool_call` | `write_file`, `patch`, `skill_manage` | `write_file` |
132
134
  | `pre_tool_call` | `read_file` | `read_file` |
133
- | `pre_tool_call` | `web_search`, `web_extract`, `browser_navigate` | `network_request` |
135
+ | `pre_tool_call` | `web_search`, `web_extract`, `browser_navigate`, `browser_open`, `web_open`, `open_url`, `visit_url`, `open` | `network_request` |
134
136
  | `post_tool_call` | Same tools | Audit-only |
135
137
 
136
138
  Hermes `pre_tool_call` supports allow/block only. If AgentGuard returns `ask`,
137
139
  the Hermes hook reports it as a block with a confirmation-oriented message.
140
+ When AgentGuard Cloud is connected through `agentguard connect`, the hook uses
141
+ the shared runtime protection path and syncs pre-tool decisions to Cloud.
138
142
 
139
143
  ### Procedure
140
144
 
@@ -149,12 +153,13 @@ the Hermes hook reports it as a block with a confirmation-oriented message.
149
153
  ```bash
150
154
  npm install -g @goplus/agentguard
151
155
  ```
152
- 3. Read `hermes-hooks.yaml`, replace `AGENTGUARD_SKILL_DIR` with the absolute
153
- skill directory, and show the resulting YAML to the user.
154
- 4. Ask for explicit confirmation before editing `~/.hermes/config.yaml`.
155
- 5. If confirmed, merge the `hooks:` entries into `~/.hermes/config.yaml`.
156
- Preserve existing hooks and config values. Do not overwrite unrelated user
157
- configuration.
156
+ 3. Prefer `agentguard init --agent hermes --force` to install and merge the
157
+ hook entries automatically.
158
+ 4. For manual setup, read `hermes-hooks.yaml`, replace
159
+ `AGENTGUARD_SKILL_DIR` with the absolute skill directory, and show the
160
+ resulting YAML to the user.
161
+ 5. Ask for explicit confirmation before manually editing
162
+ `~/.hermes/config.yaml`.
158
163
  6. Tell the user to restart Hermes or launch it with one of the first-use
159
164
  consent options:
160
165
  ```bash
@@ -162,6 +167,11 @@ the Hermes hook reports it as a block with a confirmation-oriented message.
162
167
  HERMES_ACCEPT_HOOKS=1 hermes chat
163
168
  ```
164
169
  They may also set `hooks_auto_accept: true` in `~/.hermes/config.yaml`.
170
+ 7. For troubleshooting, run Hermes hook checks with
171
+ `AGENTGUARD_HERMES_DEBUG=1` to print the runtime decision, risk level, and
172
+ policy source to stderr. Use `hermes hooks doctor` or
173
+ `hermes hooks test pre_tool_call --for-tool terminal` when available to
174
+ confirm Hermes is parsing the block response.
165
175
 
166
176
  ### Verification
167
177
 
@@ -188,7 +198,7 @@ printf '{"hook_event_name":"pre_tool_call","tool_name":"terminal","tool_input":{
188
198
  Expected output contains:
189
199
 
190
200
  ```json
191
- {"action":"block"}
201
+ {"action":"block","decision":"block","block":true}
192
202
  ```
193
203
 
194
204
  ## Subcommand: subscribe
@@ -215,13 +225,13 @@ agentguard subscribe --cron "0 * * * *" --force
215
225
 
216
226
  Without `--quiet`, `agentguard subscribe` pulls new threat-feed advisories and notifies the user to review them manually. With `--quiet`, it runs the full automated flow: pull new advisories, self-check local skills, report local matches back to Cloud, and notify only when local matches are found.
217
227
 
218
- When `--cron <expr>` is used, the CLI first runs the subscribe flow once, then installs a recurring job using a standard five-field crontab expression such as `"0 * * * *"`. `--cron-target auto` is the default and uses the agent host saved by `agentguard init --agent`: `openclaw` uses the native `openclaw cron add` command and falls back to the OpenClaw Gateway at `127.0.0.1:18789`, `qclaw` uses the QClaw Gateway at `127.0.0.1:28789`, `hermes` uses native `hermes cron create` with a no-agent script under `~/.hermes/scripts/`, while `claude-code` and `codex` install a user crontab entry. If no agent host is saved, auto asks the user to run `agentguard init --agent <claude-code|codex|openclaw|hermes|qclaw>` first or pass `--cron-target openclaw`, `--cron-target qclaw`, `--cron-target hermes`, or `--cron-target system` explicitly. Pass `--cron-name <name>` to choose the job name. If a job with the same name already exists, the CLI leaves it untouched unless `--force` is passed.
228
+ When `--cron <expr>` is used, the CLI first runs the subscribe flow once, then installs a recurring job using a standard five-field crontab expression such as `"0 * * * *"`. `--cron-target auto` is the default and uses the agent host saved by `agentguard init --agent`: `openclaw` uses the native `openclaw cron add` command and falls back to the OpenClaw Gateway at `127.0.0.1:18789`, `qclaw` uses the QClaw Gateway at `127.0.0.1:28789`, `hermes` uses native `hermes cron create` with a no-agent script under `~/.hermes/scripts/`, while `claude-code` and `codex` install a user crontab entry. OpenClaw/QClaw cron jobs use host `announce` delivery to the last chat route and run internal `--cron-notify-run`, which prints either the exact notification body or `NO_REPLY`; this keeps no-op cron ticks silent without embedding chat IDs in the job. If no agent host is saved, auto asks the user to run `agentguard init --agent <claude-code|codex|openclaw|hermes|qclaw>` first or pass `--cron-target openclaw`, `--cron-target qclaw`, `--cron-target hermes`, or `--cron-target system` explicitly. Pass `--cron-name <name>` to choose the job name. If a job with the same name already exists, the CLI leaves it untouched unless `--force` is passed.
219
229
 
220
230
  System cron writes output to `~/.agentguard/feed-cron.log`; it does not send OpenClaw agent-channel notifications.
221
231
 
222
232
  `agentguard subscribe --json` always includes a stable `cron` object with `requested`, `installed`, and optional `result` fields. If cron installation fails, the command exits non-zero instead of printing a misleading success summary.
223
233
 
224
- `--since <iso>` overrides the persisted feed cursor for one run. `--no-report` skips uploading local matches back to Cloud in quiet mode. `--cron-run` is internal and should only be used by the OpenClaw cron prompt unless the user explicitly asks to reproduce cron behavior.
234
+ `--since <iso>` overrides the persisted feed cursor for one run. `--no-report` skips uploading local matches back to Cloud in quiet mode. `--cron-run` and `--cron-notify-run` are internal and should only be used by installed cron jobs unless the user explicitly asks to reproduce cron behavior.
225
235
 
226
236
  ---
227
237
 
@@ -231,6 +241,33 @@ System cron writes output to `~/.agentguard/feed-cron.log`; it does not send Ope
231
241
 
232
242
  Scan the target path for security risks using all detection rules.
233
243
 
244
+ **Argument parsing**: Extract from `$ARGUMENTS`:
245
+ - The scan target path (first positional argument, or value after `scan`)
246
+ - `--format <fmt>` flag: supported values are `sarif` (SARIF 2.1.0 JSON) and `text` (default markdown)
247
+ - `--output <file>` flag: write output to this file instead of stdout
248
+
249
+ If `--format sarif` is present, follow the **SARIF Output Flow** at the end of this section instead of the standard Output Format.
250
+
251
+ ### Suppression Rules (read first)
252
+
253
+ Before running any detection, check for a suppression config file in the scan target root:
254
+
255
+ 1. Use the Read tool to read `<scan_target>/.agentguard-suppress.yaml`. If the file does not exist (Read returns an error or empty), skip suppression — no findings will be filtered.
256
+ 2. Parse the `suppress:` list. Each entry has:
257
+ - `rule` (required): rule ID to suppress (e.g. `PRIVATE_KEY_PATTERN`)
258
+ - `paths` (optional): list of glob patterns matched against the finding's file path (relative to scan root). `*` matches within one directory level; `**` matches across directories.
259
+ - `domains` (optional): list of substring/wildcard patterns matched against the finding's evidence text. `*` acts as a wildcard prefix or suffix.
260
+ - `reason` (required): explanation shown in the suppression summary.
261
+ 3. Keep this suppression list in memory — you will apply it after all detection rules have run.
262
+
263
+ **A finding is suppressed when ALL of the following are true:**
264
+ - Its `rule_id` exactly matches the entry's `rule` field.
265
+ - If the entry has `paths`: the finding's file path matches at least one glob pattern.
266
+ - If the entry has `domains`: the finding's evidence text contains at least one domain pattern match.
267
+ - If neither `paths` nor `domains` are specified: the finding is suppressed regardless of file or evidence.
268
+
269
+ Suppressed findings are **excluded from the findings table and risk level calculation**. At the end of the report, add a note: `> N finding(s) suppressed via .agentguard-suppress.yaml — run with details to review.`
270
+
234
271
  ### File Discovery
235
272
 
236
273
  Use Glob to find all scannable files at the given path. Include: `*.js`, `*.ts`, `*.jsx`, `*.tsx`, `*.mjs`, `*.cjs`, `*.py`, `*.json`, `*.yaml`, `*.yml`, `*.toml`, `*.sol`, `*.sh`, `*.bash`, `*.md`
@@ -252,8 +289,8 @@ For each rule, use Grep to search the relevant file types. Record every match wi
252
289
  | 4 | READ_ENV_SECRETS | MEDIUM | js,ts,mjs,py | Environment variable access |
253
290
  | 5 | READ_SSH_KEYS | CRITICAL | all | SSH key file access |
254
291
  | 6 | READ_KEYCHAIN | CRITICAL | all | System keychain / browser profiles |
255
- | 7 | PRIVATE_KEY_PATTERN | CRITICAL | all | Hardcoded private keys |
256
- | 8 | MNEMONIC_PATTERN | CRITICAL | all | Hardcoded mnemonic phrases |
292
+ | 7 | PRIVATE_KEY_PATTERN | CRITICAL* | all | Hardcoded private keys |
293
+ | 8 | MNEMONIC_PATTERN | CRITICAL* | all | Hardcoded mnemonic phrases |
257
294
  | 9 | WALLET_DRAINING | CRITICAL | js,ts,sol | Approve + transferFrom patterns |
258
295
  | 10 | UNLIMITED_APPROVAL | HIGH | js,ts,sol | Unlimited token approvals |
259
296
  | 11 | DANGEROUS_SELFDESTRUCT | HIGH | sol | selfdestruct in contracts |
@@ -271,6 +308,19 @@ For each rule, use Grep to search the relevant file types. Record every match wi
271
308
  | 23 | SUSPICIOUS_IP | MEDIUM | all | Hardcoded public IPv4 addresses |
272
309
  | 24 | SOCIAL_ENGINEERING | HIGH | md | Pressure language + execution instructions |
273
310
 
311
+ ### Git Context Check (Rules 7 & 8 only)
312
+
313
+ Rules marked **CRITICAL\*** start at CRITICAL but must be downgraded based on git context **before** being added to the findings list. For every file that matched Rule 7 (PRIVATE_KEY_PATTERN) or Rule 8 (MNEMONIC_PATTERN), run the following checks in order and assign the final severity:
314
+
315
+ 1. **Not in a git repo** — if `git -C <file_dir> rev-parse --git-dir 2>/dev/null` returns nothing → keep **CRITICAL**. Stop.
316
+ 2. **Ever committed** — run `git -C <file_dir> log --all --oneline -- <file_path>`. If output is non-empty → keep **CRITICAL**. Stop.
317
+ 3. **Not gitignored** — run `git -C <file_dir> check-ignore -q <file_path>`. If exit code is non-zero (file is NOT ignored) → downgrade to **HIGH**. Stop.
318
+ 4. **Gitignored** — exit code 0 → downgrade to **MEDIUM**.
319
+
320
+ Record the git context result (`committed` / `not-ignored` / `gitignored` / `no-git-repo`) in the finding's Evidence column alongside the matched content.
321
+
322
+ **Important**: these checks require `git` to be available. If `git` is not in PATH, skip the check and keep **CRITICAL**.
323
+
274
324
  ### Risk Level Calculation
275
325
 
276
326
  - Any **CRITICAL** finding -> Overall **CRITICAL**
@@ -286,7 +336,7 @@ For each rule, use Grep to search the relevant file types. Record every match wi
286
336
  **Target**: <scanned path>
287
337
  **Risk Level**: CRITICAL | HIGH | MEDIUM | LOW
288
338
  **Files Scanned**: <count>
289
- **Total Findings**: <count>
339
+ **Total Findings**: <count of non-suppressed findings>
290
340
 
291
341
  ### Findings
292
342
 
@@ -296,7 +346,10 @@ For each rule, use Grep to search the relevant file types. Record every match wi
296
346
 
297
347
  ### Summary
298
348
  <Human-readable summary of key risks, impact, and recommendations>
349
+
350
+ > N finding(s) suppressed via .agentguard-suppress.yaml
299
351
  ```
352
+ (Omit the suppression note line if no suppression file was found or no findings were suppressed.)
300
353
 
301
354
  ### Post-Scan Trust Registration
302
355
 
@@ -319,15 +372,53 @@ After outputting the scan report, if the scanned target appears to be a skill (c
319
372
  - `id`: the directory name of the scanned path
320
373
  - `source`: the absolute path to the scanned directory
321
374
  - `version`: read the `version` field from `package.json` in the scanned directory using the Read tool (if present), otherwise use `unknown`
322
- - `hash`: compute by running AgentGuard's own script: `node scripts/trust-cli.ts hash --path <scanned_path>` and extracting the `hash` field from the JSON output
375
+ - `hash`: compute by running AgentGuard's own script: `node scripts/trust-cli.js hash --path <scanned_path>` and extracting the `hash` field from the JSON output
323
376
  3. Show the user the full registration command and ask for confirmation before executing:
324
377
  ```
325
- node scripts/trust-cli.ts attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> --preset <preset> --reviewed-by agentguard-scan --notes "Auto-registered after scan. Risk level: <risk_level>." --force
378
+ node scripts/trust-cli.js attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> --preset <preset> --reviewed-by agentguard-scan --notes "Auto-registered after scan. Risk level: <risk_level>." --force
326
379
  ```
327
380
  4. Only execute after user approval. Show the registration result.
328
381
 
329
382
  If scripts are not available (e.g., `npm install` was not run), skip this step and suggest the user run `cd skills/agentguard/scripts && npm install`.
330
383
 
384
+ ### SARIF Output Flow (when `--format sarif` is present)
385
+
386
+ **Run Steps 1–3 (File Discovery, Detection Rules, Risk Level Calculation) exactly as above.** Then, instead of the standard markdown Output Format, do the following:
387
+
388
+ **Step A — Assemble findings as structured JSON** and write to `/tmp/agentguard-scan-findings.json`:
389
+
390
+ ```json
391
+ {
392
+ "target": "<scanned path>",
393
+ "scanned_at": "<ISO 8601 timestamp>",
394
+ "files_scanned": <number>,
395
+ "risk_level": "<CRITICAL|HIGH|MEDIUM|LOW>",
396
+ "findings": [
397
+ {
398
+ "rule_id": "<RULE_ID>",
399
+ "severity": "<CRITICAL|HIGH|MEDIUM|LOW>",
400
+ "file": "<relative/path/to/file.ext>",
401
+ "line": <line number>,
402
+ "evidence": "<matched content snippet>"
403
+ }
404
+ ]
405
+ }
406
+ ```
407
+
408
+ Use relative paths for `file` (relative to the scan target root). If no findings, use `"findings": []`.
409
+
410
+ **Step B — Run the SARIF converter** (cd into the skill directory first):
411
+
412
+ ```bash
413
+ cd <skill_directory> && node scripts/scan-to-sarif.js --file /tmp/agentguard-scan-findings.json
414
+ ```
415
+
416
+ **Step C — Handle output**:
417
+ - If `--output <file>` was specified: write the SARIF JSON to that file using the Write tool, then tell the user the file path.
418
+ - Otherwise: print the SARIF JSON to stdout (the user will redirect it, e.g. `> findings.sarif`).
419
+
420
+ **Do NOT** output the standard markdown report when `--format sarif` is active. Skip the Post-Scan Trust Registration offer.
421
+
331
422
  ---
332
423
 
333
424
  ## Subcommand: action
@@ -367,27 +458,27 @@ Parse the user's action description and apply the appropriate detector:
367
458
 
368
459
  ### Web3 Enhanced Detection
369
460
 
370
- When the action involves **web3_tx** or **web3_sign**, use AgentGuard's bundled `action-cli.ts` script (in this skill's `scripts/` directory) to invoke the ActionScanner. This script integrates the trust registry and optionally the GoPlus API (requires `GOPLUS_API_KEY` and `GOPLUS_API_SECRET` environment variables, if available):
461
+ When the action involves **web3_tx** or **web3_sign**, use AgentGuard's bundled `action-cli.js` script (in this skill's `scripts/` directory) to invoke the ActionScanner. This script integrates the trust registry and optionally the GoPlus API (requires `GOPLUS_API_KEY` and `GOPLUS_API_SECRET` environment variables, if available):
371
462
 
372
463
  For web3_tx:
373
464
  ```
374
- node scripts/action-cli.ts decide --type web3_tx --chain-id <id> --from <addr> --to <addr> --value <wei> [--data <calldata>] [--origin <url>] [--user-present]
465
+ node scripts/action-cli.js decide --type web3_tx --chain-id <id> --from <addr> --to <addr> --value <wei> [--data <calldata>] [--origin <url>] [--user-present]
375
466
  ```
376
467
 
377
468
  For web3_sign:
378
469
  ```
379
- node scripts/action-cli.ts decide --type web3_sign --chain-id <id> --signer <addr> [--message <msg>] [--typed-data <json>] [--origin <url>] [--user-present]
470
+ node scripts/action-cli.js decide --type web3_sign --chain-id <id> --signer <addr> [--message <msg>] [--typed-data <json>] [--origin <url>] [--user-present]
380
471
  ```
381
472
 
382
473
  For standalone transaction simulation:
383
474
  ```
384
- node scripts/action-cli.ts simulate --chain-id <id> --from <addr> --to <addr> --value <wei> [--data <calldata>] [--origin <url>]
475
+ node scripts/action-cli.js simulate --chain-id <id> --from <addr> --to <addr> --value <wei> [--data <calldata>] [--origin <url>]
385
476
  ```
386
477
 
387
478
  The `decide` command also works for non-Web3 actions (exec_command, network_request, etc.) and automatically resolves the skill's trust level and capabilities from the registry:
388
479
 
389
480
  ```
390
- node scripts/action-cli.ts decide --type exec_command --command "<cmd>" [--skill-source <source>] [--skill-id <id>]
481
+ node scripts/action-cli.js decide --type exec_command --command "<cmd>" [--skill-source <source>] [--skill-id <id>]
391
482
  ```
392
483
 
393
484
  Parse the JSON output and incorporate findings into your evaluation:
@@ -419,29 +510,25 @@ Always combine script results with the policy-based checks (webhook domains, sec
419
510
 
420
511
  ## Subcommand: patrol
421
512
 
422
- **OpenClaw-specific daily security patrol.** Runs 8 automated checks that leverage AgentGuard's scan engine, trust registry, and audit log to assess the security posture of an OpenClaw deployment.
513
+ **Daily security patrol.** Runs 8 automated checks that leverage AgentGuard's scan engine, trust registry, and audit log to assess the security posture of your agent deployment. Works on OpenClaw and standard cron environments.
423
514
 
424
515
  For detailed check definitions, commands, and thresholds, see [patrol-checks.md](patrol-checks.md).
425
516
 
426
517
  ### Sub-subcommands
427
518
 
428
519
  - **`patrol`** or **`patrol run`** — Execute all 8 checks and output a patrol report
429
- - **`patrol setup`** — Configure as an OpenClaw daily cron job
520
+ - **`patrol setup`** — Configure as a daily cron job (OpenClaw or system crontab)
430
521
  - **`patrol status`** — Show last patrol results and cron schedule
431
522
 
432
- ### Pre-flight: OpenClaw Detection
523
+ ### Platform Detection
433
524
 
434
- Before running any checks, verify the OpenClaw environment:
525
+ Before running `patrol setup` or `patrol status`, detect the available scheduling platform:
435
526
 
436
- 1. Check for `$OPENCLAW_STATE_DIR` env var, fall back to `~/.openclaw/`
437
- 2. Verify the directory exists and contains `openclaw.json`
438
- 3. Check if `openclaw` CLI is available in PATH
527
+ 1. **OpenClaw**: Check for `$OPENCLAW_STATE_DIR` env var (fall back to `~/.openclaw/`), verify the directory exists and contains `openclaw.json`, and check if `openclaw` CLI is in PATH. If all three pass → use OpenClaw path.
528
+ 2. **System crontab**: Check if `crontab` command is available in PATH → use crontab path.
529
+ 3. **Neither available**: Inform the user and output the manual cron entry for them to add themselves.
439
530
 
440
- If OpenClaw is not detected, output:
441
- ```
442
- This command requires an OpenClaw environment. Detected: <what was found/missing>
443
- For non-OpenClaw environments, use /agentguard scan and /agentguard report instead.
444
- ```
531
+ For `patrol run`, no scheduling platform is needed run checks on any platform.
445
532
 
446
533
  Set `$OC` to the resolved OpenClaw state directory for all subsequent checks.
447
534
 
@@ -453,8 +540,8 @@ Detect tampered or unregistered skill packages by comparing file hashes against
453
540
 
454
541
  **Steps**:
455
542
  1. Discover skill directories under `$OC/skills/` (look for dirs containing `SKILL.md`)
456
- 2. For each skill, compute hash: `node scripts/trust-cli.ts hash --path <skill_dir>`
457
- 3. Look up the attested hash: `node scripts/trust-cli.ts lookup --source <skill_dir>`
543
+ 2. For each skill, compute hash: `node scripts/trust-cli.js hash --path <skill_dir>`
544
+ 3. Look up the attested hash: `node scripts/trust-cli.js lookup --source <skill_dir>`
458
545
  4. If hash differs from attested → **INTEGRITY_DRIFT** (HIGH)
459
546
  5. If skill has no trust record → **UNREGISTERED_SKILL** (MEDIUM)
460
547
  6. For drifted skills, run the scan rules against the changed files to detect new threats
@@ -498,13 +585,13 @@ Audit all cron jobs for download-and-execute patterns.
498
585
  Detect suspicious file modifications in the last 24 hours.
499
586
 
500
587
  **Steps**:
501
- 1. Find recently modified files: `find $OC/ ~/.ssh/ ~/.gnupg/ /etc/cron.d/ -type f -mtime -1`
588
+ 1. Find recently modified files: use Glob with patterns `$OC/**/*`, `~/.ssh/**/*`, `~/.gnupg/**/*` and filter results by mtime within 24h using `stat -f '%m %N' <file>` (macOS) or `stat -c '%Y %n' <file>` (Linux) — do NOT use the `find` binary as it may be unavailable in hardened environments
502
589
  2. For modified files with scannable extensions (.js/.ts/.py/.sh/.md/.json), run the full scan rule set
503
590
  3. Check permissions on critical files:
504
591
  - `$OC/openclaw.json` → should be 600
505
592
  - `$OC/devices/paired.json` → should be 600
506
593
  - `~/.ssh/authorized_keys` → should be 600
507
- 4. Detect new executable files in workspace: `find $OC/workspace/ -type f -perm +111 -mtime -1`
594
+ 4. Detect new executable files in workspace: use Glob `$OC/workspace/**/*` and check each file's executable bit with `stat` — do NOT use `find` with `-perm`
508
595
 
509
596
  #### [6] Audit Log Analysis (24h)
510
597
 
@@ -535,7 +622,7 @@ Verify security configuration is production-appropriate.
535
622
  Check for expired, stale, or over-privileged trust records.
536
623
 
537
624
  **Steps**:
538
- 1. List all records: `node scripts/trust-cli.ts list`
625
+ 1. List all records: `node scripts/trust-cli.js list`
539
626
  2. Flag:
540
627
  - Expired attestations (`expires_at` in the past)
541
628
  - Trusted skills not re-scanned in 30+ days
@@ -588,17 +675,20 @@ After outputting the report, append a summary entry to `~/.agentguard/audit.json
588
675
 
589
676
  ### patrol setup
590
677
 
591
- Configure the patrol as an OpenClaw daily cron job.
678
+ Configure the patrol as a daily cron job. Detects the available platform and uses the appropriate method.
592
679
 
593
680
  **Steps**:
594
681
 
595
- 1. Verify OpenClaw environment (same pre-flight as `patrol run`)
682
+ 1. Run platform detection (see above).
596
683
  2. Ask the user for:
597
- - **Timezone** (default: UTC). Examples: `Asia/Shanghai`, `America/New_York`, `Europe/London`
598
684
  - **Schedule** (default: `0 3 * * *` — daily at 03:00)
599
- - **Notification channel** (optional): `telegram`, `discord`, `signal`
685
+ - **Timezone** (default: UTC). Examples: `Asia/Shanghai`, `America/New_York`, `Europe/London`
686
+ - **Notification channel** (optional, OpenClaw only): `telegram`, `discord`, `signal`
600
687
  - **Chat ID / webhook** (required if channel is set)
601
- 3. Generate the cron registration command:
688
+
689
+ #### Path A — OpenClaw available
690
+
691
+ Generate and show the OpenClaw cron registration command:
602
692
 
603
693
  ```bash
604
694
  openclaw cron add \
@@ -616,11 +706,41 @@ openclaw cron add \
616
706
  --to <chat-id>
617
707
  ```
618
708
 
619
- 4. **Show the exact command to the user and wait for explicit confirmation** before executing
620
- 5. After execution, verify with `openclaw cron list`
621
- 6. Output confirmation with the cron schedule
709
+ **Show the exact command and wait for explicit user confirmation before executing.**
710
+ After execution, verify with `openclaw cron list`.
711
+
712
+ > **Note**: `--timeout-seconds 300` is required because isolated sessions need cold-start time.
713
+
714
+ #### Path B — System crontab available (OpenClaw not available)
715
+
716
+ Resolve the absolute path to this skill's directory (parent of this SKILL.md file) as `<SKILL_DIR>`.
717
+
718
+ Validate before generating the entry:
719
+ - `<schedule>` must be a standard five-field cron expression. Reject values that contain newlines.
720
+ - `<SKILL_DIR>` must be an absolute path. Reject paths containing single quotes, double quotes, null bytes, or newlines.
721
+ - Do not include notification channel, chat ID, or webhook values in the system crontab entry. System cron writes only to the local patrol log.
722
+
723
+ Generate the crontab entry using a single-quoted skill directory. If `<SKILL_DIR>` contains spaces, keep it inside the quotes exactly as shown:
724
+ ```
725
+ <schedule> cd '<SKILL_DIR>' && AGENTGUARD_AUTO_SCAN=1 node scripts/auto-scan.js >> "$HOME/.agentguard/patrol.log" 2>&1
726
+ ```
727
+
728
+ **Show the exact entry and wait for explicit user confirmation before writing.**
729
+
730
+ After confirmation, add the entry to the user's crontab:
731
+ ```bash
732
+ (crontab -l 2>/dev/null; printf '%s\n' "<schedule> cd '<SKILL_DIR>' && AGENTGUARD_AUTO_SCAN=1 node scripts/auto-scan.js >> \"\$HOME/.agentguard/patrol.log\" 2>&1") | crontab -
733
+ ```
734
+
735
+ Verify with `crontab -l | grep agentguard`.
622
736
 
623
- > **Note**: `--timeout-seconds 300` is required because isolated sessions need cold-start time. The default 120s is not enough.
737
+ #### Path C Neither available
738
+
739
+ Output the crontab entry for the user to add manually:
740
+ ```
741
+ <schedule> cd '<SKILL_DIR>' && AGENTGUARD_AUTO_SCAN=1 node scripts/auto-scan.js >> "$HOME/.agentguard/patrol.log" 2>&1
742
+ ```
743
+ Explain that neither `openclaw` nor `crontab` was found in PATH, so the entry must be added manually.
624
744
 
625
745
  ### patrol status
626
746
 
@@ -628,11 +748,10 @@ Show the current patrol state.
628
748
 
629
749
  **Steps**:
630
750
 
631
- 1. Read `~/.agentguard/audit.jsonl`, find the most recent `event: "patrol"` entry
632
- 2. If found, display: timestamp, overall status, finding counts
633
- 3. Run `openclaw cron list` and look for `agentguard-patrol` job
634
- 4. If cron is configured, show: schedule, timezone, last run time, next run time
635
- 5. If cron is not configured, suggest: `/agentguard patrol setup`
751
+ 1. Read `~/.agentguard/audit.jsonl`, find the most recent `event: "patrol"` or `event: "auto_scan"` entry. If found, display: timestamp, overall status, finding counts.
752
+ 2. **OpenClaw available**: run `openclaw cron list` and look for `agentguard-patrol`. Show schedule, timezone, last/next run time if found.
753
+ 3. **System crontab available**: run `crontab -l 2>/dev/null | grep agentguard`. Show the matching entry if found.
754
+ 4. If no cron is configured on any platform, suggest: `/agentguard patrol setup`.
636
755
 
637
756
  ---
638
757
 
@@ -673,23 +792,127 @@ web3.tx_policy: 'allow' | 'confirm_high_risk' | 'deny'
673
792
 
674
793
  ### Operations
675
794
 
676
- **lookup** — `node scripts/trust-cli.ts lookup --source <source> --version <version>`
795
+ **lookup** — `node scripts/trust-cli.js lookup --source <source> --version <version>`
677
796
  Query the registry for a skill's trust record.
678
797
 
679
- **attest** — `node scripts/trust-cli.ts attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> --preset <preset> --reviewed-by <name>`
798
+ **attest** — `node scripts/trust-cli.js attest --id <id> --source <source> --version <version> --hash <hash> --trust-level <level> --preset <preset> --reviewed-by <name>`
680
799
  Create or update a trust record. Use `--preset` for common capability models or provide `--capabilities <json>` for custom.
681
800
 
682
- **revoke** — `node scripts/trust-cli.ts revoke --source <source> --reason <reason>`
801
+ **revoke** — `node scripts/trust-cli.js revoke --source <source> --reason <reason>`
683
802
  Revoke trust for a skill. Supports `--source-pattern` for wildcards.
684
803
 
685
- **list** — `node scripts/trust-cli.ts list [--trust-level <level>] [--status <status>]`
804
+ **list** — `node scripts/trust-cli.js list [--trust-level <level>] [--status <status>]`
686
805
  List all trust records with optional filters.
687
806
 
807
+ **seed** — `agentguard trust seed [--auto-attest-low-risk] [--auto-attest-medium-risk] [--dry-run]`
808
+ Batch-scan all installed skills and auto-attest those meeting the risk threshold. Designed for initial baseline setup when many skills are already installed.
809
+
810
+ Flags:
811
+ - `--auto-attest-low-risk` (default when `seed` is invoked): attest LOW-risk skills as `trusted` with `read_only` preset.
812
+ - `--auto-attest-medium-risk`: also attest MEDIUM-risk skills as `restricted` with `none` preset.
813
+ - `--dry-run`: preview only — show the plan table without executing any attest commands.
814
+
815
+ **HIGH and CRITICAL risk skills are never auto-attested** regardless of flags. They must be reviewed and attested manually.
816
+
817
+ #### seed Flow
818
+
819
+ **Step 1 — Discover skills**
820
+
821
+ Glob all of the following paths for `*/SKILL.md` (same as checkup):
822
+ - `~/.claude/skills/*/SKILL.md`
823
+ - `~/.openclaw/skills/*/SKILL.md`
824
+ - `~/.openclaw/workspace/skills/*/SKILL.md`
825
+ - `~/.qclaw/skills/*/SKILL.md`
826
+ - `~/.qclaw/workspace/skills/*/SKILL.md`
827
+
828
+ Skip `agentguard` itself. Collect the parent directory of each found `SKILL.md` as the skill path.
829
+
830
+ **Step 2 — Filter unregistered skills**
831
+
832
+ For each discovered skill, run:
833
+ ```
834
+ node scripts/trust-cli.js lookup --source <skill_path>
835
+ ```
836
+ If the lookup returns a record with `status: active`, the skill is already registered — skip it and note "already registered" in the summary. Only proceed with skills that have no active trust record.
837
+
838
+ **Step 3 — Scan unregistered skills**
839
+
840
+ For each unregistered skill, run the full scan (24 detection rules, same as `/agentguard scan <skill_path>`). Record: skill name, skill path, risk level (LOW/MEDIUM/HIGH/CRITICAL), finding count.
841
+
842
+ **Step 4 — Build preview table**
843
+
844
+ Output a plan table before taking any action:
845
+
846
+ ```
847
+ ## AgentGuard Trust Seed — Plan
848
+
849
+ Scanned <N> unregistered skills. Proposed actions:
850
+
851
+ | Skill | Path | Risk | Findings | Proposed Action |
852
+ |-------|------|------|----------|-----------------|
853
+ | foo | ~/.claude/skills/foo | LOW | 0 | ✅ attest trusted/read_only |
854
+ | bar | ~/.claude/skills/bar | MEDIUM | 2 | ⚠️ attest restricted/none (requires --auto-attest-medium-risk) |
855
+ | baz | ~/.claude/skills/baz | HIGH | 5 | 🚫 SKIP — manual review required |
856
+ | qux | ~/.claude/skills/qux | CRITICAL | 8 | 🚫 SKIP — manual review required |
857
+
858
+ Already registered (skipped): <M> skills
859
+ Will attest: <K> skills
860
+ Will skip (HIGH/CRITICAL): <J> skills
861
+ ```
862
+
863
+ If `--dry-run` is present: output this table and stop. Add: `Dry run complete — no changes made. Remove --dry-run to execute.`
864
+
865
+ **Step 5 — User confirmation (REQUIRED)**
866
+
867
+ After showing the plan table, **always ask for explicit user confirmation** before executing any attest commands:
868
+
869
+ > "Ready to attest <K> skill(s). Confirm? (yes/no)"
870
+
871
+ Do NOT proceed without a clear affirmative response. If the user declines, stop and suggest `--dry-run` for future previews.
872
+
873
+ **Step 6 — Batch attest**
874
+
875
+ For each skill approved for attestation, compute its hash and run attest:
876
+
877
+ ```bash
878
+ # Compute hash
879
+ node scripts/trust-cli.js hash --path <skill_path>
880
+
881
+ # Attest
882
+ node scripts/trust-cli.js attest \
883
+ --id <skill_dir_name> \
884
+ --source <skill_path> \
885
+ --version <version_from_package.json_or_unknown> \
886
+ --hash <computed_hash> \
887
+ --trust-level <trusted|restricted> \
888
+ --preset <read_only|none> \
889
+ --reviewed-by agentguard-seed \
890
+ --notes "Auto-attested by trust seed. Scan risk: <risk_level>. Findings: <count>." \
891
+ --force
892
+ ```
893
+
894
+ Run these sequentially (not in parallel) to avoid registry write conflicts.
895
+
896
+ **Step 7 — Result summary**
897
+
898
+ ```
899
+ ## Trust Seed Complete
900
+
901
+ ✅ Attested: <N> skills
902
+ ⚠️ Skipped (already registered): <M> skills
903
+ 🚫 Skipped (HIGH/CRITICAL risk — manual review required): <J> skills
904
+ ❌ Failed: <K> skills (list errors)
905
+
906
+ Skills requiring manual review:
907
+ - <skill_name> (<path>) — Risk: HIGH/CRITICAL, <N> findings
908
+ Run: /agentguard scan <path> then /agentguard trust attest ...
909
+ ```
910
+
688
911
  ### Script Execution
689
912
 
690
913
  If the agentguard package is installed, execute trust operations via AgentGuard's own bundled script:
691
914
  ```
692
- node scripts/trust-cli.ts <subcommand> [args]
915
+ node scripts/trust-cli.js <subcommand> [args]
693
916
  ```
694
917
 
695
918
  For operations that modify the trust registry (`attest`, `revoke`), always show the user the exact command and ask for explicit confirmation before executing.
@@ -786,7 +1009,15 @@ If the log file doesn't exist, inform the user that no security events have been
786
1009
 
787
1010
  ## Subcommand: checkup
788
1011
 
789
- Run a comprehensive agent health checkup across 6 security dimensions. Generates a visual HTML report with a lobster mascot and opens it in the browser. The lobster's appearance reflects the agent's health: muscular bodybuilder (score 90+), healthy with shield (70–89), tired with coffee (50–69), or sick with bandages (0–49).
1012
+ Run a comprehensive agent health checkup across 5 security dimensions. Generates a visual HTML report with a lobster mascot and opens it in the browser. The lobster's appearance reflects the agent's health: muscular bodybuilder (score 90+), healthy with shield (70–89), tired with coffee (50–69), or sick with bandages (0–49).
1013
+
1014
+ **Scoring is handled by `checkup-score.js` — you MUST NOT calculate scores yourself. Your role is to collect raw facts, assemble them into structured JSON, and pass to the script.**
1015
+
1016
+ **Argument parsing**: Extract from `$ARGUMENTS`:
1017
+ - `--format json` flag: skip HTML generation and write the checkup JSON to a file instead
1018
+ - `--output <file>` flag: path for the JSON output file (required when `--format json` is used; defaults to `/tmp/agentguard-checkup-data.json` if omitted)
1019
+
1020
+ If `--format json` is present, follow the modified flow noted in Step 4 below.
790
1021
 
791
1022
  Plain `checkup` must always run this comprehensive workflow, even if the user phrases it as `agentguard checkup`. Do not answer that an advisory ID is required. Advisory IDs are optional and only switch to the targeted threat-feed self-check mode described below.
792
1023
 
@@ -803,6 +1034,8 @@ That CLI path fetches the current Cloud advisory feed and checks local skills ag
803
1034
 
804
1035
  **IMPORTANT: You MUST run ALL 7 checks below — not just the skill scan. The checkup covers 5 security dimensions, not just code scanning. Do NOT skip checks 2–7.**
805
1036
 
1037
+ **EVIDENCE RULE: Every finding you report MUST be backed by actual tool output collected in this step. You MUST quote the exact command output (or "no output" if the command returned nothing) in the finding's evidence field. Findings without concrete evidence from tool execution are FORBIDDEN — do not infer, assume, or fabricate results.**
1038
+
806
1039
  Run these checks in parallel where possible. These are **universal agent security checks** — they apply to any Claude Code or OpenClaw environment, regardless of whether AgentGuard is installed.
807
1040
 
808
1041
  1. **[REQUIRED] Discover & scan installed skills** (→ feeds Dimension 1: Code Safety): Glob ALL of the following paths for `*/SKILL.md`:
@@ -812,164 +1045,131 @@ Run these checks in parallel where possible. These are **universal agent securit
812
1045
  - `~/.qclaw/skills/*/SKILL.md`
813
1046
  - `~/.qclaw/workspace/skills/*/SKILL.md`
814
1047
 
815
- For **every** discovered skill, **run `/agentguard scan <skill_path>`** using the scan subcommand logic (24 detection rules). Do NOT skip any skill regardless of how many are found. Collect the scan results (risk level, findings count, risk tags) for each skill.
1048
+ For **every** discovered skill, **run `/agentguard scan <skill_path>`** using the scan subcommand logic (24 detection rules). Do NOT skip any skill regardless of how many are found. Record for each skill: name, risk_level, and exact findings list (rule, severity, file, line).
816
1049
  2. **[REQUIRED] Credential file permissions** (→ feeds Dimension 2: Credential Safety): Platform-aware check — behavior differs by OS:
817
- - **macOS/Linux**: Run `stat -f '%Lp' <path> 2>/dev/null || stat -c '%a' <path> 2>/dev/null` on `~/.ssh/`, `~/.gnupg/`, and if OpenClaw: on `$OC/openclaw.json`, `$OC/devices/paired.json`. **If the command returns empty output, the directory does not exist — treat as N/A (award full points), do NOT flag as a failure.**
818
- - **Windows**: `stat` is not available. Use `icacls <path>` to check ACLs instead. If the directory does not exist, treat as N/A (award full points). If it exists, check that the ACL grants access only to the current user (no `Everyone`, `Users`, or `Authenticated Users` with write/read access). Flag as FAIL only if the directory exists AND the ACL is overly permissive.
1050
+ - **macOS/Linux**: Run `stat -f '%Lp' <path> 2>/dev/null || stat -c '%a' <path> 2>/dev/null` on `~/.ssh/`, `~/.gnupg/`. **If the command returns empty output, the directory does not exist — record `exists: false`.**
1051
+ - **Windows**: `stat` is not available. Use `icacls <path>` to check ACLs instead. If directory doesn't exist, record `exists: false`. If it exists, record whether the ACL grants access to `Everyone`, `Users`, or `Authenticated Users`.
1052
+ - Also check OpenClaw config files if applicable (`$OC/openclaw.json`, `$OC/devices/paired.json`).
819
1053
  3. **[REQUIRED] Sensitive credential scan / DLP** (→ feeds Dimension 2: Credential Safety): Use Grep to scan **all** agent workspace directories for leaked secrets. This MUST cover the entire workspace root, not just the current agent's directory:
820
- - For OpenClaw / QClaw: scan `~/.openclaw/workspace/` and `~/.qclaw/workspace/` recursively — this includes **all** `workspace-agent-*/` subdirectories, not just the current agent's workspace
1054
+ - For OpenClaw / QClaw: scan `~/.openclaw/workspace/` and `~/.qclaw/workspace/` recursively
821
1055
  - For Claude Code: scan `~/.claude/` recursively
822
1056
  - For Hermes Agent: scan `~/.hermes/` recursively
823
1057
  - Patterns to detect:
824
1058
  - Private keys: `0x[a-fA-F0-9]{64}`, `-----BEGIN.*PRIVATE KEY-----`
825
1059
  - Mnemonics: sequences of 12+ BIP-39 words, `seed_phrase`, `mnemonic`
826
1060
  - API keys/tokens: `AKIA[0-9A-Z]{16}`, `gh[pousr]_[A-Za-z0-9_]{36}`, plaintext passwords
1061
+ - Record: `private_keys_found`, `mnemonics_found`, `api_keys_found` (boolean, with location if found).
827
1062
  - **Important**: Use the workspace *root* directory as the scan target (e.g. `~/.qclaw/workspace/`), not a specific agent subdirectory. All sibling `workspace-agent-*` directories must be included.
828
- 4. **[REQUIRED] Network exposure** (→ feeds Dimension 3: Network & System): Run `lsof -i -P -n 2>/dev/null | grep LISTEN` or `ss -tlnp 2>/dev/null` to check for dangerous open ports (Redis 6379, Docker API 2375, MySQL 3306, MongoDB 27017 on 0.0.0.0)
829
- 5. **[REQUIRED] Scheduled tasks audit** (→ feeds Dimension 3: Network & System): Check `crontab -l 2>/dev/null` for suspicious entries containing `curl|bash`, `wget|sh`, or accessing `~/.ssh/`
830
- 6. **[REQUIRED] Environment variable exposure** (→ feeds Dimension 3: Network & System): Run `env` and check for sensitive variable names (`PRIVATE_KEY`, `MNEMONIC`, `SECRET`, `PASSWORD`) — detect presence only, mask values
831
- 7. **[REQUIRED] Runtime protection check** (→ feeds Dimension 4: Runtime Protection): Check if security hooks exist in `~/.claude/settings.json`, `~/.openclaw/openclaw.json`, or `~/.hermes/config.yaml`, check for audit logs at `~/.agentguard/audit.jsonl`
832
-
833
- ### Step 2: Score Calculation
834
-
835
- **Additive scoring**: Each dimension starts at **0**. For each check that **passes**, add the listed points. Maximum is 100 per dimension. **Every failed check = 1 finding with severity and description.**
836
-
837
- #### Dimension 1: Skill & Code Safety (weight: 25%)
838
-
839
- Uses AgentGuard's 24-rule scan engine (`/agentguard scan`) to audit each installed skill. Start at base 100 and **deduct** for findings:
840
-
841
- - Base score: **100**
842
- - Each CRITICAL finding: **−15**
843
- - Each HIGH finding: **−8**
844
- - Each MEDIUM finding: **−3**
845
- - Floor at **0** (never negative)
1063
+ 4. **[REQUIRED] Network exposure** (→ feeds Dimension 3: Network & System): Run `lsof -i -P -n 2>/dev/null | grep LISTEN` or `ss -tlnp 2>/dev/null` to check for dangerous open ports (Redis 6379, Docker API 2375, MySQL 3306, MongoDB 27017 on 0.0.0.0). Record list of dangerous ports found (e.g. `["Redis on 0.0.0.0:6379"]`).
1064
+ 5. **[REQUIRED] Scheduled tasks audit** (→ feeds Dimension 3: Network & System): Check `crontab -l 2>/dev/null` for suspicious entries containing `curl|bash`, `wget|sh`, or accessing `~/.ssh/`. Record list of suspicious cron command strings found.
1065
+ 6. **[REQUIRED] Environment variable exposure** (→ feeds Dimension 3: Network & System): Run `env` and check for sensitive variable names (`PRIVATE_KEY`, `MNEMONIC`, `SECRET`, `PASSWORD`) — detect presence only, mask values. Record list of sensitive variable names found.
1066
+ 7. **[REQUIRED] Runtime protection check** (→ feeds Dimension 4: Runtime Protection): Check if security hooks exist in `~/.claude/settings.json`, `~/.openclaw/openclaw.json`, or `~/.hermes/config.yaml`. Check for audit logs at `~/.agentguard/audit.jsonl`. Check if installed skills have been previously scanned (audit log contains `scan` events). Record booleans: `hooks_installed`, `audit_log_exists`, `skills_ever_scanned`.
846
1067
 
847
- For each finding, add: `"<rule_id> in <skill>:<file>:<line>"` with its severity.
1068
+ ### Step 2: Assemble Raw Facts JSON
848
1069
 
849
- **False-positive suppression**: When the scanned skill is `agentguard` itself (skill path contains `agentguard`), suppress `READ_ENV_SECRETS` findings AgentGuard reads environment variables as part of its own configuration detection, which is expected behaviour and not a security risk. Do not deduct points or list these as findings in the report.
1070
+ After completing all 7 checks, assemble the raw facts into a structured JSON and write it to a temporary file (e.g. `/tmp/agentguard-raw-facts.json`):
850
1071
 
851
- If no skills installed: score = **70**, add finding: "No third-party skills installed — no code to audit" (LOW).
852
-
853
- #### Dimension 2: Credential & Secret Safety (weight: 25%)
854
-
855
- Checks for leaked credentials and permission hygiene. Start at **0**, add points for each check that **passes** (total possible = 100):
856
-
857
- | Check | Points if PASS | If FAIL → finding |
858
- |-------|---------------|-------------------|
859
- | `~/.ssh/` permissions are 700 or stricter | **+25** | "~/.ssh/ permissions too open (<actual>) — should be 700" (HIGH) |
860
- | `~/.gnupg/` permissions are 700 or stricter | **+15** | "~/.gnupg/ permissions too open (<actual>) — should be 700" (MEDIUM) |
861
-
862
- **Permission check rules (to avoid false positives):**
863
- - **Directory does not exist** (stat/icacls returns empty or "file not found"): Treat as N/A — award the points. A missing `~/.ssh/` or `~/.gnupg/` is not a security risk.
864
- - **Windows**: Use `icacls` instead of `stat`. Award full points if directory doesn't exist. Flag as FAIL only if directory exists AND ACL grants access to `Everyone`, `Users`, or `Authenticated Users`.
865
- - **macOS/Linux**: Flag as FAIL only when the directory exists AND stat returns a numeric value AND that value is greater than 700.
866
- | No private keys (hex 0x..64, PEM) found in skill code or workspace | **+25** | "Plaintext private key found in <location>" (CRITICAL) |
867
- | No mnemonic phrases found in skill code or workspace | **+20** | "Plaintext mnemonic found in <location>" (CRITICAL) |
868
- | No API keys/tokens (AWS AKIA.., GitHub gh*_) found in skill code | **+15** | "API key/token found in <location>" (HIGH) |
869
-
870
- #### Dimension 3: Network & System Exposure (weight: 20%)
871
-
872
- Checks for dangerous network exposure and system-level risks. Start at **0**, add points for each check that **passes** (total possible = 100):
873
-
874
- | Check | Points if PASS | If FAIL → finding |
875
- |-------|---------------|-------------------|
876
- | No high-risk ports exposed on 0.0.0.0 (Redis/Docker/MySQL/MongoDB) | **+35** | "Dangerous port exposed: <service> on 0.0.0.0:<port>" (HIGH) |
877
- | No suspicious cron jobs (curl\|bash, wget\|sh, accessing ~/.ssh/) | **+30** | "Suspicious cron job: <command>" (HIGH) |
878
- | No sensitive env vars with dangerous names (PRIVATE_KEY, MNEMONIC) | **+20** | "Sensitive env var exposed: <name>" (MEDIUM) |
879
- | OpenClaw config files have proper permissions (600) if applicable | **+15** | "OpenClaw config <file> permissions too open" (MEDIUM) |
880
-
881
- **Example**: If no dangerous ports (+35), no suspicious cron (+30), but env var `PRIVATE_KEY` found (+0), and not OpenClaw (+15 skip, give points) → score = 35 + 30 + 0 + 15 = **80**.
882
-
883
- #### Dimension 4: Runtime Protection (weight: 15%)
884
-
885
- Checks whether the agent has active security monitoring. Start at **0**, add points for each check that **passes** (total possible = 100):
886
-
887
- | Check | Points if PASS | If FAIL → finding |
888
- |-------|---------------|-------------------|
889
- | Security hooks/guards installed (AgentGuard, custom hooks, etc.) | **+40** | "No security hooks installed — actions are unmonitored" (HIGH) |
890
- | Security audit log exists with recent events | **+30** | "No security audit log — no threat history available" (MEDIUM) |
891
- | Skills have been security-scanned at least once | **+30** | "Installed skills have never been security-scanned" (MEDIUM) |
892
-
893
- #### Dimension 5: Web3 Safety (weight: 15% if applicable)
1072
+ ```json
1073
+ {
1074
+ "skills": [
1075
+ {
1076
+ "name": "<skill-name>",
1077
+ "risk_level": "<low|medium|high|critical>",
1078
+ "findings": [
1079
+ { "rule": "<RULE_ID>", "severity": "<CRITICAL|HIGH|MEDIUM|LOW>", "file": "<filename>", "line": <number> }
1080
+ ]
1081
+ }
1082
+ ],
1083
+ "credential_files": {
1084
+ "ssh_dir": { "exists": <bool>, "permissions": "<octal string, e.g. 700>" },
1085
+ "gnupg_dir": { "exists": <bool>, "permissions": "<octal string>" },
1086
+ "openclaw_config": { "exists": <bool>, "ok": <bool> }
1087
+ },
1088
+ "dlp": {
1089
+ "private_keys_found": <bool>,
1090
+ "mnemonics_found": <bool>,
1091
+ "api_keys_found": <bool>
1092
+ },
1093
+ "network": {
1094
+ "dangerous_ports": ["<description>"],
1095
+ "suspicious_crons": ["<command>"],
1096
+ "sensitive_env_vars": ["<VAR_NAME>"],
1097
+ "openclaw_config_ok": <bool|null>
1098
+ },
1099
+ "runtime": {
1100
+ "hooks_installed": <bool>,
1101
+ "audit_log_exists": <bool>,
1102
+ "skills_ever_scanned": <bool>
1103
+ },
1104
+ "web3": {
1105
+ "detected": <bool>,
1106
+ "wallet_draining_found": <bool>,
1107
+ "unlimited_approval_found": <bool>,
1108
+ "goplus_configured": <bool>
1109
+ }
1110
+ }
1111
+ ```
894
1112
 
895
- Only if Web3 usage is detected (env vars like `GOPLUS_API_KEY`, `CHAIN_ID`, `RPC_URL`, or web3-related skills installed). Otherwise `{ "score": null, "na": true }`. Start at **0**, add points for each check that **passes** (total possible = 100):
1113
+ **Web3 detection**: set `detected: true` if any of these are present: env vars `GOPLUS_API_KEY`, `CHAIN_ID`, or `RPC_URL`; or any skill with web3-related findings (WALLET_DRAINING, UNLIMITED_APPROVAL).
896
1114
 
897
- | Check | Points if PASS | If FAIL → finding |
898
- |-------|---------------|-------------------|
899
- | No wallet-draining patterns (approve+transferFrom) in skill code | **+40** | "Wallet-draining pattern detected in <skill>" (CRITICAL) |
900
- | No unlimited token approval patterns in skill code | **+30** | "Unlimited approval pattern detected in <skill>" (HIGH) |
901
- | Transaction security API configured (GoPlus or equivalent) | **+30** | "No transaction security API Web3 calls are unverified" (MEDIUM) |
1115
+ **Pre-Step-3 validation** verify all fields are populated before proceeding:
1116
+ - [ ] `skills` — from check 1
1117
+ - [ ] `credential_files` from check 2
1118
+ - [ ] `dlp` from check 3
1119
+ - [ ] `network`from checks 4, 5, 6
1120
+ - [ ] `runtime` — from check 7
1121
+ - [ ] `web3` — detected flag + fields
902
1122
 
903
- #### Composite Score Calculation
1123
+ **If any field is missing, go back and run the missing check. Do NOT proceed with incomplete data.**
904
1124
 
905
- Calculate the weighted average of all applicable dimensions:
1125
+ ### Step 3: Compute Scores with checkup-score.js
906
1126
 
907
- ```
908
- composite_score = (code_safety × 0.25) + (credential_safety × 0.25) + (network_exposure × 0.20) + (runtime_protection × 0.15) + (web3_safety × 0.15)
909
- ```
1127
+ Run the scoring script (it reads the raw facts and deterministically computes all dimension scores, composite score, and tier — do NOT calculate these yourself):
910
1128
 
911
- If Web3 Safety is N/A, redistribute its 15% weight proportionally across the other 4 dimensions:
912
- ```
913
- composite_score = (code_safety × 0.294) + (credential_safety × 0.294) + (network_exposure × 0.235) + (runtime_protection × 0.176)
1129
+ ```bash
1130
+ cd <skill_directory> && node scripts/checkup-score.js --file /tmp/agentguard-raw-facts.json
914
1131
  ```
915
1132
 
916
- Round to the nearest integer.
1133
+ The script outputs a JSON object with:
1134
+ - `composite_score` (0–100)
1135
+ - `tier` (S/A/B/F) and `tier_label`
1136
+ - `total_findings`
1137
+ - `dimensions`: `code_safety`, `credential_safety`, `network_exposure`, `runtime_protection`, `web3_safety` — each with `score` and `findings[]`
917
1138
 
918
- **Tier assignment (MUST use these exact thresholds):**
1139
+ Capture this JSON output — you will use it in Step 4.
919
1140
 
920
- | Score Range | Tier | Label |
921
- |-------------|------|-------|
922
- | **90–100** | **S** | JACKED |
923
- | **70–89** | **A** | Healthy |
924
- | **50–69** | **B** | Tired |
925
- | **0–49** | **F** | Critical |
1141
+ ### Step 4: Generate Analysis Report
926
1142
 
927
- **Example**: code_safety=100, credential_safety=80, network_exposure=85, runtime_protection=30, web3=N/A composite = (100×0.294)+(80×0.294)+(85×0.235)+(30×0.176) = 29.4+23.5+20.0+5.3 = **78** Tier **A** (Healthy).
928
-
929
- ### Step 3: Generate Analysis Report
930
-
931
- Based on all collected data and findings, write a **comprehensive security analysis report** as a single text block. This is where you use your AI reasoning ability — don't just list facts, **analyze** them:
1143
+ Based on the scored output from Step 3 and the raw facts you collected, write a **comprehensive security analysis report** as a single text block. This is where you use your AI reasoning ability — don't just list facts, **analyze** them:
932
1144
 
933
1145
  - Summarize the overall security posture in 2-3 sentences
934
1146
  - Highlight the most critical risks and explain **why** they matter (e.g. "Your ~/.ssh/ permissions allow any process running as your user to read your private keys, which means a malicious skill could silently exfiltrate them")
935
- - For each major finding, provide a specific actionable fix (exact command to run)
1147
+ - For each major finding from the scored output, provide a specific actionable fix (exact command to run)
936
1148
  - Note what's going well — acknowledge secure areas
937
- - If applicable, explain attack scenarios that the current configuration is vulnerable to (e.g. "A malicious skill could install a cron job that phones home your credentials every hour")
1149
+ - If applicable, explain attack scenarios that the current configuration is vulnerable to
938
1150
  - Keep the tone professional but direct, like a security consultant's report
939
1151
 
940
- This report goes into the `"analysis"` field of the JSON output.
1152
+ This report goes into the `"analysis"` field of the final JSON.
941
1153
 
942
1154
  Also generate a list of actionable recommendations as `{ "severity": "...", "text": "..." }` objects for the structured view.
943
1155
 
944
- ### Pre-Step-4 Validation
945
-
946
- **Before assembling the JSON, verify you have collected data for ALL 5 dimensions:**
947
-
948
- - [ ] `code_safety` — from Step 1 check 1 (skill scanning)
949
- - [ ] `credential_safety` — from Step 1 checks 2 + 3 (permissions + DLP)
950
- - [ ] `network_exposure` — from Step 1 checks 4 + 5 + 6 (ports + cron + env vars)
951
- - [ ] `runtime_protection` — from Step 1 check 7 (hooks + audit log)
952
- - [ ] `web3_safety` — from Step 2 (only if Web3 detected, otherwise `{ "score": null, "na": true }`)
1156
+ ### Step 5: Generate HTML Report
953
1157
 
954
- **If any dimension is missing data, go back and run the missing checks. Do NOT submit a report with only code_safety filled in.**
955
-
956
- ### Step 4: Generate Report
957
-
958
- Assemble the results into a JSON object and pipe it to the report generator:
1158
+ Assemble the final JSON by merging the scored output from Step 3 with the analysis from Step 4, then pass it to the report generator:
959
1159
 
960
1160
  ```json
961
1161
  {
962
1162
  "timestamp": "<ISO 8601>",
963
- "composite_score": <0-100>,
964
- "tier": "<S|A|B|F>",
1163
+ "composite_score": <from checkup-score.js>,
1164
+ "tier": "<from checkup-score.js>",
965
1165
  "dimensions": {
966
- "code_safety": { "score": <n>, "findings": [...], "details": "<one-line summary>" },
967
- "credential_safety": { "score": <n>, "findings": [...], "details": "<one-line summary>" },
968
- "network_exposure": { "score": <n>, "findings": [...], "details": "<one-line summary>" },
969
- "runtime_protection": { "score": <n>, "findings": [...], "details": "<one-line summary>" },
970
- "web3_safety": { "score": <n|null>, "na": <bool>, "findings": [...], "details": "<one-line summary>" }
1166
+ "code_safety": { "score": <from score>, "findings": [...], "details": "<one-line summary>" },
1167
+ "credential_safety": { "score": <from score>, "findings": [...], "details": "<one-line summary>" },
1168
+ "network_exposure": { "score": <from score>, "findings": [...], "details": "<one-line summary>" },
1169
+ "runtime_protection": { "score": <from score>, "findings": [...], "details": "<one-line summary>" },
1170
+ "web3_safety": { "score": <from score|null>, "na": <bool>, "findings": [...], "details": "<one-line summary>" }
971
1171
  },
972
- "skills_scanned": <count>,
1172
+ "skills_scanned": <count of skills from Step 1>,
973
1173
  "protection_level": "<level>",
974
1174
  "analysis": "<the comprehensive AI-written security analysis report>",
975
1175
  "recommendations": [
@@ -978,19 +1178,24 @@ Assemble the results into a JSON object and pipe it to the report generator:
978
1178
  }
979
1179
  ```
980
1180
 
981
- Execute the report generator. **Use the `--file` method for cross-platform compatibility** (the `echo | pipe` method fails on Windows due to shell quoting differences):
1181
+ **If `--format json` was specified**:
1182
+ 1. Write this JSON to the `--output <file>` path (or `/tmp/agentguard-checkup-data.json` if no `--output` given) using the Write tool.
1183
+ 2. Tell the user: "Checkup JSON written to `<file>`." — include the composite score and tier in the message.
1184
+ 3. **Stop here** — skip Steps 5 and 6 (HTML generation and MEDIA delivery). The terminal summary in Step 5 is also skipped since the user is consuming the raw JSON programmatically.
1185
+
1186
+ **Otherwise (default HTML flow)**:
1187
+
1188
+ Write the JSON to a temporary file using the Write tool (e.g. `/tmp/agentguard-checkup-data.json`), then run (remember to `cd` into the skill directory first — see "Resolving Script Paths" above):
982
1189
 
983
- 1. First, write the JSON to a temporary file using the Write tool (e.g. `/tmp/agentguard-checkup-data.json`)
984
- 2. Then run (remember to `cd` into the skill directory first — see "Resolving Script Paths" above):
985
1190
  ```bash
986
1191
  cd <skill_directory> && node scripts/checkup-report.js --file /tmp/agentguard-checkup-data.json
987
1192
  ```
988
1193
 
989
- The script outputs the HTML file path to stdout (e.g. `/tmp/agentguard-checkup-1234567890.html`). Capture this path — you will need it for delivery in Step 6.
1194
+ The script outputs the HTML file path to stdout (e.g. `/tmp/agentguard-checkup-1234567890.html`). Capture this path — you will need it for delivery in Step 7.
990
1195
 
991
- > **Note**: The script also supports stdin pipe (`echo '<json>' | node scripts/checkup-report.js`) but this may fail on Windows cmd.exe where single quotes are not string delimiters. Always prefer `--file`.
1196
+ > **Note**: The script also supports stdin pipe (`echo '<json>' | node scripts/checkup-report.js`) but this may fail on Windows cmd.exe. Always prefer `--file`.
992
1197
 
993
- ### Step 5: Terminal Summary (REQUIRED)
1198
+ ### Step 6: Terminal Summary (REQUIRED)
994
1199
 
995
1200
  **You MUST output this summary after the report generates.** This is the primary output the user sees. Do NOT skip this step — always show the score, dimension table, and report path:
996
1201
 
@@ -1034,11 +1239,11 @@ Examples of plain-language descriptions:
1034
1239
  - SSH permissions: "Your SSH key folder has loose permissions — other processes on this machine could potentially read your private keys."
1035
1240
  - Plaintext credential: "A private key or API token was found in plain text in a file — it should be removed and rotated."
1036
1241
 
1037
- ### Step 6: Deliver the Report to the User
1242
+ ### Step 7: Deliver the Report to the User
1038
1243
 
1039
1244
  After printing the terminal summary, deliver the HTML report file. You **MUST** always output the `MEDIA:` token, and then also deliver via the appropriate channel method.
1040
1245
 
1041
- #### 6a. MEDIA token (required — always do this)
1246
+ #### 7a. MEDIA token (required — always do this)
1042
1247
 
1043
1248
  Output the following line on its **own line** in your response:
1044
1249
 
@@ -1050,10 +1255,10 @@ For example: `MEDIA:/tmp/agentguard-checkup-1234567890.html`
1050
1255
 
1051
1256
  This is how platforms like OpenClaw automatically deliver the file as a Telegram/Discord/WhatsApp attachment via `sendDocument`. The platform strips this line from visible text — the user won't see it. **Always output this regardless of what channel you think you're in.**
1052
1257
 
1053
- #### 6b. Channel-specific delivery (in addition to MEDIA token)
1258
+ #### 7b. Channel-specific delivery (in addition to MEDIA token)
1054
1259
 
1055
1260
  **Claude Code (local desktop)**
1056
- - The browser should already be open from Step 4.
1261
+ - The browser should already be open from Step 5.
1057
1262
  - Also copy to Desktop: `cp <file_path> ~/Desktop/agentguard-checkup-$(date +%Y-%m-%d).html`
1058
1263
  - Tell the user: "✅ Report saved to your Desktop and opened in browser."
1059
1264