feat: implement Ghaymah CLI Pre-Deployment Scanner and add associated audit and integration documentation

هذا الالتزام موجود في:
2026-08-23 18:39:08 +03:00
الأصل e823358715
التزام 21e1f1ea51
8 ملفات معدلة مع 2586 إضافات و48 حذوفات

عرض الملف

@@ -0,0 +1,267 @@
#!/bin/sh
# ═══════════════════════════════════════════════════════════════════════
# Ghaymah Pre-Deploy Scanner — Exhaustive QA Test Suite
# 16 scenarios covering baseline + deep edge cases
# ═══════════════════════════════════════════════════════════════════════
SCANNER="/root/ghaymah-v2-test/Scripts/gy-scanner.py"
TB="/tmp/gy-scanner-tests"
PASS=0
FAIL=0
TOTAL=0
RED='\033[1;31m'
GRN='\033[1;32m'
YLW='\033[1;33m'
CYN='\033[1;36m'
RST='\033[0m'
rm -rf "$TB"; mkdir -p "$TB"
echo ""
echo "═══════════════════════════════════════════════════════════════════"
echo " 🛡️ Ghaymah Pre-Deploy Scanner — Exhaustive QA Test Suite"
echo " 📅 $(date -u)"
echo "═══════════════════════════════════════════════════════════════════"
echo ""
run_test() {
name="$1"; dir="$2"; expect="$3"
TOTAL=$((TOTAL+1))
printf " 🧪 TEST #%02d: %-55s " "$TOTAL" "$name"
python3 "$SCANNER" --strict "$dir" >/dev/null 2>&1
got=$?
if [ "$got" -eq "$expect" ]; then
printf "${GRN}PASSED${RST} (exit=%d)\n" "$got"
PASS=$((PASS+1))
else
printf "${RED}FAILED${RST} (expected=%d got=%d)\n" "$expect" "$got"
FAIL=$((FAIL+1))
fi
}
verbose() {
name="$1"; dir="$2"; mode="$3"
echo ""
echo " ┌─────────────────────────────────────────────────────────────"
echo " │ 🔬 VERBOSE: $name"
echo " │ 📁 $dir"
echo " └─────────────────────────────────────────────────────────────"
flags=""
[ "$mode" = "json" ] && flags="--json"
python3 "$SCANNER" $flags "$dir" 2>&1
echo " Exit Code: $?"
echo " ─────────────────────────────────────────────────────────────"
}
# ══════════════ PART 1: BASELINE ══════════════
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📋 PART 1: BASELINE AUTOMATED TESTS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# 1: Secure App
D="$TB/secure"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
RUN addgroup --system nonroot && adduser --system nonroot
USER nonroot
COPY src/ /app/
EXPOSE 8080
E
printf ".env\n.git\n*.pem\n*.key\n" > "$D/.dockerignore"
run_test "Secure App (all pass)" "$D" 0
# 2: Env Leak
D="$TB/envleak"; mkdir -p "$D"
echo "SECRET=123" > "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
COPY . /app
USER nonroot
E
run_test "Env Leak (COPY . + no .dockerignore)" "$D" 1
# 3: Env Poisoning
D="$TB/poison"; mkdir -p "$D"
printf 'MAL=$(id)\nBAD=`whoami`\n' > "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
USER nonroot
EXPOSE 8080
E
run_test "Env Poisoning (shell substitution)" "$D" 1
# 4: Unclosed Quotes
D="$TB/unclosed"; mkdir -p "$D"
echo 'BROKEN="quote' > "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
USER nonroot
E
run_test "Unclosed Quotes (DoS vector)" "$D" 1
# ══════════════ PART 2: DEEP EDGE CASES ══════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📋 PART 2: DEEP EDGE-CASE SCENARIOS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
# A: Sneaky Dev (path traversal + string port)
D="$TB/sneaky"; mkdir -p "$D"
cat > "$D/.gy.json" <<'E'
{"app":"../../../etc/passwd","port":"8080","project":"legit","tier":"t1","env":{"NODE_ENV":"production"}}
E
cat > "$D/Dockerfile" <<'E'
FROM node:20-alpine
COPY src/ /app/
USER nonroot
EXPOSE 3000
E
run_test "Scenario A: Sneaky Dev (path traversal)" "$D" 1
# B: DoS Attempt (10k string + unclosed quote)
D="$TB/dos"; mkdir -p "$D"
python3 -c "print('PAYLOAD=\"' + 'A'*10000)" > "$D/.env"
echo 'OK=fine' >> "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM python:3.12-slim
USER nonroot
EXPOSE 8000
E
run_test "Scenario B: DoS (10k char + unclosed quote)" "$D" 1
# C: Symlink Attack (Dockerfile → /etc/shadow)
D="$TB/symlink"; mkdir -p "$D"
ln -sf /etc/shadow "$D/Dockerfile"
echo "NORMAL=val" > "$D/.env"
run_test "Scenario C: Symlink Attack (Dockerfile→/etc/shadow)" "$D" 1
# D: False Positive (legit complex .env)
D="$TB/falsep"; mkdir -p "$D"
cat > "$D/.env" <<'E'
DATABASE_URL=postgres://user:p@ssw0rd@db.example.com:5432/mydb?sslmode=require&connect_timeout=10
REDIS_URL=redis://default:abc123@redis.internal:6379/0
API_ENDPOINT=https://api.example.com/v2/webhooks?token=abc123&format=json
FEATURE_FLAGS={"darkMode":true,"beta":false}
NORMAL_STRING=hello world
PORT=8080
E
cat > "$D/Dockerfile" <<'E'
FROM node:20-alpine
COPY package.json yarn.lock ./
RUN yarn --frozen-lockfile
COPY src/ ./src/
RUN addgroup --system nonroot && adduser --system nonroot
USER nonroot
EXPOSE 3000
E
printf ".env\n.git\n*.pem\n*.key\n" > "$D/.dockerignore"
run_test "Scenario D: False Positive (legit complex .env)" "$D" 0
# E: Missing FROM
D="$TB/nofrom"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
COPY . /app
RUN echo "no FROM"
E
run_test "Scenario E: Missing FROM Instruction" "$D" 1
# F: Hardcoded Secrets
D="$TB/secrets"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
ENV API_KEY=sk-live-abc123def456
ENV DB_PASSWORD=SuperSecret123
USER nonroot
EXPOSE 8080
E
run_test "Scenario F: Hardcoded Secrets in ENV" "$D" 1
# G: curl piped to bash
D="$TB/curlpipe"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
FROM ubuntu:22.04
RUN curl -fsSL https://example.com/install.sh | bash -
USER nonroot
EXPOSE 3000
E
run_test "Scenario G: curl piped to bash" "$D" 1
# H: chmod 777
D="$TB/chmod"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
COPY . /app
RUN chmod 777 /app
USER nonroot
EXPOSE 8080
E
run_test "Scenario H: chmod 777" "$D" 1
# I: Invalid EXPOSE port
D="$TB/badport"; mkdir -p "$D"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
USER nonroot
EXPOSE 99999
E
run_test "Scenario I: EXPOSE port 99999 (out of range)" "$D" 1
# J: Control characters
D="$TB/ctrl"; mkdir -p "$D"
printf 'INJECT=value\x01\x02hidden' > "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM alpine:3.18
USER nonroot
EXPOSE 8080
E
run_test "Scenario J: Control chars in env value" "$D" 1
# K: No Dockerfile (auto-gen)
D="$TB/nodf"; mkdir -p "$D"
echo '{"app":"my-app","port":3000}' > "$D/.gy.json"
run_test "Scenario K: No Dockerfile (auto-gen, pass)" "$D" 0
# L: Weak .dockerignore
D="$TB/weakdi"; mkdir -p "$D"
echo "node_modules" > "$D/.dockerignore"
echo "DB_SECRET=hunter2" > "$D/.env"
cat > "$D/Dockerfile" <<'E'
FROM node:20-alpine
COPY . /app
USER nonroot
EXPOSE 3000
E
run_test "Scenario L: .dockerignore missing .env" "$D" 1
# ══════════════ PART 3: VERBOSE OUTPUT ══════════════
echo ""
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " 📋 PART 3: VERBOSE OUTPUTS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
verbose "Scenario 1: Secure App" "$TB/secure" "human"
verbose "Scenario A: Sneaky Dev" "$TB/sneaky" "json"
verbose "Scenario B: DoS Attempt" "$TB/dos" "human"
verbose "Scenario C: Symlink Attack" "$TB/symlink" "human"
verbose "Scenario D: False Positive" "$TB/falsep" "human"
# ══════════════ FINAL SUMMARY ══════════════
echo ""
echo "═══════════════════════════════════════════════════════════════════"
echo " 📊 FINAL RESULTS"
echo "═══════════════════════════════════════════════════════════════════"
echo ""
echo " Total Tests: $TOTAL"
printf " ${GRN}Passed: %d${RST}\n" "$PASS"
printf " ${RED}Failed: %d${RST}\n" "$FAIL"
echo ""
if [ "$FAIL" -eq 0 ]; then
printf " ${GRN}✅ ALL %d TESTS PASSED — Scanner is production-ready.${RST}\n" "$TOTAL"
else
printf " ${RED}⛔ %d TEST(S) FAILED — Review required.${RST}\n" "$FAIL"
fi
echo ""
echo "═══════════════════════════════════════════════════════════════════"
rm -rf "$TB"
exit $FAIL