@goplus/agentguard 1.0.14 → 1.1.1
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/dist/scanner/rules/trojan.js +1 -1
- package/dist/scanner/rules/trojan.js.map +1 -1
- package/package.json +1 -1
- package/skills/agentguard/SKILL.md +138 -61
- package/skills/agentguard/patrol-checks.md +12 -2
- package/skills/agentguard/scan-rules.md +1 -1
- package/skills/agentguard/scripts/checkup-report.js +84 -30
- /package/skills/agentguard/{scripts/package.json → package.json} +0 -0
|
@@ -81,7 +81,7 @@ exports.TROJAN_RULES = [
|
|
|
81
81
|
{
|
|
82
82
|
id: 'SOCIAL_ENGINEERING',
|
|
83
83
|
description: 'Detects social engineering pressure language in skill instructions',
|
|
84
|
-
severity: '
|
|
84
|
+
severity: 'high',
|
|
85
85
|
file_patterns: ['*.md'],
|
|
86
86
|
patterns: [
|
|
87
87
|
/CRITICAL\s+REQUIREMENT/i,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"trojan.js","sourceRoot":"","sources":["../../../src/scanner/rules/trojan.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"trojan.js","sourceRoot":"","sources":["../../../src/scanner/rules/trojan.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@goplus/agentguard",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "GoPlus AgentGuard — Security guard for AI agents. Blocks dangerous commands, prevents data leaks, protects secrets. 20 detection rules, runtime action evaluation, trust registry.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: agentguard
|
|
3
|
-
description: GoPlus AgentGuard — AI agent security guard. Run /agentguard checkup for a full security health check
|
|
3
|
+
description: GoPlus AgentGuard — AI agent security guard. Run /agentguard checkup for a full security health check, scans all installed skills, checks credentials, permissions, and network exposure, then delivers an HTML report directly to you. Also use for scanning third-party code, blocking dangerous commands, preventing data leaks, evaluating action safety, and running daily security patrols.
|
|
4
4
|
license: MIT
|
|
5
5
|
compatibility: Requires Node.js 18+. Optional GoPlus API credentials for enhanced Web3 simulation.
|
|
6
6
|
metadata:
|
|
@@ -8,7 +8,7 @@ metadata:
|
|
|
8
8
|
version: "1.1"
|
|
9
9
|
optional_env: "GOPLUS_API_KEY, GOPLUS_API_SECRET (for Web3 transaction simulation only)"
|
|
10
10
|
user-invocable: true
|
|
11
|
-
allowed-tools: Read, 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(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 *)
|
|
11
|
+
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(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 *)
|
|
12
12
|
argument-hint: "[scan|action|patrol|trust|report|config|checkup] [args...]"
|
|
13
13
|
---
|
|
14
14
|
|
|
@@ -89,7 +89,7 @@ For each rule, use Grep to search the relevant file types. Record every match wi
|
|
|
89
89
|
| 21 | TROJAN_DISTRIBUTION | CRITICAL | md | Trojanized binary download + password + execute |
|
|
90
90
|
| 22 | SUSPICIOUS_PASTE_URL | HIGH | all | URLs to paste sites (pastebin, glot.io, etc.) |
|
|
91
91
|
| 23 | SUSPICIOUS_IP | MEDIUM | all | Hardcoded public IPv4 addresses |
|
|
92
|
-
| 24 | SOCIAL_ENGINEERING |
|
|
92
|
+
| 24 | SOCIAL_ENGINEERING | HIGH | md | Pressure language + execution instructions |
|
|
93
93
|
|
|
94
94
|
### Risk Level Calculation
|
|
95
95
|
|
|
@@ -284,7 +284,7 @@ Detect tampered or unregistered skill packages by comparing file hashes against
|
|
|
284
284
|
Scan workspace files for leaked secrets using AgentGuard's own detection patterns.
|
|
285
285
|
|
|
286
286
|
**Steps**:
|
|
287
|
-
1. Use Grep to scan `$OC/workspace/` (
|
|
287
|
+
1. Use Grep to scan `$OC/workspace/` **recursively, covering all agent subdirectories** (e.g. all `workspace-agent-*/` directories, not just the current agent's workspace) with patterns from:
|
|
288
288
|
- scan-rules.md Rule 7 (PRIVATE_KEY_PATTERN): `0x[a-fA-F0-9]{64}` in quotes
|
|
289
289
|
- scan-rules.md Rule 8 (MNEMONIC_PATTERN): BIP-39 word sequences, `seed_phrase`, `mnemonic`
|
|
290
290
|
- scan-rules.md Rule 5 (READ_SSH_KEYS): SSH key file references in workspace
|
|
@@ -610,89 +610,129 @@ Run a comprehensive agent health checkup across 6 security dimensions. Generates
|
|
|
610
610
|
|
|
611
611
|
### Step 1: Data Collection
|
|
612
612
|
|
|
613
|
+
**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.**
|
|
614
|
+
|
|
613
615
|
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.
|
|
614
616
|
|
|
615
|
-
1. **Discover & scan installed skills
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
-
|
|
619
|
-
-
|
|
620
|
-
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
617
|
+
1. **[REQUIRED] Discover & scan installed skills** (→ feeds Dimension 1: Code Safety): Glob ALL of the following paths for `*/SKILL.md`:
|
|
618
|
+
- `~/.claude/skills/*/SKILL.md`
|
|
619
|
+
- `~/.openclaw/skills/*/SKILL.md`
|
|
620
|
+
- `~/.openclaw/workspace/skills/*/SKILL.md`
|
|
621
|
+
- `~/.qclaw/skills/*/SKILL.md`
|
|
622
|
+
- `~/.qclaw/workspace/skills/*/SKILL.md`
|
|
623
|
+
|
|
624
|
+
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.
|
|
625
|
+
2. **[REQUIRED] Credential file permissions** (→ feeds Dimension 2: Credential Safety): Platform-aware check — behavior differs by OS:
|
|
626
|
+
- **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.**
|
|
627
|
+
- **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.
|
|
628
|
+
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:
|
|
629
|
+
- For OpenClaw / QClaw: scan `~/.openclaw/workspace/` and `~/.qclaw/workspace/` recursively — this includes **all** `workspace-agent-*/` subdirectories, not just the current agent's workspace
|
|
630
|
+
- For Claude Code: scan `~/.claude/` recursively
|
|
631
|
+
- Patterns to detect:
|
|
632
|
+
- Private keys: `0x[a-fA-F0-9]{64}`, `-----BEGIN.*PRIVATE KEY-----`
|
|
633
|
+
- Mnemonics: sequences of 12+ BIP-39 words, `seed_phrase`, `mnemonic`
|
|
634
|
+
- API keys/tokens: `AKIA[0-9A-Z]{16}`, `gh[pousr]_[A-Za-z0-9_]{36}`, plaintext passwords
|
|
635
|
+
- **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.
|
|
636
|
+
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)
|
|
637
|
+
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/`
|
|
638
|
+
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
|
|
639
|
+
7. **[REQUIRED] Runtime protection check** (→ feeds Dimension 4: Runtime Protection): Check if security hooks exist in `~/.claude/settings.json` or `~/.openclaw/openclaw.json`, check for audit logs at `~/.agentguard/audit.jsonl`
|
|
625
640
|
|
|
626
641
|
### Step 2: Score Calculation
|
|
627
642
|
|
|
628
|
-
|
|
643
|
+
**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.**
|
|
629
644
|
|
|
630
645
|
#### Dimension 1: Skill & Code Safety (weight: 25%)
|
|
631
646
|
|
|
632
|
-
Uses AgentGuard's 24-rule scan engine (`/agentguard scan`) to audit each installed skill.
|
|
647
|
+
Uses AgentGuard's 24-rule scan engine (`/agentguard scan`) to audit each installed skill. Start at base 100 and **deduct** for findings:
|
|
648
|
+
|
|
649
|
+
- Base score: **100**
|
|
650
|
+
- Each CRITICAL finding: **−15**
|
|
651
|
+
- Each HIGH finding: **−8**
|
|
652
|
+
- Each MEDIUM finding: **−3**
|
|
653
|
+
- Floor at **0** (never negative)
|
|
633
654
|
|
|
634
|
-
|
|
635
|
-
|-------|-------|---------------------|
|
|
636
|
-
| All skills scanned with risk level LOW | +40 | For each skill with findings, add per-finding: "<rule_id> in <skill>:<file>:<line>" with its severity |
|
|
637
|
-
| No CRITICAL scan findings across all skills | +30 | "CRITICAL: <rule_id> detected in <skill>" (CRITICAL) |
|
|
638
|
-
| No HIGH scan findings across all skills | +30 | "HIGH: <rule_id> detected in <skill>" (HIGH) |
|
|
655
|
+
For each finding, add: `"<rule_id> in <skill>:<file>:<line>"` with its severity.
|
|
639
656
|
|
|
640
|
-
|
|
657
|
+
**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.
|
|
641
658
|
|
|
642
|
-
If no skills installed: score = 70
|
|
659
|
+
If no skills installed: score = **70**, add finding: "No third-party skills installed — no code to audit" (LOW).
|
|
643
660
|
|
|
644
661
|
#### Dimension 2: Credential & Secret Safety (weight: 25%)
|
|
645
662
|
|
|
646
|
-
Checks for leaked credentials and permission hygiene.
|
|
663
|
+
Checks for leaked credentials and permission hygiene. Start at **0**, add points for each check that **passes** (total possible = 100):
|
|
647
664
|
|
|
648
|
-
| Check |
|
|
649
|
-
|
|
650
|
-
| `~/.ssh/` permissions are 700 or stricter |
|
|
651
|
-
| `~/.gnupg/` permissions are 700 or stricter |
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
665
|
+
| Check | Points if PASS | If FAIL → finding |
|
|
666
|
+
|-------|---------------|-------------------|
|
|
667
|
+
| `~/.ssh/` permissions are 700 or stricter | **+25** | "~/.ssh/ permissions too open (<actual>) — should be 700" (HIGH) |
|
|
668
|
+
| `~/.gnupg/` permissions are 700 or stricter | **+15** | "~/.gnupg/ permissions too open (<actual>) — should be 700" (MEDIUM) |
|
|
669
|
+
|
|
670
|
+
**Permission check rules (to avoid false positives):**
|
|
671
|
+
- **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.
|
|
672
|
+
- **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`.
|
|
673
|
+
- **macOS/Linux**: Flag as FAIL only when the directory exists AND stat returns a numeric value AND that value is greater than 700.
|
|
674
|
+
| No private keys (hex 0x..64, PEM) found in skill code or workspace | **+25** | "Plaintext private key found in <location>" (CRITICAL) |
|
|
675
|
+
| No mnemonic phrases found in skill code or workspace | **+20** | "Plaintext mnemonic found in <location>" (CRITICAL) |
|
|
676
|
+
| No API keys/tokens (AWS AKIA.., GitHub gh*_) found in skill code | **+15** | "API key/token found in <location>" (HIGH) |
|
|
655
677
|
|
|
656
678
|
#### Dimension 3: Network & System Exposure (weight: 20%)
|
|
657
679
|
|
|
658
|
-
Checks for dangerous network exposure and system-level risks.
|
|
680
|
+
Checks for dangerous network exposure and system-level risks. Start at **0**, add points for each check that **passes** (total possible = 100):
|
|
681
|
+
|
|
682
|
+
| Check | Points if PASS | If FAIL → finding |
|
|
683
|
+
|-------|---------------|-------------------|
|
|
684
|
+
| 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) |
|
|
685
|
+
| No suspicious cron jobs (curl\|bash, wget\|sh, accessing ~/.ssh/) | **+30** | "Suspicious cron job: <command>" (HIGH) |
|
|
686
|
+
| No sensitive env vars with dangerous names (PRIVATE_KEY, MNEMONIC) | **+20** | "Sensitive env var exposed: <name>" (MEDIUM) |
|
|
687
|
+
| OpenClaw config files have proper permissions (600) if applicable | **+15** | "OpenClaw config <file> permissions too open" (MEDIUM) |
|
|
659
688
|
|
|
660
|
-
|
|
661
|
-
|-------|-------|---------------------|
|
|
662
|
-
| 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) |
|
|
663
|
-
| No suspicious cron jobs (curl\|bash, wget\|sh, accessing ~/.ssh/) | +30 | "Suspicious cron job: <command>" (HIGH) |
|
|
664
|
-
| No sensitive env vars with dangerous names (PRIVATE_KEY, MNEMONIC) | +20 | "Sensitive env var exposed: <name>" (MEDIUM) |
|
|
665
|
-
| OpenClaw config files have proper permissions (600) if applicable | +15 | "OpenClaw config <file> permissions too open" (MEDIUM) |
|
|
689
|
+
**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**.
|
|
666
690
|
|
|
667
691
|
#### Dimension 4: Runtime Protection (weight: 15%)
|
|
668
692
|
|
|
669
|
-
Checks whether the agent has active security monitoring.
|
|
693
|
+
Checks whether the agent has active security monitoring. Start at **0**, add points for each check that **passes** (total possible = 100):
|
|
670
694
|
|
|
671
|
-
| Check |
|
|
672
|
-
|
|
673
|
-
| Security hooks/guards installed (AgentGuard, custom hooks, etc.) |
|
|
674
|
-
| Security audit log exists with recent events |
|
|
675
|
-
| Skills have been security-scanned at least once |
|
|
695
|
+
| Check | Points if PASS | If FAIL → finding |
|
|
696
|
+
|-------|---------------|-------------------|
|
|
697
|
+
| Security hooks/guards installed (AgentGuard, custom hooks, etc.) | **+40** | "No security hooks installed — actions are unmonitored" (HIGH) |
|
|
698
|
+
| Security audit log exists with recent events | **+30** | "No security audit log — no threat history available" (MEDIUM) |
|
|
699
|
+
| Skills have been security-scanned at least once | **+30** | "Installed skills have never been security-scanned" (MEDIUM) |
|
|
676
700
|
|
|
677
701
|
#### Dimension 5: Web3 Safety (weight: 15% if applicable)
|
|
678
702
|
|
|
679
|
-
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 }`.
|
|
703
|
+
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):
|
|
704
|
+
|
|
705
|
+
| Check | Points if PASS | If FAIL → finding |
|
|
706
|
+
|-------|---------------|-------------------|
|
|
707
|
+
| No wallet-draining patterns (approve+transferFrom) in skill code | **+40** | "Wallet-draining pattern detected in <skill>" (CRITICAL) |
|
|
708
|
+
| No unlimited token approval patterns in skill code | **+30** | "Unlimited approval pattern detected in <skill>" (HIGH) |
|
|
709
|
+
| Transaction security API configured (GoPlus or equivalent) | **+30** | "No transaction security API — Web3 calls are unverified" (MEDIUM) |
|
|
710
|
+
|
|
711
|
+
#### Composite Score Calculation
|
|
712
|
+
|
|
713
|
+
Calculate the weighted average of all applicable dimensions:
|
|
714
|
+
|
|
715
|
+
```
|
|
716
|
+
composite_score = (code_safety × 0.25) + (credential_safety × 0.25) + (network_exposure × 0.20) + (runtime_protection × 0.15) + (web3_safety × 0.15)
|
|
717
|
+
```
|
|
718
|
+
|
|
719
|
+
If Web3 Safety is N/A, redistribute its 15% weight proportionally across the other 4 dimensions:
|
|
720
|
+
```
|
|
721
|
+
composite_score = (code_safety × 0.294) + (credential_safety × 0.294) + (network_exposure × 0.235) + (runtime_protection × 0.176)
|
|
722
|
+
```
|
|
680
723
|
|
|
681
|
-
|
|
682
|
-
|-------|-------|---------------------|
|
|
683
|
-
| No wallet-draining patterns (approve+transferFrom) in skill code | +40 | "Wallet-draining pattern detected in <skill>" (CRITICAL) |
|
|
684
|
-
| No unlimited token approval patterns in skill code | +30 | "Unlimited approval pattern detected in <skill>" (HIGH) |
|
|
685
|
-
| Transaction security API configured (GoPlus or equivalent) | +30 | "No transaction security API — Web3 calls are unverified" (MEDIUM) |
|
|
724
|
+
Round to the nearest integer.
|
|
686
725
|
|
|
687
|
-
|
|
726
|
+
**Tier assignment (MUST use these exact thresholds):**
|
|
688
727
|
|
|
689
|
-
|
|
728
|
+
| Score Range | Tier | Label |
|
|
729
|
+
|-------------|------|-------|
|
|
730
|
+
| **90–100** | **S** | JACKED |
|
|
731
|
+
| **70–89** | **A** | Healthy |
|
|
732
|
+
| **50–69** | **B** | Tired |
|
|
733
|
+
| **0–49** | **F** | Critical |
|
|
690
734
|
|
|
691
|
-
|
|
692
|
-
- 90–100 → Tier **S** (JACKED)
|
|
693
|
-
- 70–89 → Tier **A** (Healthy)
|
|
694
|
-
- 50–69 → Tier **B** (Tired)
|
|
695
|
-
- 0–49 → Tier **F** (Critical)
|
|
735
|
+
**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).
|
|
696
736
|
|
|
697
737
|
### Step 3: Generate Analysis Report
|
|
698
738
|
|
|
@@ -709,6 +749,18 @@ This report goes into the `"analysis"` field of the JSON output.
|
|
|
709
749
|
|
|
710
750
|
Also generate a list of actionable recommendations as `{ "severity": "...", "text": "..." }` objects for the structured view.
|
|
711
751
|
|
|
752
|
+
### Pre-Step-4 Validation
|
|
753
|
+
|
|
754
|
+
**Before assembling the JSON, verify you have collected data for ALL 5 dimensions:**
|
|
755
|
+
|
|
756
|
+
- [ ] `code_safety` — from Step 1 check 1 (skill scanning)
|
|
757
|
+
- [ ] `credential_safety` — from Step 1 checks 2 + 3 (permissions + DLP)
|
|
758
|
+
- [ ] `network_exposure` — from Step 1 checks 4 + 5 + 6 (ports + cron + env vars)
|
|
759
|
+
- [ ] `runtime_protection` — from Step 1 check 7 (hooks + audit log)
|
|
760
|
+
- [ ] `web3_safety` — from Step 2 (only if Web3 detected, otherwise `{ "score": null, "na": true }`)
|
|
761
|
+
|
|
762
|
+
**If any dimension is missing data, go back and run the missing checks. Do NOT submit a report with only code_safety filled in.**
|
|
763
|
+
|
|
712
764
|
### Step 4: Generate Report
|
|
713
765
|
|
|
714
766
|
Assemble the results into a JSON object and pipe it to the report generator:
|
|
@@ -734,16 +786,21 @@ Assemble the results into a JSON object and pipe it to the report generator:
|
|
|
734
786
|
}
|
|
735
787
|
```
|
|
736
788
|
|
|
737
|
-
Execute
|
|
789
|
+
Execute the report generator. **Use the `--file` method for cross-platform compatibility** (the `echo | pipe` method fails on Windows due to shell quoting differences):
|
|
790
|
+
|
|
791
|
+
1. First, write the JSON to a temporary file using the Write tool (e.g. `/tmp/agentguard-checkup-data.json`)
|
|
792
|
+
2. Then run (remember to `cd` into the skill directory first — see "Resolving Script Paths" above):
|
|
738
793
|
```bash
|
|
739
|
-
cd <skill_directory> &&
|
|
794
|
+
cd <skill_directory> && node scripts/checkup-report.js --file /tmp/agentguard-checkup-data.json
|
|
740
795
|
```
|
|
741
796
|
|
|
742
797
|
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.
|
|
743
798
|
|
|
744
|
-
|
|
799
|
+
> **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`.
|
|
800
|
+
|
|
801
|
+
### Step 5: Terminal Summary (REQUIRED)
|
|
745
802
|
|
|
746
|
-
|
|
803
|
+
**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:
|
|
747
804
|
|
|
748
805
|
```
|
|
749
806
|
## 🦞 GoPlus AgentGuard Health Checkup
|
|
@@ -763,7 +820,27 @@ After the report generates, output a brief summary in the terminal:
|
|
|
763
820
|
**Full visual report**: <path> (opened in browser)
|
|
764
821
|
|
|
765
822
|
💡 Top recommendation: <first recommendation text>
|
|
823
|
+
|
|
824
|
+
### Next Steps
|
|
825
|
+
(Only include this section if there are HIGH or CRITICAL findings.)
|
|
826
|
+
|
|
827
|
+
List each HIGH or CRITICAL finding as a plain-language suggestion — no commands, no JSON, no technical details. One sentence per item. Ask the user to confirm if they'd like help with any of them.
|
|
828
|
+
|
|
829
|
+
Format:
|
|
766
830
|
```
|
|
831
|
+
⚠️ A few things need your attention:
|
|
832
|
+
1. 🔴 <plain description of critical issue and why it matters>
|
|
833
|
+
2. 🟠 <plain description of high issue and why it matters>
|
|
834
|
+
...
|
|
835
|
+
|
|
836
|
+
Reply with the number(s) you'd like help with and I'll walk you through it.
|
|
837
|
+
```
|
|
838
|
+
|
|
839
|
+
Examples of plain-language descriptions:
|
|
840
|
+
- No hooks: "Security monitoring isn't active — AgentGuard can't block threats in real-time until hooks are configured."
|
|
841
|
+
- Unregistered skills: "10 installed skills haven't been security-reviewed — they're running with no trust level assigned."
|
|
842
|
+
- SSH permissions: "Your SSH key folder has loose permissions — other processes on this machine could potentially read your private keys."
|
|
843
|
+
- Plaintext credential: "A private key or API token was found in plain text in a file — it should be removed and rotated."
|
|
767
844
|
|
|
768
845
|
### Step 6: Deliver the Report to the User
|
|
769
846
|
|
|
@@ -75,6 +75,7 @@ Detailed commands, patterns, and thresholds for the 8 patrol checks. This docume
|
|
|
75
75
|
|
|
76
76
|
### Permission Checks
|
|
77
77
|
|
|
78
|
+
**macOS/Linux:**
|
|
78
79
|
```bash
|
|
79
80
|
# SSH directory — should be 700
|
|
80
81
|
stat -f "%Lp" ~/.ssh/ 2>/dev/null || stat -c "%a" ~/.ssh/ 2>/dev/null
|
|
@@ -82,10 +83,19 @@ stat -f "%Lp" ~/.ssh/ 2>/dev/null || stat -c "%a" ~/.ssh/ 2>/dev/null
|
|
|
82
83
|
stat -f "%Lp" ~/.gnupg/ 2>/dev/null || stat -c "%a" ~/.gnupg/ 2>/dev/null
|
|
83
84
|
```
|
|
84
85
|
|
|
86
|
+
**Windows (use icacls instead of stat):**
|
|
87
|
+
```powershell
|
|
88
|
+
icacls $env:USERPROFILE\.ssh 2>$null
|
|
89
|
+
icacls $env:USERPROFILE\.gnupg 2>$null
|
|
90
|
+
```
|
|
91
|
+
|
|
85
92
|
| Condition | Severity |
|
|
86
93
|
|-----------|----------|
|
|
87
|
-
| `~/.ssh/` permissions > 700 | HIGH |
|
|
88
|
-
| `~/.gnupg/` permissions > 700 | MEDIUM |
|
|
94
|
+
| macOS/Linux: `~/.ssh/` exists AND permissions > 700 | HIGH |
|
|
95
|
+
| macOS/Linux: `~/.gnupg/` exists AND permissions > 700 | MEDIUM |
|
|
96
|
+
| Windows: `~/.ssh/` exists AND ACL grants access to Everyone/Users/Authenticated Users | HIGH |
|
|
97
|
+
| Windows: `~/.gnupg/` exists AND ACL grants access to Everyone/Users/Authenticated Users | MEDIUM |
|
|
98
|
+
| Directory does not exist (stat/icacls returns empty) | N/A — not a finding |
|
|
89
99
|
|
|
90
100
|
---
|
|
91
101
|
|
|
@@ -292,7 +292,7 @@ Detects trojanized binary distribution patterns. Flags when 2+ of the following
|
|
|
292
292
|
- Version-like patterns (`x.0.0.0`)
|
|
293
293
|
- Values > 255 in any octet
|
|
294
294
|
|
|
295
|
-
## Rule 24: SOCIAL_ENGINEERING (
|
|
295
|
+
## Rule 24: SOCIAL_ENGINEERING (HIGH)
|
|
296
296
|
**Files**: `*.md`
|
|
297
297
|
|
|
298
298
|
| Pattern | Description |
|
|
@@ -15,9 +15,17 @@
|
|
|
15
15
|
import { writeFileSync, readFileSync, existsSync } from 'node:fs';
|
|
16
16
|
import { join, dirname } from 'node:path';
|
|
17
17
|
import { tmpdir, homedir } from 'node:os';
|
|
18
|
-
import { exec } from 'node:child_process';
|
|
18
|
+
import { exec, spawn } from 'node:child_process';
|
|
19
19
|
import { fileURLToPath } from 'node:url';
|
|
20
20
|
|
|
21
|
+
const DIM_META = {
|
|
22
|
+
code_safety: { icon: 'find_in_page', name: 'Skill & Code Safety', zh: '技能与代码安全' },
|
|
23
|
+
credential_safety: { icon: 'key', name: 'Credential & Secrets', zh: '凭证与密钥安全' },
|
|
24
|
+
network_exposure: { icon: 'lan', name: 'Network & System', zh: '网络与系统暴露' },
|
|
25
|
+
runtime_protection: { icon: 'shield', name: 'Runtime Protection', zh: '运行时防护' },
|
|
26
|
+
web3_safety: { icon: 'token', name: 'Web3 Safety', zh: 'Web3 安全' },
|
|
27
|
+
};
|
|
28
|
+
|
|
21
29
|
// Try to load favicon from agentguard-server or fallback
|
|
22
30
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
23
31
|
let faviconB64 = '';
|
|
@@ -29,13 +37,27 @@ for (const p of iconPaths) {
|
|
|
29
37
|
if (existsSync(p)) { faviconB64 = readFileSync(p).toString('base64'); break; }
|
|
30
38
|
}
|
|
31
39
|
|
|
32
|
-
|
|
33
|
-
process.
|
|
34
|
-
|
|
35
|
-
process.
|
|
36
|
-
try {
|
|
37
|
-
|
|
38
|
-
|
|
40
|
+
// Support --file <path> argument to read JSON from file (cross-platform friendly)
|
|
41
|
+
const fileArgIdx = process.argv.indexOf('--file');
|
|
42
|
+
if (fileArgIdx !== -1 && process.argv[fileArgIdx + 1]) {
|
|
43
|
+
const filePath = process.argv[fileArgIdx + 1];
|
|
44
|
+
try {
|
|
45
|
+
const content = readFileSync(filePath, 'utf8');
|
|
46
|
+
generateReport(JSON.parse(content));
|
|
47
|
+
} catch (err) {
|
|
48
|
+
process.stderr.write(`Error reading ${filePath}: ${err.message}\n`);
|
|
49
|
+
process.exit(1);
|
|
50
|
+
}
|
|
51
|
+
} else {
|
|
52
|
+
// Fallback: read JSON from stdin (pipe)
|
|
53
|
+
let input = '';
|
|
54
|
+
process.stdin.setEncoding('utf8');
|
|
55
|
+
process.stdin.on('data', (chunk) => { input += chunk; });
|
|
56
|
+
process.stdin.on('end', () => {
|
|
57
|
+
try { generateReport(JSON.parse(input)); }
|
|
58
|
+
catch (err) { process.stderr.write(`Error: ${err.message}\n`); process.exit(1); }
|
|
59
|
+
});
|
|
60
|
+
}
|
|
39
61
|
|
|
40
62
|
// ---------------------------------------------------------------------------
|
|
41
63
|
// Helpers
|
|
@@ -73,13 +95,7 @@ function getTier(score) {
|
|
|
73
95
|
])};
|
|
74
96
|
}
|
|
75
97
|
|
|
76
|
-
|
|
77
|
-
code_safety: { icon: 'find_in_page', name: 'Skill & Code Safety', zh: '技能与代码安全' },
|
|
78
|
-
credential_safety: { icon: 'key', name: 'Credential & Secrets', zh: '凭证与密钥安全' },
|
|
79
|
-
network_exposure: { icon: 'lan', name: 'Network & System', zh: '网络与系统暴露' },
|
|
80
|
-
runtime_protection: { icon: 'shield', name: 'Runtime Protection', zh: '运行时防护' },
|
|
81
|
-
web3_safety: { icon: 'token', name: 'Web3 Safety', zh: 'Web3 安全' },
|
|
82
|
-
};
|
|
98
|
+
|
|
83
99
|
|
|
84
100
|
function esc(s) { return String(s).replace(/&/g,'&').replace(/</g,'<').replace(/>/g,'>').replace(/"/g,'"'); }
|
|
85
101
|
|
|
@@ -730,7 +746,8 @@ function generateReport(data) {
|
|
|
730
746
|
autoRecs.push({ severity: 'LOW', text: 'Enable auto-scan on session start: export AGENTGUARD_AUTO_SCAN=1', zh: '启用会话启动时自动扫描:export AGENTGUARD_AUTO_SCAN=1' });
|
|
731
747
|
|
|
732
748
|
// Merge: user recs first, then auto recs (dedup by text similarity)
|
|
733
|
-
|
|
749
|
+
// Guard against malformed entries where text may be undefined (#37)
|
|
750
|
+
const allRecs = recommendations.filter(r => r && typeof r.text === 'string');
|
|
734
751
|
for (const ar of autoRecs) {
|
|
735
752
|
if (!allRecs.some(r => r.text.toLowerCase().includes(ar.text.slice(0, 30).toLowerCase()))) {
|
|
736
753
|
allRecs.push(ar);
|
|
@@ -747,11 +764,10 @@ function generateReport(data) {
|
|
|
747
764
|
const sc = sevColor(sev);
|
|
748
765
|
const zhText = r.zh || r.text;
|
|
749
766
|
return `
|
|
750
|
-
<div class="flex items-center gap-3 px-4 py-3 rounded-lg
|
|
767
|
+
<div class="flex items-center gap-3 px-4 py-3 rounded-lg">
|
|
751
768
|
<span class="w-5 text-xs font-headline font-bold text-[#849588]/60">${i+1}</span>
|
|
752
769
|
<span class="px-2 py-0.5 rounded text-[9px] font-bold uppercase tracking-wide text-white shrink-0" style="background:${sc}">${sev}</span>
|
|
753
770
|
<span class="text-sm text-[#b9cbbd] leading-snug rec-text" data-en="${esc(r.text)}" data-zh="${esc(zhText)}">${esc(r.text)}</span>
|
|
754
|
-
<span class="material-symbols-outlined text-sm text-[#849588]/0 group-hover:text-[#849588]/50 transition-colors ml-auto shrink-0">chevron_right</span>
|
|
755
771
|
</div>`;
|
|
756
772
|
}).join('')}</div>`
|
|
757
773
|
: '<div class="text-center py-12 text-[#849588]" data-i18n="no_recs">No recommendations.</div>';
|
|
@@ -759,12 +775,7 @@ function generateReport(data) {
|
|
|
759
775
|
// ── AI Analysis report ──
|
|
760
776
|
const analysisText = data.analysis || '';
|
|
761
777
|
const analysisHtml = analysisText
|
|
762
|
-
? `<div class="
|
|
763
|
-
<div class="bg-[#0a0e14] border border-[#3a4a3f]/10 rounded-xl p-5 text-sm text-[#b9cbbd] leading-relaxed whitespace-pre-line" id="analysisText">${esc(analysisText)}</div>
|
|
764
|
-
<button onclick="copyReport()" id="copyBtn" class="absolute top-3 right-3 flex items-center gap-1.5 px-3 py-1.5 bg-[#262a31] border border-[#3a4a3f]/30 rounded-lg text-[11px] font-semibold text-[#849588] hover:text-[#dfe2eb] hover:border-[#849588]/50 transition-all opacity-0 group-hover:opacity-100">
|
|
765
|
-
<span class="material-symbols-outlined text-sm" id="copyIcon">content_copy</span><span id="copyLabel" data-i18n="copy_report">Copy Report</span>
|
|
766
|
-
</button>
|
|
767
|
-
</div>`
|
|
778
|
+
? `<div class="bg-[#0a0e14] border border-[#3a4a3f]/10 rounded-xl p-5 text-sm text-[#b9cbbd] leading-relaxed whitespace-pre-line" id="analysisText">${esc(analysisText)}</div>`
|
|
768
779
|
: '';
|
|
769
780
|
|
|
770
781
|
// ── Health status label ──
|
|
@@ -919,6 +930,9 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
|
|
|
919
930
|
<p class="text-[10px] font-label uppercase tracking-[0.2em] text-[#849588] mb-1" data-i18n="sec_analysis">Security Analysis</p>
|
|
920
931
|
<h1 class="text-2xl font-headline font-bold text-[#f5fff5] tracking-tight flex items-center gap-3">
|
|
921
932
|
<span class="material-symbols-outlined" style="color:${tier.color}">analytics</span><span data-i18n="diag_report">Diagnostic Report</span>
|
|
933
|
+
<button onclick="copyReport()" id="copyBtn" class="flex items-center gap-1 px-2 py-1 bg-[#262a31] border border-[#3a4a3f]/30 rounded-lg text-[11px] font-semibold text-[#849588] hover:text-[#dfe2eb] hover:border-[#849588]/50 transition-all ml-1">
|
|
934
|
+
<span class="material-symbols-outlined text-sm" id="copyIcon">content_copy</span><span id="copyLabel" class="hidden sm:inline" data-i18n="copy_report">Copy Report</span>
|
|
935
|
+
</button>
|
|
922
936
|
</h1>
|
|
923
937
|
</div>
|
|
924
938
|
<div class="bg-[#262a31] border border-[#3a4a3f]/15 rounded-lg px-4 py-2 flex items-center gap-2">
|
|
@@ -1036,7 +1050,7 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
|
|
|
1036
1050
|
i18n.zh.quote=quotes_zh['${tier.grade}']||quotes_zh.B;
|
|
1037
1051
|
i18n.en.quote='"${tier.quote.replace(/'/g,"\\'")}\"';
|
|
1038
1052
|
|
|
1039
|
-
let curLang='en';
|
|
1053
|
+
let curLang=(${JSON.stringify(data.analysis||'')}).match(/[\u4e00-\u9fff]/) ? 'zh' : 'en';
|
|
1040
1054
|
window.toggleLang=function(){
|
|
1041
1055
|
curLang=curLang==='en'?'zh':'en';
|
|
1042
1056
|
document.getElementById('langLabel').textContent=curLang==='en'?'中文':'EN';
|
|
@@ -1054,6 +1068,22 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
|
|
|
1054
1068
|
});
|
|
1055
1069
|
};
|
|
1056
1070
|
|
|
1071
|
+
// Apply initial language on load (auto-detect Chinese from analysis content)
|
|
1072
|
+
if(curLang==='zh'){
|
|
1073
|
+
document.getElementById('langLabel').textContent='EN';
|
|
1074
|
+
document.querySelectorAll('[data-i18n]').forEach(el=>{
|
|
1075
|
+
const key=el.getAttribute('data-i18n');
|
|
1076
|
+
if(key==='findings_range'){
|
|
1077
|
+
const range=el.getAttribute('data-range'),total=el.getAttribute('data-total');
|
|
1078
|
+
el.textContent='发现 — '+range+' / 共 '+total;
|
|
1079
|
+
} else if(i18n.zh[key]!=null)el.textContent=i18n.zh[key];
|
|
1080
|
+
});
|
|
1081
|
+
document.querySelectorAll('.finding-dim,.finding-text,.clean-dims,.rec-text').forEach(el=>{
|
|
1082
|
+
const t=el.getAttribute('data-zh');
|
|
1083
|
+
if(t)el.textContent=t;
|
|
1084
|
+
});
|
|
1085
|
+
}
|
|
1086
|
+
|
|
1057
1087
|
// Dimension data for share card (must be before shareReport)
|
|
1058
1088
|
const _dims=${JSON.stringify(Object.fromEntries(Object.entries(DIM_META).map(([k])=>[k,dimensions[k]||{score:null,na:false}])))};
|
|
1059
1089
|
|
|
@@ -1334,11 +1364,35 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
|
|
|
1334
1364
|
|
|
1335
1365
|
const outPath = join(tmpdir(), `agentguard-checkup-${Date.now()}.html`);
|
|
1336
1366
|
writeFileSync(outPath, html, 'utf8');
|
|
1337
|
-
console.log(outPath);
|
|
1338
1367
|
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1368
|
+
// Skip browser open for headless/bot environments (Qclaw, OpenClaw, CI)
|
|
1369
|
+
const isHeadless = process.env.OPENCLAW_STATE_DIR || process.env.QCLAW || process.env.CI;
|
|
1370
|
+
|
|
1371
|
+
// Flush stdout before doing anything else — on Windows/Linux in non-TTY/pipe
|
|
1372
|
+
// mode, console.log() is non-blocking and process.exit() can terminate before
|
|
1373
|
+
// the buffer is flushed, causing the caller (Claude) to receive an empty path.
|
|
1374
|
+
// Flush stdout before doing anything else — on Windows/Linux in non-TTY/pipe
|
|
1375
|
+
// mode, console.log() is non-blocking and process.exit() can terminate before
|
|
1376
|
+
// the buffer is flushed, causing the caller (Claude) to receive an empty path.
|
|
1377
|
+
process.stdout.write(outPath + '\n', () => {
|
|
1378
|
+
if (!isHeadless) {
|
|
1379
|
+
if (process.platform === 'win32') {
|
|
1380
|
+
// Use PowerShell Start-Process to open the file via Shell Execute API,
|
|
1381
|
+
// bypassing cmd.exe entirely — cmd /c start creates a visible intermediate
|
|
1382
|
+
// window whose title is the file path, which is the UX bug in #23.
|
|
1383
|
+
spawn('powershell', [
|
|
1384
|
+
'-NoProfile', '-WindowStyle', 'Hidden', '-Command',
|
|
1385
|
+
`Start-Process '${outPath.replace(/'/g, "''")}'`,
|
|
1386
|
+
], { detached: true, stdio: 'ignore', windowsHide: true }).unref();
|
|
1387
|
+
} else {
|
|
1388
|
+
const cmd = process.platform === 'darwin' ? 'open' : 'xdg-open';
|
|
1389
|
+
exec(`${cmd} "${outPath}"`, (err) => {
|
|
1390
|
+
if (err) process.stderr.write(`Could not open browser: ${err.message}\n`);
|
|
1391
|
+
});
|
|
1392
|
+
}
|
|
1393
|
+
}
|
|
1394
|
+
// Hard exit after 3s — guards against exec child process hanging and
|
|
1395
|
+
// blocking Node from exiting naturally (e.g. xdg-open on misconfigured Linux).
|
|
1396
|
+
setTimeout(() => process.exit(0), 3000).unref();
|
|
1342
1397
|
});
|
|
1343
|
-
setTimeout(() => process.exit(0), 2000);
|
|
1344
1398
|
}
|
|
File without changes
|