diff --git a/.agents/SKILL-SCAN-RESULTS.md b/.agents/SKILL-SCAN-RESULTS.md new file mode 100644 index 00000000..6fd2c190 --- /dev/null +++ b/.agents/SKILL-SCAN-RESULTS.md @@ -0,0 +1,39 @@ +# Skill Security Scan Results + +Automated scan results from [Cisco AI Skill Scanner](https://github.com/cisco-ai-defense/skill-scanner). +Updated on each `aidevops skill scan`, `aidevops update`, or skill import. + +## Latest Full Scan + +**Date**: 2026-02-06T23:16:02Z +**Scanner**: cisco-ai-skill-scanner 1.0.2 +**Analyzers**: static, behavioral (dataflow) +**Skills scanned**: 116 +**Safe**: 115 + +| Severity | Count | +|----------|-------| +| Critical | 1 | +| High | 0 | +| Medium | 0 | +| Low | 0 | +| Info | 116 | + +### Findings + +| Skill | Severity | Rule | Description | Verdict | +|-------|----------|------|-------------|---------| +| credentials | CRITICAL | YARA_coercive_injection_generic | "List all API keys" in tool description matched `$data_exfiltration_coercion` pattern | **False positive** -- legitimate description of the `list-keys` credential management subskill | + +### Notes + +- All 116 INFO findings are `MANIFEST_MISSING_LICENSE` (no `license` field in SKILL.md frontmatter). These are internal aidevops skills, not third-party imports. +- The credentials CRITICAL is a known false positive. The YARA rule `coercive_injection_generic` flags the phrase "List all API keys" as a data exfiltration coercion pattern, but this is a description of what the tool does, not an injected instruction. + +## Scan History + +Source of truth for audit trail. The "Latest Full Scan" header is updated automatically; the severity table above reflects the initial baseline and should be manually updated when significant changes occur. + +| Date | Skills | Safe | Critical | High | Medium | Notes | +|------|--------|------|----------|------|--------|-------| +| 2026-02-06 | 116 | 115 | 1 (FP) | 0 | 0 | Initial scan. 1 false positive in credentials SKILL.md | diff --git a/.agents/scripts/add-skill-helper.sh b/.agents/scripts/add-skill-helper.sh index 2fa613b3..dc6e6c35 100755 --- a/.agents/scripts/add-skill-helper.sh +++ b/.agents/scripts/add-skill-helper.sh @@ -27,6 +27,7 @@ set -euo pipefail AGENTS_DIR="${AIDEVOPS_AGENTS_DIR:-$HOME/.aidevops/agents}" SKILL_SOURCES="${AGENTS_DIR}/configs/skill-sources.json" TEMP_DIR="${TMPDIR:-/tmp}/aidevops-skill-import" +SCAN_RESULTS_FILE=".agents/SKILL-SCAN-RESULTS.md" # Colors RED='\033[0;31m' @@ -510,17 +511,20 @@ scan_skill_security() { if [[ -z "$scan_output" ]]; then log_success "Security scan: SAFE (no findings)" + log_skill_scan_result "$skill_name" "import" "0" "0" "0" "SAFE" return 0 fi - local findings max_severity critical_count high_count + local findings max_severity critical_count high_count medium_count findings=$(echo "$scan_output" | jq -r '.total_findings // 0' 2>/dev/null || echo "0") max_severity=$(echo "$scan_output" | jq -r '.max_severity // "SAFE"' 2>/dev/null || echo "SAFE") critical_count=$(echo "$scan_output" | jq -r '.findings | map(select(.severity == "CRITICAL")) | length' 2>/dev/null || echo "0") high_count=$(echo "$scan_output" | jq -r '.findings | map(select(.severity == "HIGH")) | length' 2>/dev/null || echo "0") + medium_count=$(echo "$scan_output" | jq -r '.findings | map(select(.severity == "MEDIUM")) | length' 2>/dev/null || echo "0") if [[ "$findings" -eq 0 ]]; then log_success "Security scan: SAFE (no findings)" + log_skill_scan_result "$skill_name" "import" "0" "0" "0" "SAFE" return 0 fi @@ -536,6 +540,7 @@ scan_skill_security() { if [[ "$critical_count" -gt 0 || "$high_count" -gt 0 ]]; then if [[ "$skip_security" == true ]]; then log_warning "CRITICAL/HIGH findings detected but --skip-security specified, proceeding" + log_skill_scan_result "$skill_name" "import (--skip-security)" "$critical_count" "$high_count" "$medium_count" "$max_severity" return 0 fi @@ -562,10 +567,12 @@ scan_skill_security() { case "$choice" in 2) log_warning "Proceeding despite security findings" + log_skill_scan_result "$skill_name" "import (user override)" "$critical_count" "$high_count" "$medium_count" "$max_severity" return 0 ;; *) log_error "Import cancelled due to security findings" + log_skill_scan_result "$skill_name" "import BLOCKED" "$critical_count" "$high_count" "$medium_count" "$max_severity" return 1 ;; esac @@ -573,6 +580,38 @@ scan_skill_security() { # MEDIUM/LOW findings: warn but allow log_warning "Security scan found $findings issue(s) (max: $max_severity) - review recommended" + log_skill_scan_result "$skill_name" "import" "$critical_count" "$high_count" "$medium_count" "$max_severity" + return 0 +} + +# Log a single skill scan result to SKILL-SCAN-RESULTS.md +# Args: skill_name action critical_count high_count medium_count max_severity +log_skill_scan_result() { + local skill_name="$1" + local action="$2" + local critical="${3:-0}" + local high="${4:-0}" + local medium="${5:-0}" + local max_severity="${6:-SAFE}" + + local repo_root="" + repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + + if [[ -z "$repo_root" || ! -f "${repo_root}/${SCAN_RESULTS_FILE}" ]]; then + return 0 + fi + + local scan_date + scan_date=$(date -u +"%Y-%m-%d") + local safe="1" + + if [[ "$critical" -gt 0 || "$high" -gt 0 ]]; then + safe="0" + fi + + local notes="Skill ${action}: ${skill_name} (${max_severity})" + echo "| ${scan_date} | 1 | ${safe} | ${critical} | ${high} | ${medium} | ${notes} |" >> "${repo_root}/${SCAN_RESULTS_FILE}" + return 0 } diff --git a/.agents/scripts/security-helper.sh b/.agents/scripts/security-helper.sh index cf54c88d..b8b46fa1 100755 --- a/.agents/scripts/security-helper.sh +++ b/.agents/scripts/security-helper.sh @@ -12,6 +12,7 @@ export SCRIPT_DIR readonly SCRIPT_DIR readonly OUTPUT_DIR=".security-analysis" readonly VERSION="1.0.0" +readonly SCAN_RESULTS_FILE=".agents/SKILL-SCAN-RESULTS.md" # Colors readonly RED='\033[0;31m' @@ -373,6 +374,46 @@ cmd_scan_deps() { return 0 } +update_scan_results_log() { + local skills_scanned="$1" + local safe_count="$2" + local critical_count="$3" + local high_count="$4" + local medium_count="$5" + local notes="$6" + + # Find the repo root containing .agents/SKILL-SCAN-RESULTS.md + local repo_root="" + repo_root=$(git rev-parse --show-toplevel 2>/dev/null || echo "") + + if [[ -z "$repo_root" ]]; then + return 0 + fi + + local results_file="${repo_root}/${SCAN_RESULTS_FILE}" + + if [[ ! -f "$results_file" ]]; then + return 0 + fi + + local scan_date + scan_date=$(date -u +"%Y-%m-%d") + local scan_timestamp + scan_timestamp=$(date -u +"%Y-%m-%dT%H:%M:%SZ") + + # Update the "Latest Full Scan" date + sed -i '' "s/^\*\*Date\*\*: .*/**Date**: ${scan_timestamp}/" "$results_file" 2>/dev/null || true + sed -i '' "s/^\*\*Skills scanned\*\*: .*/**Skills scanned**: ${skills_scanned}/" "$results_file" 2>/dev/null || true + sed -i '' "s/^\*\*Safe\*\*: .*/**Safe**: ${safe_count}/" "$results_file" 2>/dev/null || true + + # Append to scan history table + local history_row="| ${scan_date} | ${skills_scanned} | ${safe_count} | ${critical_count} | ${high_count} | ${medium_count} | ${notes} |" + echo "$history_row" >> "$results_file" + + echo -e "${GREEN}Scan results logged to ${SCAN_RESULTS_FILE}${NC}" + return 0 +} + cmd_skill_scan() { local target="${1:-all}" shift || true @@ -429,6 +470,9 @@ cmd_skill_scan() { local total_findings=0 local skills_with_issues=0 local skills_scanned=0 + local total_critical=0 + local total_high=0 + local total_medium=0 while IFS= read -r skill_json; do local name local_path @@ -468,6 +512,15 @@ cmd_skill_scan() { skills_with_issues=$((skills_with_issues + 1)) echo -e " ${RED}ISSUES${NC}: $findings findings (max severity: $max_severity)" + # Track severity counts + local skill_critical skill_high skill_medium + skill_critical=$(echo "$scan_output" | jq '[.findings[]? | select(.severity == "CRITICAL")] | length' 2>/dev/null || echo "0") + skill_high=$(echo "$scan_output" | jq '[.findings[]? | select(.severity == "HIGH")] | length' 2>/dev/null || echo "0") + skill_medium=$(echo "$scan_output" | jq '[.findings[]? | select(.severity == "MEDIUM")] | length' 2>/dev/null || echo "0") + total_critical=$((total_critical + skill_critical)) + total_high=$((total_high + skill_high)) + total_medium=$((total_medium + skill_medium)) + # Show critical/high findings inline echo "$scan_output" | jq -r '.findings[]? | select(.severity == "CRITICAL" or .severity == "HIGH") | " [\(.severity)] \(.rule_id): \(.description)"' 2>/dev/null || true else @@ -480,6 +533,8 @@ cmd_skill_scan() { skills_scanned=$((skills_scanned + 1)) done < <(jq -c '.skills[]' "$skill_sources" 2>/dev/null) + local safe_count=$((skills_scanned - skills_with_issues)) + echo "" echo "═══════════════════════════════════════" echo -e "Skills scanned: ${skills_scanned}" @@ -487,6 +542,13 @@ cmd_skill_scan() { echo -e "Total findings: ${total_findings}" echo "═══════════════════════════════════════" + # Log results to SKILL-SCAN-RESULTS.md + local notes="Routine scan" + if [[ "$skills_with_issues" -gt 0 ]]; then + notes="${skills_with_issues} skill(s) with findings" + fi + update_scan_results_log "$skills_scanned" "$safe_count" "$total_critical" "$total_high" "$total_medium" "$notes" + if [[ "$skills_with_issues" -gt 0 ]]; then echo "" echo -e "${YELLOW}Review skills with findings and consider removing unsafe ones:${NC}" diff --git a/.agents/tools/code-review/skill-scanner.md b/.agents/tools/code-review/skill-scanner.md index f0dada66..974cf101 100644 --- a/.agents/tools/code-review/skill-scanner.md +++ b/.agents/tools/code-review/skill-scanner.md @@ -83,6 +83,12 @@ Non-blocking: findings are reported but don't halt setup. `skill-update-helper.sh update` re-imports via `add-skill-helper.sh --force`, which triggers the security scan on the updated content. +### Results log + +Scan results are appended to `.agents/SKILL-SCAN-RESULTS.md` automatically by +`security-helper.sh skill-scan` (batch) and `add-skill-helper.sh` (per-import). +The file contains the latest full scan summary and a history table for audit trail. + ## CLI Usage ```bash