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
63 changes: 42 additions & 21 deletions .agents/scripts/stats-functions.sh
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,24 @@ if type _validate_int &>/dev/null; then
SESSION_COUNT_WARN=$(_validate_int SESSION_COUNT_WARN "$SESSION_COUNT_WARN" 5 1)
fi

#######################################
# Validate a repo slug matches the expected owner/repo format.
# Rejects path traversal, quotes, and other injection vectors.
# Arguments:
# $1 - repo slug to validate
# Returns: 0 if valid, 1 if invalid
#######################################
_validate_repo_slug() {
local slug="$1"
# Must be non-empty, match owner/repo with only alphanumeric, hyphens,
# underscores, and dots (GitHub's allowed characters)
if [[ "$slug" =~ ^[a-zA-Z0-9._-]+/[a-zA-Z0-9._-]+$ ]]; then
return 0
fi
echo "[stats] Invalid repo slug rejected: ${slug}" >>"$LOGFILE"
return 1
}

#######################################
# Count interactive AI sessions (duplicate of pulse-wrapper's check_session_count)
#
Expand Down Expand Up @@ -93,6 +111,12 @@ _get_runner_role() {
local runner_user="$1"
local repo_slug="$2"

# Validate slug before using in API path (defense-in-depth)
if ! _validate_repo_slug "$repo_slug"; then
echo "contributor"
return 0
fi

# Check cache (env var keyed by slug — avoids repeated API calls)
local cache_key="__RUNNER_ROLE_${repo_slug//[^a-zA-Z0-9]/_}"
local cached_role="${!cache_key:-}"
Expand Down Expand Up @@ -553,7 +577,7 @@ ${cross_repo_person_stats_md:-_Cross-repo person stats unavailable._}
| Processes | ${sys_procs} |

---
_Auto-updated by ${runner_role} pulse. Do not edit manually._"
_Auto-updated by ${runner_role} stats process. Do not edit manually._"

# Update the issue body
gh issue edit "$health_issue_number" --repo "$repo_slug" --body "$body" >/dev/null 2>&1 || {
Expand Down Expand Up @@ -602,11 +626,11 @@ _cleanup_stale_pinned_issues() {
local owner="${repo_slug%%/*}"
local name="${repo_slug##*/}"

# Query all pinned issues via GraphQL
# Query all pinned issues via GraphQL (parameterized to prevent injection)
local pinned_json
pinned_json=$(gh api graphql -f query="
query {
repository(owner: \"${owner}\", name: \"${name}\") {
pinned_json=$(gh api graphql -F owner="$owner" -F name="$name" -f query="
query(\$owner: String!, \$name: String!) {
repository(owner: \$owner, name: \$name) {
pinnedIssues(first: 10) {
nodes {
issue {
Expand All @@ -618,7 +642,8 @@ _cleanup_stale_pinned_issues() {
}
}
}
}" 2>/dev/null || echo "")
}
" 2>>"$LOGFILE" || echo "")

[[ -z "$pinned_json" ]] && return 0

Expand Down Expand Up @@ -1441,7 +1466,7 @@ ${coderabbit_section}
${review_scan_section}

---
_Auto-generated by pulse-wrapper.sh daily quality sweep. The supervisor will review findings and create actionable issues._"
_Auto-generated by stats-wrapper.sh daily quality sweep. The supervisor will review findings and create actionable issues._"

# --- 7. Update issue body with stats dashboard (t1411) ---
# Mirrors the supervisor health issue pattern: the issue body is a live
Expand Down Expand Up @@ -1495,23 +1520,19 @@ _update_quality_issue_body() {
# (CodeRabbit review feedback — gh issue list defaults to 30 results).
local debt_open=0
local debt_closed=0
debt_open=$(gh api graphql -f query="
query {
search(
query: \"repo:${repo_slug} is:issue is:open label:quality-debt\",
type: ISSUE,
first: 1
) {
debt_open=$(gh api graphql \
-F searchQuery="repo:${repo_slug} is:issue is:open label:quality-debt" \
-f query="
query(\$searchQuery: String!) {
search(query: \$searchQuery, type: ISSUE, first: 1) {
issueCount
}
}" --jq '.data.search.issueCount' 2>>"$LOGFILE" || echo "0")
debt_closed=$(gh api graphql -f query="
query {
search(
query: \"repo:${repo_slug} is:issue is:closed label:quality-debt\",
type: ISSUE,
first: 1
) {
debt_closed=$(gh api graphql \
-F searchQuery="repo:${repo_slug} is:issue is:closed label:quality-debt" \
-f query="
query(\$searchQuery: String!) {
search(query: \$searchQuery, type: ISSUE, first: 1) {
issueCount
}
}" --jq '.data.search.issueCount' 2>>"$LOGFILE" || echo "0")
Expand Down
2 changes: 1 addition & 1 deletion .agents/scripts/tests/test-pr-3885-recovery.sh
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ assert_line_exists() {
fi

fail "$message"
return 1
return 0
}

run_checks() {
Expand Down
2 changes: 1 addition & 1 deletion aidevops.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2012,7 +2012,7 @@ SOPSEOF
local security_posture_script="$AGENTS_DIR/scripts/security-posture-helper.sh"
if [[ -f "$security_posture_script" ]]; then
print_info "Running security posture assessment..."
if bash "$security_posture_script" store "$project_root" 2>/dev/null; then
if bash "$security_posture_script" store "$project_root"; then
print_success "Security posture assessed and stored in .aidevops.json"
else
print_warning "Security posture assessment found issues (review with: aidevops security audit)"
Expand Down
Loading