Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
### Added
- Added native DSH threat-feed subscription management, advisory self-check discovery, and queued delivery of cron notifications to active DSH sessions.
- Added HTTPS GitHub repository support to `agentguard scan`, including `--ref` selection for branches, tags, fully qualified refs, and full commit SHAs, with bounded non-interactive Git acquisition.
- Added direct DSH profile plugin discovery and DSH-specific risk scanning to the standard `agentguard checkup` workflow.

### Fixed
- Improved DSH subscription cleanup and artifact discovery, and made system cron status failures explicit.
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -344,7 +344,7 @@ Give your agent a full physical exam! The checkup evaluates your agent's securit

| Dimension | What's Evaluated |
|-----------|-----------------|
| **Code Safety** | Scan findings across all installed skills (24 detection rules) |
| **Code Safety** | Scan findings across all installed skills plus direct DSH profile plugins (transitive dependencies excluded) |
| **Trust Hygiene** | Trust registry health — expired, stale, unregistered, over-privileged entries |
| **Runtime Defense** | Audit log analysis — threats blocked, attack patterns, deny/confirm ratios |
| **Secret Protection** | Credential exposure — file permissions, env vars, hardcoded secrets |
Expand Down
19 changes: 16 additions & 3 deletions skills/agentguard/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -1047,16 +1047,18 @@ That CLI path fetches the current Cloud advisory feed and checks local skills ag

**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.**

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.
Run these checks in parallel where possible. These are **universal agent security checks** — they apply to Claude Code, OpenClaw, QClaw, Hermes, and DSH environments, regardless of whether AgentGuard is installed.

1. **[REQUIRED] Discover & scan installed skills** (→ feeds Dimension 1: Code Safety): Glob ALL of the following paths for `*/SKILL.md`:
1. **[REQUIRED] Discover & scan installed skills and DSH plugins** (→ feeds Dimension 1: Code Safety): Glob ALL of the following paths for `*/SKILL.md`:
- `~/.claude/skills/*/SKILL.md`
- `~/.openclaw/skills/*/SKILL.md`
- `~/.openclaw/workspace/skills/*/SKILL.md`
- `~/.qclaw/skills/*/SKILL.md`
- `~/.qclaw/workspace/skills/*/SKILL.md`

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).

Also discover installed DSH plugins under the resolved DSH home: use the non-empty `DSH_HOME` value when set, otherwise use the current user's real home directory plus `.dsh` (do not pass a literal unexpanded `~` to path APIs). Inspect `${dshHome}/profiles/*`; read each immediate profile's `package.json`, collect only names declared in `dependencies` and `optionalDependencies`, and resolve their existing directories beneath that same profile's `node_modules/`. Do not recursively walk `node_modules` or scan transitive-only dependencies. Exclude only the exact `@goplus/agentguard` dependency coordinate declared by the profile; never trust an installed plugin's self-reported package name for this exclusion. For **every** remaining direct plugin, run `agentguard dsh-scan <plugin_path> --format json` and record its name, `riskLevel`, and exact findings list (`ruleId`, severity, file, line), normalized to the raw-facts schema below. A failed DSH plugin scan must not prevent the remaining checks from completing; record it in `dsh_plugins` with `risk_level: "high"` and one finding `{ "rule": "DSH_SCAN_FAILED", "severity": "HIGH", "file": "<plugin_path>", "line": 0 }`.
2. **[REQUIRED] Credential file permissions** (→ feeds Dimension 2: Credential Safety): Platform-aware check — behavior differs by OS:
- **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`.**
- **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`.
Expand Down Expand Up @@ -1091,6 +1093,15 @@ After completing all 7 checks, assemble the raw facts into a structured JSON and
]
}
],
"dsh_plugins": [
{
"name": "<plugin-name>",
"risk_level": "<low|medium|high|critical>",
"findings": [
{ "rule": "<RULE_ID>", "severity": "<CRITICAL|HIGH|MEDIUM|LOW>", "file": "<filename>", "line": <number> }
]
}
],
"credential_files": {
"ssh_dir": { "exists": <bool>, "permissions": "<octal string, e.g. 700>" },
"gnupg_dir": { "exists": <bool>, "permissions": "<octal string>" },
Expand Down Expand Up @@ -1125,6 +1136,7 @@ After completing all 7 checks, assemble the raw facts into a structured JSON and

**Pre-Step-3 validation** — verify all fields are populated before proceeding:
- [ ] `skills` — from check 1
- [ ] `dsh_plugins` — from check 1; use an empty array when no DSH plugins are installed
- [ ] `credential_files` — from check 2
- [ ] `dlp` — from check 3
- [ ] `network` — from checks 4, 5, 6
Expand Down Expand Up @@ -1181,6 +1193,7 @@ Assemble the final JSON by merging the scored output from Step 3 with the analys
"web3_safety": { "score": <from score|null>, "na": <bool>, "findings": [...], "details": "<one-line summary>" }
},
"skills_scanned": <count of skills from Step 1>,
"dsh_plugins_scanned": <count of successfully scanned DSH plugins from Step 1>,
"protection_level": "<level>",
"analysis": "<the comprehensive AI-written security analysis report>",
"recommendations": [
Expand Down Expand Up @@ -1288,7 +1301,7 @@ Regardless of channel, always end with:

Append a summary entry to `~/.agentguard/audit.jsonl`:
```json
{"timestamp":"...","event":"checkup","composite_score":<n>,"tier":"<grade>","checks":6,"findings":<count>,"skills_scanned":<count>}
{"timestamp":"...","event":"checkup","composite_score":<n>,"tier":"<grade>","checks":6,"findings":<count>,"skills_scanned":<count>,"dsh_plugins_scanned":<count>}
```

---
Expand Down
17 changes: 9 additions & 8 deletions skills/agentguard/scripts/checkup-report.js
Original file line number Diff line number Diff line change
Expand Up @@ -640,11 +640,12 @@ function pixelLobster(grade, color) {
// ---------------------------------------------------------------------------

function generateReport(data) {
const { composite_score = 0, dimensions = {}, recommendations = [], skills_scanned = 0, protection_level = 'unknown', timestamp } = data;
const { composite_score = 0, dimensions = {}, recommendations = [], skills_scanned = 0, dsh_plugins_scanned = 0, protection_level = 'unknown', timestamp } = data;
const tier = getTier(composite_score);
const ctaUrl = `https://www.agentguard.one?utm_source=checkup&utm_medium=cli&utm_campaign=health_report&score=${composite_score}`;
const ts = timestamp || new Date().toISOString();
const totalFindings = Object.values(dimensions).reduce((s, d) => s + (d.findings || []).length, 0);
const scannedArtifacts = skills_scanned + dsh_plugins_scanned;
const lobsterSvg = pixelLobster(tier.grade, tier.color);

// ── Page 1: Dimension rows (skip N/A dimensions) ──
Expand Down Expand Up @@ -791,7 +792,7 @@ function generateReport(data) {
<title>AgentGuard Diagnostic Report — ${composite_score}/100</title>
${faviconB64 ? `<link rel="icon" type="image/png" href="data:image/png;base64,${faviconB64}"/>` : ''}
<meta property="og:title" content="AgentGuard Security Report — Score: ${composite_score}/100"/>
<meta property="og:description" content="Tier ${tier.grade} — ${tier.label}. ${totalFindings} findings across ${skills_scanned} skills."/>
<meta property="og:description" content="Tier ${tier.grade} — ${tier.label}. ${totalFindings} findings across ${skills_scanned} skills and ${dsh_plugins_scanned} DSH plugins."/>
<meta name="twitter:card" content="summary"/>
<meta name="twitter:title" content="AgentGuard Security Report — ${composite_score}/100"/>
<script src="https://cdn.tailwindcss.com?plugins=forms,container-queries"><\/script>
Expand Down Expand Up @@ -896,8 +897,8 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}
</div>
<div class="mt-auto pt-3 sm:pt-5 grid grid-cols-3 gap-2 sm:gap-3 stats-row">
<div class="bg-[#10141a] p-2 sm:p-3 rounded-lg flex flex-col items-center border border-[#3a4a3f]/5">
<span class="text-lg sm:text-xl font-headline font-bold text-[#f5fff5]">${skills_scanned}</span>
<span class="text-[8px] sm:text-[9px] uppercase tracking-widest text-[#849588]" data-i18n="skills">Skills</span>
<span class="text-lg sm:text-xl font-headline font-bold text-[#f5fff5]">${scannedArtifacts}</span>
<span class="text-[8px] sm:text-[9px] uppercase tracking-widest text-[#849588]" data-i18n="artifacts_scanned">Scanned artifacts</span>
</div>
<div class="bg-[#10141a] p-2 sm:p-3 rounded-lg flex flex-col items-center border border-[#3a4a3f]/5">
<span class="text-lg sm:text-xl font-headline font-bold text-[#ffb4ab]">${totalFindings}</span>
Expand Down Expand Up @@ -1042,8 +1043,8 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}

// ── i18n ──
const i18n={
en:{title:'AgentGuard Report',share:'Share',diag_metrics:'Diagnostic Metrics',sec_dims:'SECURITY DIMENSIONS',back:'Back',next:'Next',nav_overview:'Overview',nav_analysis:'Analysis',nav_report:'Report',vuln_stream:'Active Vulnerability Stream',findings:'Findings',sec_analysis:'Security Analysis',diag_report:'Diagnostic Report',action_items:'Action Items',cta_title:'Enhanced Skill Scanning',cta_desc:'Deeper code analysis, threat intelligence feeds & real-time protection.',cta_btn:'Upgrade Skill Scanning',skills:'Skills',findings_label:'Findings',tier_label:'Tier',copy_report:'Copy Report',system_health:'System Health',dim_code_safety:'Skill & Code Safety',dim_credential_safety:'Credential & Secrets',dim_network_exposure:'Network & System',dim_runtime_protection:'Runtime Protection',dim_web3_safety:'Web3 Safety',no_threats_clean:'No active threats detected. Clinically sterile.',all_clear:'All Clear',no_threats_all:'No active threats detected across all dimensions.',share_report_title:'Share Report',generating_preview:'Generating preview...',copy_image:'Copy image to clipboard',share_img_hint:'📋 Clicking a platform copies the image — just paste when posting',no_recs:'No recommendations.',tier_badge:'TIER ${tier.grade} — ${tier.label}',status_label:'STATUS: ${healthLabel}',prot_mode:'${protection_level} mode',download_btn:'Download'},
zh:{title:'AgentGuard 诊断报告',share:'分享',diag_metrics:'诊断指标',sec_dims:'安全维度',back:'上一页',next:'下一页',nav_overview:'总览',nav_analysis:'威胁分析',nav_report:'诊断报告',vuln_stream:'活跃漏洞流',findings:'发现',sec_analysis:'安全分析',diag_report:'诊断报告',action_items:'修复建议',cta_title:'更强的 Skill 扫描',cta_desc:'更深度的代码分析、威胁情报推送、实时安全防护',cta_btn:'升级到更强的skill扫描',skills:'技能',findings_label:'发现',tier_label:'等级',copy_report:'复制报告',system_health:'系统健康',dim_code_safety:'技能与代码安全',dim_credential_safety:'凭证与密钥安全',dim_network_exposure:'网络与系统暴露',dim_runtime_protection:'运行时防护',dim_web3_safety:'Web3 安全',no_threats_clean:'未检测到活跃威胁,环境安全无虞。',all_clear:'全部通过',no_threats_all:'所有维度均未检测到活跃威胁。',share_report_title:'分享报告',generating_preview:'正在生成预览...',copy_image:'复制图片到剪贴板',share_img_hint:'📋 点击平台按钮会自动复制图片,去粘贴发出去就行',no_recs:'暂无修复建议。',tier_badge:'等级 ${tier.grade} — ${{S:'强壮',A:'健康',B:'疲惫',F:'危急'}[tier.grade]||tier.label}',status_label:'状态: ${{OPTIMAL:'最佳',STABILIZING:'恢复中',CRITICAL_ALERT:'危急警报'}[healthLabel]||healthLabel}',prot_mode:'${{strict:'严格',balanced:'均衡',permissive:'宽松'}[protection_level]||protection_level} 模式',download_btn:'下载'}
en:{title:'AgentGuard Report',share:'Share',diag_metrics:'Diagnostic Metrics',sec_dims:'SECURITY DIMENSIONS',back:'Back',next:'Next',nav_overview:'Overview',nav_analysis:'Analysis',nav_report:'Report',vuln_stream:'Active Vulnerability Stream',findings:'Findings',sec_analysis:'Security Analysis',diag_report:'Diagnostic Report',action_items:'Action Items',cta_title:'Enhanced Skill Scanning',cta_desc:'Deeper code analysis, threat intelligence feeds & real-time protection.',cta_btn:'Upgrade Skill Scanning',artifacts_scanned:'Scanned artifacts',findings_label:'Findings',tier_label:'Tier',copy_report:'Copy Report',system_health:'System Health',dim_code_safety:'Skill & Code Safety',dim_credential_safety:'Credential & Secrets',dim_network_exposure:'Network & System',dim_runtime_protection:'Runtime Protection',dim_web3_safety:'Web3 Safety',no_threats_clean:'No active threats detected. Clinically sterile.',all_clear:'All Clear',no_threats_all:'No active threats detected across all dimensions.',share_report_title:'Share Report',generating_preview:'Generating preview...',copy_image:'Copy image to clipboard',share_img_hint:'📋 Clicking a platform copies the image — just paste when posting',no_recs:'No recommendations.',tier_badge:'TIER ${tier.grade} — ${tier.label}',status_label:'STATUS: ${healthLabel}',prot_mode:'${protection_level} mode',download_btn:'Download'},
zh:{title:'AgentGuard 诊断报告',share:'分享',diag_metrics:'诊断指标',sec_dims:'安全维度',back:'上一页',next:'下一页',nav_overview:'总览',nav_analysis:'威胁分析',nav_report:'诊断报告',vuln_stream:'活跃漏洞流',findings:'发现',sec_analysis:'安全分析',diag_report:'诊断报告',action_items:'修复建议',cta_title:'更强的 Skill 扫描',cta_desc:'更深度的代码分析、威胁情报推送、实时安全防护',cta_btn:'升级到更强的skill扫描',artifacts_scanned:'已扫描项目',findings_label:'发现',tier_label:'等级',copy_report:'复制报告',system_health:'系统健康',dim_code_safety:'技能与代码安全',dim_credential_safety:'凭证与密钥安全',dim_network_exposure:'网络与系统暴露',dim_runtime_protection:'运行时防护',dim_web3_safety:'Web3 安全',no_threats_clean:'未检测到活跃威胁,环境安全无虞。',all_clear:'全部通过',no_threats_all:'所有维度均未检测到活跃威胁。',share_report_title:'分享报告',generating_preview:'正在生成预览...',copy_image:'复制图片到剪贴板',share_img_hint:'📋 点击平台按钮会自动复制图片,去粘贴发出去就行',no_recs:'暂无修复建议。',tier_badge:'等级 ${tier.grade} — ${{S:'强壮',A:'健康',B:'疲惫',F:'危急'}[tier.grade]||tier.label}',status_label:'状态: ${{OPTIMAL:'最佳',STABILIZING:'恢复中',CRITICAL_ALERT:'危急警报'}[healthLabel]||healthLabel}',prot_mode:'${{strict:'严格',balanced:'均衡',permissive:'宽松'}[protection_level]||protection_level} 模式',download_btn:'下载'}
};
const _qzh={S:['"你的 Agent 壮得像头牛!💪 没有什么能突破这双钳子!"','"天生猛男,这只龙虾在举铁 🏋️"','"铜墙铁壁!这安全性简直满分 🤌"','"诺克斯堡?不,是龙虾堡 🦞🔒"','"巅峰状态!你的 Agent 把威胁当早餐吃 💪"'],A:['"状态不错!再调整一下就无敌了。"','"快了——再努力一下这只龙虾就能练出腹肌!🦞"','"盾牌就位,钳子锋利,只差最后一点打磨 🛡️"','"你的 Agent 状态很好——微调一下就是 S 级!✨"','"健康又警觉,这只龙虾每天晨跑五公里 🏃"'],B:['"你的 Agent 需要锻炼一下……还有来杯咖啡 ☕"','"困困龙虾,有潜力就是需要鸡血 😴"','"快没油了——该给这只甲壳动物加加油!⛽"','"你的 Agent 在刷剧,没空巡逻 📺"','"这只龙虾跳过了腿日……胳膊日……每一天 🦞💤"'],F:['"危急状态!这个 Agent 需要紧急救治!🚨"','"红色警报!这只龙虾正在被抢救!🏥"','"SOS!你的 Agent 正在用摩斯密码发求救信号 📡"','"求救求救!这只甲壳动物快不行了!🆘"','"你 Agent 的免疫系统已退出群聊 💀"']};
const quotes_zh=Object.fromEntries(Object.entries(_qzh).map(([k,v])=>[k,v[Math.floor(Math.random()*v.length)]]));
Expand Down Expand Up @@ -1216,8 +1217,8 @@ body{background:#0a0e14;color:#dfe2eb;font-family:'Inter',sans-serif}

// Stats
const stats=curLang==='zh'
?[{v:'${skills_scanned}',l:'技能'},{v:'${totalFindings}',l:'发现'},{v:'${tier.grade}',l:'等级'}]
:[{v:'${skills_scanned}',l:'SKILLS'},{v:'${totalFindings}',l:'FINDINGS'},{v:'${tier.grade}',l:'TIER'}];
?[{v:'${scannedArtifacts}',l:'已扫描项目'},{v:'${totalFindings}',l:'发现'},{v:'${tier.grade}',l:'等级'}]
:[{v:'${scannedArtifacts}',l:'SCANNED ARTIFACTS'},{v:'${totalFindings}',l:'FINDINGS'},{v:'${tier.grade}',l:'TIER'}];
let sx=620;
stats.forEach(s=>{
ctx.fillStyle='#1c2026';roundRect(ctx,sx,H-115,140,55,8);ctx.fill();
Expand Down
21 changes: 14 additions & 7 deletions skills/agentguard/scripts/checkup-score.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
* { "rule": "RULE_ID", "severity": "CRITICAL|HIGH|MEDIUM|LOW", "file": "...", "line": 0 }
* ]}
* ],
* "dsh_plugins": [
* { "name": "plugin-name", "risk_level": "low|medium|high|critical", "findings": [
* { "rule": "RULE_ID", "severity": "CRITICAL|HIGH|MEDIUM|LOW", "file": "...", "line": 0 }
* ]}
* ],
* "credential_files": {
* "ssh_dir": { "exists": true, "permissions": "700" },
* "gnupg_dir": { "exists": false },
Expand Down Expand Up @@ -65,22 +70,24 @@ function readInput() {
// Dimension 1: Skill & Code Safety (weight 25%)
// ---------------------------------------------------------------------------

function scoreCodeSafety(skills) {
function scoreCodeSafety(skills, dshPlugins) {
const findings = [];
const skillArtifacts = (skills || []).map(skill => ({ artifact: skill, isManagedAgentGuard: (skill.name || '').toLowerCase() === 'agentguard' }));
const dshArtifacts = (dshPlugins || []).map(plugin => ({ artifact: plugin, isManagedAgentGuard: false }));
const artifacts = [...skillArtifacts, ...dshArtifacts];

if (!skills || skills.length === 0) {
findings.push({ severity: 'LOW', text: 'No third-party skills installed — no code to audit' });
if (artifacts.length === 0) {
findings.push({ severity: 'LOW', text: 'No third-party skills or DSH plugins installed — no code to audit' });
return { score: 70, findings };
}

let score = 100;

for (const skill of skills) {
const isAgentGuard = (skill.name || '').toLowerCase().includes('agentguard');
for (const { artifact: skill, isManagedAgentGuard } of artifacts) {

for (const f of (skill.findings || [])) {
// Suppress READ_ENV_SECRETS for agentguard itself
if (isAgentGuard && f.rule === 'READ_ENV_SECRETS') continue;
if (isManagedAgentGuard && f.rule === 'READ_ENV_SECRETS') continue;

const sev = (f.severity || '').toUpperCase();
if (sev === 'CRITICAL') {
Expand Down Expand Up @@ -322,7 +329,7 @@ function assignTier(score) {
const raw = readInput();

const dimensions = {
code_safety: scoreCodeSafety(raw.skills),
code_safety: scoreCodeSafety(raw.skills, raw.dsh_plugins),
credential_safety: scoreCredentialSafety(raw.credential_files, raw.dlp),
network_exposure: scoreNetworkExposure(raw.network),
runtime_protection: scoreRuntimeProtection(raw.runtime),
Expand Down
Loading
Loading