Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 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
116 changes: 61 additions & 55 deletions .agents/scripts/security-posture-helper.sh
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,12 @@
# PER-REPO AUDIT (t1412.11)
# ============================================================

# Severity level constants (SonarCloud: avoid repeated string literals)
readonly SEVERITY_CRITICAL="critical"
readonly SEVERITY_WARNING="warning"
readonly SEVERITY_INFO="info"
readonly SEVERITY_PASS="pass"

# Counters
FINDINGS_CRITICAL=0
FINDINGS_WARNING=0
Expand Down Expand Up @@ -120,7 +126,7 @@

if [[ ! -d "$workflows_dir" ]]; then
print_skip "No .github/workflows/ directory found"
add_finding "info" "workflows" "No .github/workflows/ directory"
add_finding "$SEVERITY_INFO" "workflows" "No .github/workflows/ directory"
return 0
fi

Expand All @@ -129,7 +135,7 @@

if [[ -z "$workflow_files" ]]; then
print_skip "No workflow files found"
add_finding "info" "workflows" "No workflow YAML files found"
add_finding "$SEVERITY_INFO" "workflows" "No workflow YAML files found"
return 0
fi

Expand All @@ -149,15 +155,15 @@
if grep -qE '\$\{\{\s*github\.event\.(issue|pull_request|comment)\.' "$wf" 2>/dev/null; then
if grep -qE '^\s*run:' "$wf" 2>/dev/null; then
print_crit "$wf_name: Uses github.event context in shell run step (injection risk)"
add_finding "critical" "workflows" "$wf_name: github.event context in shell run step"
add_finding "$SEVERITY_CRITICAL" "workflows" "$wf_name: github.event context in shell run step"
unsafe_found=true
fi
fi

# Check 2: Overly permissive allowed_non_write_users or permissions
if grep -qE 'allowed_non_write_users:\s*"\*"' "$wf" 2>/dev/null; then
print_crit "$wf_name: allowed_non_write_users set to wildcard '*'"
add_finding "critical" "workflows" "$wf_name: allowed_non_write_users wildcard"
add_finding "$SEVERITY_CRITICAL" "workflows" "$wf_name: allowed_non_write_users wildcard"
unsafe_found=true
fi

Expand All @@ -166,24 +172,24 @@
if grep -qE 'pull_request_target' "$wf" 2>/dev/null; then
if grep -qE 'ref:\s*\$\{\{\s*github\.event\.pull_request\.head\.(ref|sha)' "$wf" 2>/dev/null; then
print_crit "$wf_name: pull_request_target with PR head checkout (pwn request risk)"
add_finding "critical" "workflows" "$wf_name: pull_request_target with PR head checkout"
add_finding "$SEVERITY_CRITICAL" "workflows" "$wf_name: pull_request_target with PR head checkout"
unsafe_found=true
else
print_warn "$wf_name: Uses pull_request_target (review checkout refs carefully)"
add_finding "warning" "workflows" "$wf_name: uses pull_request_target"
add_finding "$SEVERITY_WARNING" "workflows" "$wf_name: uses pull_request_target"
fi
fi

# Check 4: Long-lived tokens cached or stored in artifacts
if grep -qE 'actions/cache.*token|save-state.*token|GITHUB_TOKEN.*>>.*GITHUB_ENV' "$wf" 2>/dev/null; then
print_warn "$wf_name: Possible token caching pattern detected"
add_finding "warning" "workflows" "$wf_name: possible token caching"
add_finding "$SEVERITY_WARNING" "workflows" "$wf_name: possible token caching"
fi

# Check 5: Overly broad permissions
if grep -qE '^\s*permissions:\s*write-all' "$wf" 2>/dev/null; then
print_warn "$wf_name: Uses write-all permissions (prefer least-privilege)"
add_finding "warning" "workflows" "$wf_name: write-all permissions"
add_finding "$SEVERITY_WARNING" "workflows" "$wf_name: write-all permissions"
fi

# Check 6: Third-party actions pinned to branch instead of SHA
Expand All @@ -193,14 +199,14 @@
local unpinned_count
unpinned_count=$(echo "$unpinned_actions" | wc -l | tr -d ' ')
print_warn "$wf_name: $unpinned_count action(s) pinned to branch/tag instead of SHA"
add_finding "warning" "workflows" "$wf_name: $unpinned_count actions not SHA-pinned"
add_finding "$SEVERITY_WARNING" "workflows" "$wf_name: $unpinned_count actions not SHA-pinned"
fi

done <<<"$workflow_files"

if [[ "$unsafe_found" == "false" ]]; then
print_pass "No critical workflow security issues found"
add_finding "pass" "workflows" "No critical issues"
add_finding "$SEVERITY_PASS" "workflows" "No critical issues"

Check warning on line 209 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'workflows' 10 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEW&open=AZzQXdV4C1LEfgiIqEEW&pullRequest=3628
fi

return 0
Expand All @@ -215,14 +221,14 @@
# Need gh CLI and a GitHub remote
if ! command -v gh &>/dev/null; then
print_skip "GitHub CLI (gh) not installed — cannot check branch protection"
add_finding "info" "branch_protection" "gh CLI not available"
add_finding "$SEVERITY_INFO" "branch_protection" "gh CLI not available"
return 0
fi

local slug
if ! slug=$(resolve_slug "$repo_path"); then
print_skip "No GitHub remote — branch protection check skipped"
add_finding "info" "branch_protection" "No GitHub remote"
add_finding "$SEVERITY_INFO" "branch_protection" "No GitHub remote"
return 0
fi

Expand All @@ -239,7 +245,7 @@

if [[ -z "$protection_json" || "$protection_json" == *"Not Found"* || "$protection_json" == *"Branch not protected"* ]]; then
print_crit "Default branch '$default_branch' has NO branch protection"
add_finding "critical" "branch_protection" "No branch protection on $default_branch"
add_finding "$SEVERITY_CRITICAL" "branch_protection" "No branch protection on $default_branch"
return 0
fi

Expand All @@ -249,10 +255,10 @@

if [[ "$required_reviews" -gt 0 ]]; then
print_pass "PR reviews required ($required_reviews approving review(s))"
add_finding "pass" "branch_protection" "PR reviews required: $required_reviews"
add_finding "$SEVERITY_PASS" "branch_protection" "PR reviews required: $required_reviews"
else
print_warn "PR reviews not required on $default_branch"
add_finding "warning" "branch_protection" "PR reviews not required"
add_finding "$SEVERITY_WARNING" "branch_protection" "PR reviews not required"
fi

# Check: require status checks
Expand All @@ -261,10 +267,10 @@

if [[ "$required_checks" -gt 0 ]]; then
print_pass "Required status checks configured ($required_checks check(s))"
add_finding "pass" "branch_protection" "Status checks configured: $required_checks"
add_finding "$SEVERITY_PASS" "branch_protection" "Status checks configured: $required_checks"
else
print_warn "No required status checks on $default_branch"
add_finding "warning" "branch_protection" "No required status checks"
add_finding "$SEVERITY_WARNING" "branch_protection" "No required status checks"
fi

# Check: enforce for admins
Expand All @@ -273,10 +279,10 @@

if [[ "$enforce_admins" == "true" ]]; then
print_pass "Branch protection enforced for admins"
add_finding "pass" "branch_protection" "Enforced for admins"
add_finding "$SEVERITY_PASS" "branch_protection" "Enforced for admins"
else
print_info "Branch protection not enforced for admins (common for solo repos)"
add_finding "info" "branch_protection" "Not enforced for admins"
add_finding "$SEVERITY_INFO" "branch_protection" "Not enforced for admins"

Check warning on line 285 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'branch_protection' 9 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEX&open=AZzQXdV4C1LEfgiIqEEX&pullRequest=3628
fi

return 0
Expand All @@ -292,10 +298,10 @@
local gate_workflow="$repo_path/.github/workflows/review-bot-gate.yml"
if [[ -f "$gate_workflow" ]]; then
print_pass "review-bot-gate.yml workflow present"
add_finding "pass" "review_bot_gate" "Workflow file present"
add_finding "$SEVERITY_PASS" "review_bot_gate" "Workflow file present"
else
print_warn "No review-bot-gate.yml workflow — AI review bots may not be gated"
add_finding "warning" "review_bot_gate" "No review-bot-gate.yml workflow"
add_finding "$SEVERITY_WARNING" "review_bot_gate" "No review-bot-gate.yml workflow"
print_info " Suggestion: Copy from aidevops repo or add review-bot-gate as required check"
fi

Expand All @@ -318,11 +324,11 @@

if echo "$check_contexts" | grep -q "review-bot-gate" 2>/dev/null; then
print_pass "review-bot-gate is a required status check"
add_finding "pass" "review_bot_gate" "Required status check configured"
add_finding "$SEVERITY_PASS" "review_bot_gate" "Required status check configured"
else
if [[ -f "$gate_workflow" ]]; then
print_warn "review-bot-gate workflow exists but is not a required status check"
add_finding "warning" "review_bot_gate" "Not configured as required status check"
add_finding "$SEVERITY_WARNING" "review_bot_gate" "Not configured as required status check"

Check warning on line 331 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'review_bot_gate' 4 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEY&open=AZzQXdV4C1LEfgiIqEEY&pullRequest=3628
print_info " Add 'review-bot-gate' to branch protection required checks"
fi
fi
Expand Down Expand Up @@ -358,23 +364,23 @@

if [[ "$vuln_critical" -gt 0 ]]; then
print_crit "npm audit: $vuln_critical critical, $vuln_high high ($vuln_total total vulnerabilities)"
add_finding "critical" "dependencies" "npm: $vuln_critical critical, $vuln_high high vulnerabilities"
add_finding "$SEVERITY_CRITICAL" "dependencies" "npm: $vuln_critical critical, $vuln_high high vulnerabilities"
elif [[ "$vuln_high" -gt 0 ]]; then
print_warn "npm audit: $vuln_high high ($vuln_total total vulnerabilities)"
add_finding "warning" "dependencies" "npm: $vuln_high high vulnerabilities"
add_finding "$SEVERITY_WARNING" "dependencies" "npm: $vuln_high high vulnerabilities"
elif [[ "$vuln_total" -gt 0 ]]; then
print_info "npm audit: $vuln_total low/moderate vulnerabilities"
add_finding "info" "dependencies" "npm: $vuln_total low/moderate vulnerabilities"
add_finding "$SEVERITY_INFO" "dependencies" "npm: $vuln_total low/moderate vulnerabilities"
else
print_pass "npm audit: no known vulnerabilities"
add_finding "pass" "dependencies" "npm: clean audit"
add_finding "$SEVERITY_PASS" "dependencies" "npm: clean audit"
fi
else
print_skip "npm audit returned no output"
fi
elif [[ ! -f "$repo_path/package-lock.json" ]]; then
print_warn "package.json exists but no package-lock.json — cannot run npm audit"
add_finding "warning" "dependencies" "Missing package-lock.json"
add_finding "$SEVERITY_WARNING" "dependencies" "Missing package-lock.json"
fi
fi

Expand All @@ -393,15 +399,15 @@

if [[ "$pip_vulns" -gt 0 ]]; then
print_warn "pip-audit: $pip_vulns vulnerable package(s)"
add_finding "warning" "dependencies" "pip: $pip_vulns vulnerable packages"
add_finding "$SEVERITY_WARNING" "dependencies" "pip: $pip_vulns vulnerable packages"
else
print_pass "pip-audit: no known vulnerabilities"
add_finding "pass" "dependencies" "pip: clean audit"
add_finding "$SEVERITY_PASS" "dependencies" "pip: clean audit"
fi
fi
else
print_skip "pip-audit not installed — install with: pip install pip-audit"
add_finding "info" "dependencies" "pip-audit not available"
add_finding "$SEVERITY_INFO" "dependencies" "pip-audit not available"
fi
fi

Expand All @@ -417,21 +423,21 @@
cargo_vulns=$(echo "$cargo_output" | jq '.vulnerabilities.found // 0' 2>/dev/null) || cargo_vulns="0"
if [[ "$cargo_vulns" -gt 0 ]]; then
print_warn "cargo audit: $cargo_vulns vulnerability(ies)"
add_finding "warning" "dependencies" "cargo: $cargo_vulns vulnerabilities"
add_finding "$SEVERITY_WARNING" "dependencies" "cargo: $cargo_vulns vulnerabilities"
else
print_pass "cargo audit: no known vulnerabilities"
add_finding "pass" "dependencies" "cargo: clean audit"
add_finding "$SEVERITY_PASS" "dependencies" "cargo: clean audit"
fi
fi
else
print_skip "cargo-audit not installed — install with: cargo install cargo-audit"
add_finding "info" "dependencies" "cargo-audit not available"
add_finding "$SEVERITY_INFO" "dependencies" "cargo-audit not available"
fi
fi

if [[ "$has_deps" == "false" ]]; then
print_skip "No dependency manifests found (package.json, requirements.txt, Cargo.toml)"
add_finding "info" "dependencies" "No dependency manifests found"
add_finding "$SEVERITY_INFO" "dependencies" "No dependency manifests found"

Check warning on line 440 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'dependencies' 12 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEZ&open=AZzQXdV4C1LEfgiIqEEZ&pullRequest=3628
fi

return 0
Expand All @@ -445,24 +451,24 @@

if ! command -v gh &>/dev/null; then
print_skip "GitHub CLI (gh) not installed"
add_finding "info" "collaborators" "gh CLI not available"
add_finding "$SEVERITY_INFO" "collaborators" "gh CLI not available"
return 0
fi

local slug
if ! slug=$(resolve_slug "$repo_path"); then
print_skip "No GitHub remote"
add_finding "info" "collaborators" "No GitHub remote"
add_finding "$SEVERITY_INFO" "collaborators" "No GitHub remote"
return 0
fi

# Per-repo collaborator check — never use a global cache
# Per-repo collaborator check — never use a global cache (t1412.11: must paginate)
local collab_json
collab_json=$(gh api "repos/$slug/collaborators" --jq '.[] | {login: .login, role: .role_name}' 2>/dev/null) || true
collab_json=$(gh api --paginate "repos/$slug/collaborators?per_page=100" --jq '.[] | {login: .login, role: .role_name}' 2>/dev/null) || true

if [[ -z "$collab_json" ]]; then
print_skip "Cannot access collaborator list (may require admin permissions)"
add_finding "info" "collaborators" "Cannot access collaborator list"
add_finding "$SEVERITY_INFO" "collaborators" "Cannot access collaborator list"
return 0
fi

Expand All @@ -474,14 +480,14 @@
total_count=$(echo "$collab_json" | jq -s 'length' 2>/dev/null) || total_count="0"

print_info "Collaborators: $total_count total ($admin_count admin, $write_count write)"
add_finding "info" "collaborators" "$total_count collaborators ($admin_count admin, $write_count write)"
add_finding "$SEVERITY_INFO" "collaborators" "$total_count collaborators ($admin_count admin, $write_count write)"

if [[ "$admin_count" -gt 5 ]]; then
print_warn "High number of admin collaborators ($admin_count) — review access levels"
add_finding "warning" "collaborators" "High admin count: $admin_count"
add_finding "$SEVERITY_WARNING" "collaborators" "High admin count: $admin_count"
else
print_pass "Admin collaborator count is reasonable ($admin_count)"
add_finding "pass" "collaborators" "Admin count OK: $admin_count"
add_finding "$SEVERITY_PASS" "collaborators" "Admin count OK: $admin_count"

Check warning on line 490 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'collaborators' 6 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEa&open=AZzQXdV4C1LEfgiIqEEa&pullRequest=3628
fi

return 0
Expand All @@ -496,10 +502,10 @@
# Check for SECURITY.md
if [[ -f "$repo_path/SECURITY.md" ]]; then
print_pass "SECURITY.md present"
add_finding "pass" "repo_security" "SECURITY.md present"
add_finding "$SEVERITY_PASS" "repo_security" "SECURITY.md present"
else
print_warn "No SECURITY.md — add a security policy for vulnerability reporting"
add_finding "warning" "repo_security" "Missing SECURITY.md"
add_finding "$SEVERITY_WARNING" "repo_security" "Missing SECURITY.md"
fi

# Check for .gitignore with common secret patterns
Expand All @@ -513,14 +519,14 @@

if [[ ${#missing_patterns[@]} -gt 0 ]]; then
print_warn ".gitignore missing common secret patterns: ${missing_patterns[*]}"
add_finding "warning" "repo_security" "gitignore missing: ${missing_patterns[*]}"
add_finding "$SEVERITY_WARNING" "repo_security" "gitignore missing: ${missing_patterns[*]}"
else
print_pass ".gitignore covers common secret file patterns"
add_finding "pass" "repo_security" "gitignore covers secret patterns"
add_finding "$SEVERITY_PASS" "repo_security" "gitignore covers secret patterns"
fi
else
print_warn "No .gitignore file"
add_finding "warning" "repo_security" "No .gitignore"
add_finding "$SEVERITY_WARNING" "repo_security" "No .gitignore"
fi

# Check for committed secrets (quick scan of tracked files)
Expand All @@ -531,22 +537,22 @@
local secret_count
secret_count=$(echo "$secret_files" | wc -l | tr -d ' ')
print_crit "$secret_count potential secret file(s) tracked by git"
add_finding "critical" "repo_security" "$secret_count secret files tracked: $(echo "$secret_files" | head -3 | tr '\n' ', ')"
add_finding "$SEVERITY_CRITICAL" "repo_security" "$secret_count secret files tracked: $(echo "$secret_files" | head -3 | tr '\n' ', ')"
else
print_pass "No obvious secret files tracked by git"
add_finding "pass" "repo_security" "No secret files tracked"
add_finding "$SEVERITY_PASS" "repo_security" "No secret files tracked"
fi

# Check for Dependabot or Renovate config
if [[ -f "$repo_path/.github/dependabot.yml" ]] || [[ -f "$repo_path/.github/dependabot.yaml" ]]; then
print_pass "Dependabot configured"
add_finding "pass" "repo_security" "Dependabot configured"
add_finding "$SEVERITY_PASS" "repo_security" "Dependabot configured"
elif [[ -f "$repo_path/renovate.json" ]] || [[ -f "$repo_path/.github/renovate.json" ]] || [[ -f "$repo_path/renovate.json5" ]]; then
print_pass "Renovate configured"
add_finding "pass" "repo_security" "Renovate configured"
add_finding "$SEVERITY_PASS" "repo_security" "Renovate configured"
else
print_warn "No automated dependency update tool (Dependabot/Renovate)"
add_finding "warning" "repo_security" "No dependency update automation"
add_finding "$SEVERITY_WARNING" "repo_security" "No dependency update automation"

Check warning on line 555 in .agents/scripts/security-posture-helper.sh

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Define a constant instead of using the literal 'repo_security' 10 times.

See more on https://sonarcloud.io/project/issues?id=marcusquinn_aidevops&issues=AZzQXdV4C1LEfgiIqEEb&open=AZzQXdV4C1LEfgiIqEEb&pullRequest=3628
fi

return 0
Expand Down Expand Up @@ -575,9 +581,9 @@

local posture_status="unknown"
if [[ "$FINDINGS_CRITICAL" -gt 0 ]]; then
posture_status="critical"
posture_status="$SEVERITY_CRITICAL"
elif [[ "$FINDINGS_WARNING" -gt 0 ]]; then
posture_status="warning"
posture_status="$SEVERITY_WARNING"
elif [[ "$total_findings" -gt 0 ]]; then
posture_status="good"
fi
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Expand Down
Loading
Loading