From 76104bda84f8b1ac33301d4798bcbdb9e413e31c Mon Sep 17 00:00:00 2001 From: saravanakumardb1 Date: Thu, 28 May 2026 22:30:08 -0700 Subject: [PATCH] fix(cli): harden bytelyst-cli env loading, pagination, and HTTP checks - .env via 'set -a; . ./.env; set +a' (handles quoted values/spaces safely) - printf for the GITHUB_TOKEN message so the newline renders - gh_get_all: paginate all pages (per_page=100) and verify HTTP 200 before jq; rewire list-public/list-private/check-collaborators through it - fix SC2199 whitelist membership (explicit loop, no substring false-matches) - shell-ci: gate shellcheck on bytelyst-cli + run agent-queue self-test --- .gitea/workflows/shell-ci.yml | 18 +++++----- bytelyst-cli.sh | 67 ++++++++++++++++++++++++++++------- 2 files changed, 64 insertions(+), 21 deletions(-) diff --git a/.gitea/workflows/shell-ci.yml b/.gitea/workflows/shell-ci.yml index 0915403..b588fcc 100644 --- a/.gitea/workflows/shell-ci.yml +++ b/.gitea/workflows/shell-ci.yml @@ -37,20 +37,22 @@ jobs: apt-get install -y -qq shellcheck shellcheck --version - - name: shellcheck agent-queue (errors fail the build) + - name: shellcheck (errors fail the build) run: | - shellcheck --severity=error --shell=bash agent-queue/agent-queue.sh + shellcheck --severity=error --shell=bash \ + agent-queue/agent-queue.sh \ + agent-queue/selftest.sh \ + bytelyst-cli.sh - - name: shellcheck bytelyst-cli (non-gating — legacy known issues) - continue-on-error: true - run: | - shellcheck --severity=error --shell=bash bytelyst-cli.sh - - - name: bash syntax check (gating, both scripts) + - name: bash syntax check (gating, all scripts) run: | bash -n agent-queue/agent-queue.sh + bash -n agent-queue/selftest.sh bash -n bytelyst-cli.sh + - name: agent-queue self-test (no-op engine cycle) + run: ./agent-queue/selftest.sh + - name: node syntax check (dashboard) run: node --check agent-queue/dashboard.mjs diff --git a/bytelyst-cli.sh b/bytelyst-cli.sh index 99027d9..5a700ee 100644 --- a/bytelyst-cli.sh +++ b/bytelyst-cli.sh @@ -35,17 +35,49 @@ for tool in "${REQUIRED_TOOLS[@]}"; do fi done -# Load .env if present +# Load .env if present. `set -a` exports everything sourced; this safely handles +# quoted values and spaces, unlike `export $(grep ... | xargs)`. if [[ -f .env ]]; then - export $(grep -v '^#' .env | xargs) + set -a + # shellcheck disable=SC1091 + . ./.env + set +a fi -# Validate GITHUB_TOKEN -if [[ -z "$GITHUB_TOKEN" ]]; then - echo "${RED}❌ Error: GITHUB_TOKEN is not set.\nSet it in your environment (e.g., export GITHUB_TOKEN=... in ~/.zshrc, ~/.bashrc, or .env).${RESET}" +# Validate GITHUB_TOKEN (printf so the newline renders, unlike echo "...\n...") +if [[ -z "${GITHUB_TOKEN:-}" ]]; then + printf '%s❌ Error: GITHUB_TOKEN is not set.\nSet it in your environment (e.g. export GITHUB_TOKEN=... in ~/.zshrc, ~/.bashrc, or .env).%s\n' "$RED" "$RESET" >&2 exit 1 fi +# gh_get_all -> echo one JSON array combining ALL pages (per_page=100). +# Verifies HTTP 200 on every page before parsing; returns non-zero on API error. +gh_get_all() { + local base="$1" page=1 combined="[]" + local joiner='&'; [[ "$base" == *'?'* ]] || joiner='?' + while :; do + local resp http body n + resp=$(curl -sS -w $'\n%{http_code}' \ + -H "Authorization: token $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "${base}${joiner}per_page=100&page=${page}") + http="${resp##*$'\n'}" + body="${resp%$'\n'*}" + if [[ "$http" != "200" ]]; then + printf '%s❌ GitHub API error (HTTP %s) for %s%s\n' "$RED" "$http" "$base" "$RESET" >&2 + printf '%s' "$body" | jq -r '.message? // empty' >&2 2>/dev/null || true + return 1 + fi + n=$(printf '%s' "$body" | jq 'length' 2>/dev/null || echo 0) + [[ "$n" -eq 0 ]] && break + combined=$(jq -s 'add' <(printf '%s' "$combined") <(printf '%s' "$body")) + [[ "$n" -lt 100 ]] && break + page=$((page+1)) + [[ "$page" -gt 100 ]] && break + done + printf '%s' "$combined" +} + usage() { echo "${BLUE}Bytelyst CLI - Unified GitHub DevOps Tool${RESET}" echo "" @@ -75,8 +107,9 @@ list_public_repos() { echo "${RED}❌ Please provide --user .${RESET}"; exit 1 fi echo "${BLUE}🔍 Fetching all public repositories for user: $user...${RESET}" - local response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/users/$user/repos?per_page=100&type=public") - local repos=$(echo "$response" | jq -r '.[].full_name') + local json repos + json=$(gh_get_all "https://api.github.com/users/$user/repos?type=public") || exit 1 + repos=$(printf '%s' "$json" | jq -r '.[].full_name') if [[ -z "$repos" ]]; then echo "${YELLOW}🚫 No public repositories found for user.${RESET}" else @@ -97,8 +130,9 @@ list_private_repos() { echo "${RED}❌ Please provide --org .${RESET}"; exit 1 fi echo "${BLUE}🔍 Fetching all private repositories for org: $org...${RESET}" - local response=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/orgs/$org/repos?per_page=100&type=private") - local repos=$(echo "$response" | jq -r '.[].full_name') + local json repos + json=$(gh_get_all "https://api.github.com/orgs/$org/repos?type=private") || exit 1 + repos=$(printf '%s' "$json" | jq -r '.[].full_name') if [[ -z "$repos" ]]; then echo "${YELLOW}🚫 No private repositories found for org.${RESET}" else @@ -127,12 +161,19 @@ check_collaborators() { fi for repo in "${repos[@]}"; do echo "${BLUE}🔍 Checking repo: $repo${RESET}" - local collaborators=$(curl -s -H "Authorization: token $GITHUB_TOKEN" "https://api.github.com/repos/$org/$repo/collaborators" | jq -r '.[].login') + local cjson collaborators + cjson=$(gh_get_all "https://api.github.com/repos/$org/$repo/collaborators") \ + || { echo "${YELLOW}⚠️ Skipping $repo (API error).${RESET}"; continue; } + collaborators=$(printf '%s' "$cjson" | jq -r '.[].login') local non_whitelisted=() for collab in $collaborators; do - if [[ ! " ${whitelist[@]} " =~ " ${collab} " ]]; then - non_whitelisted+=("$collab") - fi + # explicit membership test (avoids the array-concatenation pitfall of + # [[ " ${whitelist[@]} " =~ " $collab " ]], which false-matches substrings) + local is_white=false w + for w in "${whitelist[@]}"; do + [[ "$w" == "$collab" ]] && { is_white=true; break; } + done + $is_white || non_whitelisted+=("$collab") done if [[ ${#non_whitelisted[@]} -gt 0 ]]; then echo "${YELLOW}🚨 Repository: $repo${RESET}"