#!/bin/bash # Agent repo finalize script — auto-commit and push changes # AR-19, AR-20, AR-8, AR-32, F66 set -euo pipefail # --------------------------------------------------------------------------- # F66: Content-based secret scanning # Scans staged diff for credential patterns before committing. # Returns 0 if clean, 1 if secrets detected. # --------------------------------------------------------------------------- scan_for_secrets() { local diff found=0 diff=$(cat) # Extract only added lines (skip diff headers +++) local added added=$(printf '%s\n' "$diff" | grep '^+' | grep -v '^+++' || true) if [[ -z "$added" ]]; then return 0 fi # Pattern 1: AWS access key — AKIA followed by exactly 16 uppercase letters/digits if printf '%s\n' "$added" | grep -qE 'AKIA[0-9A-Z]{16}'; then echo "AWS access key pattern detected (AKIA...)" found=1 fi # Pattern 2: PEM private key header (RSA, EC, OPENSSH, etc.) if printf '%s\n' "$added" | grep -qE -- '-----BEGIN .* PRIVATE KEY-----'; then echo "PEM private key header detected" found=1 fi # Pattern 3: High-entropy credential assignment (40+ char value near key/token/secret keywords) if printf '%s\n' "$added" | grep -qiE \ '(secret[._]?key|api[._]?key|access[._]?key|auth[._]?token|\bsecret\b|\btoken\b|\bpassword\b|\bpasswd\b)[[:space:]]*[=:][[:space:]]*['"'"'""]?[A-Za-z0-9+/=_-]{40,}'; then echo "High-entropy credential assignment detected" found=1 fi return $found } # Allow sourcing for unit tests without running main if [[ "${BASH_SOURCE[0]}" != "${0}" ]]; then return 0 2>/dev/null || true fi echo "=== agent-repo/v1 finalize.sh ===" WORKING_DIR="${AGENT_WORKING_DIR:-/workspace/project}" AGENT_OUTPUT_DIR="${AGENT_OUTPUT_DIR:-/workspace/.agent-output}" METADATA_FILE="$AGENT_OUTPUT_DIR/ci_metadata.json" AGENT_EXIT="${AGENT_EXIT_CODE:-0}" TASK_ID="${AGENT_TASK_ID:-unknown}" BRANCH="${AGENT_BRANCH:-}" REPO_URL="${AGENT_REPO_URL:-}" mkdir -p "$AGENT_OUTPUT_DIR" # AR-19: Mark working dir as safe (finalize may run as different user than agent) git config --global --add safe.directory "$WORKING_DIR" # Change to working directory cd "$WORKING_DIR" || { echo "ERROR: Failed to change to working directory: $WORKING_DIR" >&2 exit 1 } # Verify this is a git repository (defensive: catches misconfigured payloads # where agent_repo field is missing but agent-repo harness is in the composite) if [ ! -d ".git" ]; then echo "ERROR: $WORKING_DIR is not a git repository." >&2 echo "Hint: Ensure the task payload includes 'agent_repo: {repo_url: ..., branch: ...}'" >&2 echo " so init.sh clones the repo before the agent runs." >&2 exit 1 fi # AR-32: Restore .gitignore from harness template to prevent removing entries HARNESS_GITIGNORE="/opt/harness/contexts/agent-repo/v1/.gitignore_template" if [ -f "$HARNESS_GITIGNORE" ]; then cat "$HARNESS_GITIGNORE" >> .gitignore fi # AR-8: Check if there are any changes to commit git add -A if [ -z "$(git status --porcelain)" ]; then echo "No changes to commit — skipping push (AR-8)" # Write metadata with agent_branch_pushed=false python3 -c " import json, os out = '$METADATA_FILE' meta = {} if os.path.isfile(out): try: with open(out) as f: meta = json.load(f) except Exception: meta = {} meta['agent_branch'] = '$BRANCH' meta['agent_repo_url'] = '$REPO_URL' meta['agent_branch_pushed'] = False with open(out, 'w') as f: json.dump(meta, f) print('Wrote ci_metadata.json (no-op: no changes)') " exit 0 fi # F66: Scan staged content for secrets before committing SCAN_FINDINGS="" SCAN_EXIT=0 set +e SCAN_FINDINGS=$(git diff --cached | scan_for_secrets) SCAN_EXIT=$? set -e if [ "$SCAN_EXIT" -ne 0 ]; then echo "SECRET SCAN BLOCKED COMMIT — credentials detected in staged content" echo "$SCAN_FINDINGS" # Write metadata indicating scan blocked the commit python3 -c " import json, os out = '$METADATA_FILE' meta = {} if os.path.isfile(out): try: with open(out) as f: meta = json.load(f) except Exception: meta = {} meta['agent_branch'] = '$BRANCH' meta['agent_repo_url'] = '$REPO_URL' meta['agent_branch_pushed'] = False meta['secret_scan_blocked'] = True meta['scan_findings'] = '''$SCAN_FINDINGS''' with open(out, 'w') as f: json.dump(meta, f) print('Wrote ci_metadata.json (blocked by secret scan)') " exit 0 fi # V3: Diff size check (warning only, does not block push) DIFF_LINES=$(git diff --cached --stat | tail -1 | grep -oP '\d+(?= insertion)' || echo "0") DIFF_KB=$(echo "$DIFF_LINES" | awk '{printf "%.1f", $1/1000}') MAX_DIFF_KB="${AGENT_MAX_DIFF_KB:-500}" if awk "BEGIN {exit !($DIFF_KB > $MAX_DIFF_KB)}"; then echo "WARNING: Large diff detected: ${DIFF_KB}KB (threshold: ${MAX_DIFF_KB}KB)" fi # AR-6, AR-18: Build commit message based on agent outcome if [ "$AGENT_EXIT" = "0" ]; then OUTCOME="succeeded" else OUTCOME="failed (exit $AGENT_EXIT)" fi # Get first line of prompt (safe, via tempfile) PROMPT_FIRST_LINE="${AGENT_PROMPT:-}" if [ -n "$PROMPT_FIRST_LINE" ]; then PROMPT_FIRST_LINE=$(echo "$PROMPT_FIRST_LINE" | head -1 | cut -c1-100) fi # Write commit message to tempfile (AR-19: safe handling) COMMIT_MSG_FILE=$(mktemp) trap "rm -f $COMMIT_MSG_FILE" EXIT if [ -n "$PROMPT_FIRST_LINE" ]; then echo "Agent task $TASK_ID ($OUTCOME): $PROMPT_FIRST_LINE" > "$COMMIT_MSG_FILE" else echo "Agent task $TASK_ID ($OUTCOME)" > "$COMMIT_MSG_FILE" fi # Commit git commit -F "$COMMIT_MSG_FILE" # Get commit SHA COMMIT_SHA=$(git rev-parse HEAD) # AR-19: Always push — even if tests failed, partial work is better than lost work. # The task exit code and ci_metadata.json track whether tests passed. echo "Pushing branch $BRANCH to $REPO_URL..." if timeout 120 git push "$REPO_URL" "HEAD:refs/heads/$BRANCH" --force; then echo "Push succeeded" PUSHED=true else echo "ERROR: Push failed" >&2 PUSHED=false fi # Write ci_metadata.json python3 -c " import json, os out = '$METADATA_FILE' meta = {} if os.path.isfile(out): try: with open(out) as f: meta = json.load(f) except Exception: meta = {} meta['agent_branch'] = '$BRANCH' meta['agent_sha'] = '$COMMIT_SHA' meta['agent_repo_url'] = '$REPO_URL' meta['agent_branch_pushed'] = $( [ '$PUSHED' = 'true' ] && echo 'True' || echo 'False' ) meta['diff_kb'] = float('$DIFF_KB') if '$DIFF_KB' else 0.0 with open(out, 'w') as f: json.dump(meta, f) print('Wrote ci_metadata.json') " if [ "$PUSHED" = "false" ]; then echo "ERROR: Push failed — branch $BRANCH was committed but not pushed" >&2 exit 1 fi echo "=== agent-repo/v1 finalize.sh complete ==="