268 أسطر
9.7 KiB
Bash
268 أسطر
9.7 KiB
Bash
#!/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
|