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
4 changes: 2 additions & 2 deletions .agents/scripts/add-skill-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -585,7 +585,7 @@ scan_skill_security() {
}

# Run VirusTotal scan on skill files and referenced domains
# Returns: 0 = safe or VT not configured, 1 = threats detected
# Returns: 0 (always, as VT scans are advisory; Cisco scanner is the gate)
scan_skill_virustotal() {
local scan_path="$1"
local skill_name="$2"
Expand All @@ -612,7 +612,7 @@ scan_skill_virustotal() {

if ! "$vt_helper" scan-skill "$scan_path" --quiet; then
log_warning "VirusTotal flagged potential threats in '$skill_name'"
log_info "Run: virustotal-helper.sh scan-skill '$scan_path' for details"
log_info "Run: $vt_helper scan-skill '$scan_path' for details"
# VT findings are advisory, not blocking (Cisco scanner is the gate)
return 0
fi
Expand Down
8 changes: 5 additions & 3 deletions .agents/scripts/security-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -594,10 +594,12 @@ cmd_skill_scan() {
continue
fi

local scan_dir
scan_dir="$(dirname "$full_path")"
echo -e "${CYAN}VT Scanning${NC}: $name"
"$vt_helper" scan-skill "$scan_dir" --quiet 2>/dev/null || {
local scan_target="$full_path"
if [[ -d "${full_path%.*}" ]]; then
scan_target="${full_path%.*}"
fi
"$vt_helper" scan-skill "$scan_target" --quiet 2>/dev/null || {
vt_issues=$((vt_issues + 1))
echo -e " ${YELLOW}VT flagged issues${NC} for $name"
}
Expand Down
76 changes: 50 additions & 26 deletions .agents/scripts/virustotal-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,25 +38,25 @@ readonly NC='\033[0m'

log_info() {
local msg="$1"
echo -e "${BLUE}[virustotal]${NC} ${msg}"
echo -e "${BLUE}[virustotal]${NC} ${msg}" >&2
return 0
}

log_success() {
local msg="$1"
echo -e "${GREEN}[OK]${NC} ${msg}"
echo -e "${GREEN}[OK]${NC} ${msg}" >&2
return 0
}

log_warning() {
local msg="$1"
echo -e "${YELLOW}[WARN]${NC} ${msg}"
echo -e "${YELLOW}[WARN]${NC} ${msg}" >&2
return 0
}

log_error() {
local msg="$1"
echo -e "${RED}[ERROR]${NC} ${msg}"
echo -e "${RED}[ERROR]${NC} ${msg}" >&2
return 0
}

Expand Down Expand Up @@ -173,12 +173,16 @@ vt_request() {
}

# Check for API errors
# Exit codes: 0=success, 1=general error, 2=not found (allows callers to distinguish)
local error_code=""
error_code=$(echo "$response" | jq -r '.error.code // empty' 2>/dev/null || echo "")
if [[ -n "$error_code" ]]; then
local error_msg=""
error_msg=$(echo "$response" | jq -r '.error.message // "Unknown error"' 2>/dev/null || echo "Unknown error")
log_error "VT API error: ${error_code} - ${error_msg}"
if [[ "$error_code" == "NotFoundError" ]]; then
return 2
fi
return 1
fi

Expand Down Expand Up @@ -253,8 +257,11 @@ cmd_scan_file() {
fi

local response=""
response=$(vt_request "GET" "/files/${sha256}") || {
# File not in VT database -- this is normal for text/markdown files
local vt_exit=0
response=$(vt_request "GET" "/files/${sha256}") || vt_exit=$?

if [[ $vt_exit -eq 2 ]]; then
# NotFoundError: file not in VT database -- normal for text/markdown files
if [[ "$quiet" == "true" ]]; then
echo "UNKNOWN"
else
Expand All @@ -263,7 +270,15 @@ cmd_scan_file() {
fi
echo "UNKNOWN|File not in VT database"
return 0
}
elif [[ $vt_exit -ne 0 ]]; then
# Real API error (quota exceeded, network failure, etc.)
if [[ "$quiet" == "true" ]]; then
echo "UNKNOWN"
else
log_error "API request failed for file: ${file}"
fi
return 1
fi

if [[ "$output_json" == "true" ]]; then
echo "$response"
Expand Down Expand Up @@ -502,15 +517,21 @@ cmd_scan_skill() {
sha256=$(file_sha256 "$file") || continue

local response=""
response=$(vt_request "GET" "/files/${sha256}" 2>/dev/null) || {
# Not in VT database -- normal for text files
local vt_exit=0
response=$(vt_request "GET" "/files/${sha256}" 2>/dev/null) || vt_exit=$?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The 2>/dev/null here suppresses the detailed API error messages that vt_request logs to stderr. Since a major goal of this PR is to improve logging and error handling, it would be better to let these errors be displayed. This would provide more context when an API error occurs, rather than just the generic "API error" message printed later. The caller can still choose to suppress stderr if needed.

Suggested change
response=$(vt_request "GET" "/files/${sha256}" 2>/dev/null) || vt_exit=$?
response=$(vt_request "GET" "/files/${sha256}") || vt_exit=$?

request_count=$((request_count + 1))

if [[ $vt_exit -ne 0 ]]; then
if [[ "$quiet" != "true" ]]; then
echo -e " ${BLUE}SKIP${NC} ${basename_file} (not in VT database)"
if [[ $vt_exit -eq 2 ]]; then
echo -e " ${BLUE}SKIP${NC} ${basename_file} (not in VT database)"
else
echo -e " ${YELLOW}SKIP${NC} ${basename_file} (API error)"
fi
fi
unknown_count=$((unknown_count + 1))
continue
}
request_count=$((request_count + 1))
fi

local verdict=""
verdict=$(parse_verdict "$response")
Expand Down Expand Up @@ -551,28 +572,20 @@ cmd_scan_skill() {
done

# Phase 2: Extract and scan URLs from skill content
local -A domains_seen=()
for file in "${files[@]}"; do
# Extract URLs (http/https) from file content
while IFS= read -r url; do
# Skip common safe domains
case "$url" in
*github.com*|*githubusercontent.com*|*npmjs.com*|*pypi.org*|*docs.virustotal.com*)
continue
;;
esac
urls_found+=("$url")

# Extract domain
local domain=""
domain=$(echo "$url" | sed -E 's|https?://([^/]+).*|\1|')
local already_found=false
for d in "${domains_found[@]+"${domains_found[@]}"}"; do
if [[ "$d" == "$domain" ]]; then
already_found=true
break
fi
done
if [[ "$already_found" == "false" ]]; then
if [[ -z "${domains_seen[$domain]+x}" ]]; then
domains_seen[$domain]=1
domains_found+=("$domain")
fi
done < <(grep -oE 'https?://[^ "'"'"'<>]+' "$file" 2>/dev/null | sort -u || true)
Expand All @@ -594,13 +607,16 @@ cmd_scan_skill() {
rate_limit_wait

local response=""
response=$(vt_request "GET" "/domains/${domain}" 2>/dev/null) || {
local vt_exit=0
response=$(vt_request "GET" "/domains/${domain}" 2>/dev/null) || vt_exit=$?
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Similar to the file scan, the 2>/dev/null here suppresses detailed error messages from vt_request. Removing it would improve debuggability by showing the actual API error when a domain lookup fails for reasons other than "not found".

Suggested change
response=$(vt_request "GET" "/domains/${domain}" 2>/dev/null) || vt_exit=$?
response=$(vt_request "GET" "/domains/${domain}") || vt_exit=$?

request_count=$((request_count + 1))

if [[ $vt_exit -ne 0 ]]; then
if [[ "$quiet" != "true" ]]; then
echo -e " ${BLUE}SKIP${NC} ${domain} (lookup failed)"
fi
continue
}
request_count=$((request_count + 1))
fi

local verdict=""
verdict=$(parse_verdict "$response")
Expand Down Expand Up @@ -748,6 +764,14 @@ main() {
local command="${1:-help}"
shift || true

# Verify required dependencies (jq is needed for all scan commands)
if [[ "$command" != "help" && "$command" != "--help" && "$command" != "-h" ]]; then
if ! command -v jq &>/dev/null; then
log_error "jq is required but not installed (brew install jq)"
return 1
fi
fi

# Parse global flags
local output_json=false
local quiet=false
Expand Down
5 changes: 2 additions & 3 deletions TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,7 @@ Tasks with no open blockers - ready to work on. Use `/ready` to refresh this lis
- [ ] t147 Retroactive triage: 50 unresolved review threads across 11 merged PRs #quality #review ~4h (ai:3h test:30m read:30m) logged:2026-02-07 ref:GH#438
- [x] t147.1 Triage PR #435 (4 threads, 2 high/critical) - fix $SUPERVISOR_DB bug ~30m blocked-by:none completed:2026-02-07
- [x] t147.2 Triage PR #392 (6 threads, 4 high/critical) - stderr suppression ~45m blocked-by:none completed:2026-02-07
- [ ] t147.3 Triage PR #410 (9 threads, 3 high/critical) - VirusTotal error handling ~45m blocked-by:none
- Notes: BLOCKED by supervisor: Max retries exceeded: backend_infrastructure_error
- [ ] t147.3 Triage PR #410 (9 threads, 3 high/critical) - VirusTotal error handling ~45m blocked-by:none started:2026-02-07
- [x] t147.4 Triage PR #391 (1 thread, 1 critical) - schema-validator set -e ~15m blocked-by:none completed:2026-02-07
- [ ] t147.5 Triage PR #406 (3 threads, 1 high) - sed -i portability, attribution ~30m blocked-by:none
- [ ] t147.6 Triage PR #403 (12 threads, 1 high) - voice AI unimplemented commands ~30m blocked-by:none
Expand Down Expand Up @@ -139,7 +138,7 @@ Tasks with no open blockers - ready to work on. Use `/ready` to refresh this lis
- [x] t135.7.2 Replace with array-based command construction ~2h blocked-by:t135.7.1 completed:2026-02-07
- [x] t135.7.3 Test affected command paths ~30m blocked-by:t135.7.2 completed:2026-02-07
- [ ] t135.8 P2-B: Increase shared-constants.sh adoption from 17% (29/170) to 80%+ ~4h blocked-by:none
- Notes: BLOCKED by supervisor: Re-prompt dispatch failed: backend_infrastructure_error - [ ] t135.8.1 Audit shared-constants.sh vs what scripts duplicate ~30m
- Notes: BLOCKED by supervisor: Re-prompt dispatch failed: backend_infrastructure_error - [ ] t135.8.1 Audit shared-constants.sh vs what scripts duplicate ~30m BLOCKED: Max retries exceeded: backend_infrastructure_error
- [ ] t135.8.2 Create migration script to replace inline print_* with source shared-constants.sh ~1.5h blocked-by:t135.8.1
- [ ] t135.8.3 Run migration in batches, testing each for regressions ~2h blocked-by:t135.8.2
- [x] t135.9 P2-C: Add trap cleanup for temp files in setup.sh and mktemp scripts ~1h blocked-by:none started:2026-02-07 completed:2026-02-07
Expand Down
Loading