#!/usr/bin/env bash # # validate-app-manifest.sh — Validate a community-submitted app manifest # # Usage: ./scripts/validate-app-manifest.sh # # Checks: # 1. Valid YAML syntax # 2. Required fields present (id, title, version, image, description) # 3. Image from trusted registry (docker.io, ghcr.io, quay.io) # 4. No :latest tag (must pin specific version) # 5. Resource limits specified (memory, cpu) # 6. Security: no privileged mode, no host networking # 7. No hardcoded secrets/passwords in environment # 8. Port conflicts with existing apps # # Exit 0 = valid, Exit 1 = issues found set -euo pipefail if [[ $# -lt 1 ]]; then echo "Usage: $0 " exit 1 fi MANIFEST="$1" PASS=0 FAIL=0 WARN=0 check() { local desc="$1" result="$2" if [[ "$result" == "pass" ]]; then PASS=$((PASS + 1)) echo " PASS: $desc" elif [[ "$result" == "warn" ]]; then WARN=$((WARN + 1)) echo " WARN: $desc" else FAIL=$((FAIL + 1)) echo " FAIL: $desc" fi } echo "Validating: $MANIFEST" echo "" # 1. File exists and is readable if [[ ! -f "$MANIFEST" ]]; then echo " FAIL: File not found: $MANIFEST" exit 1 fi check "File exists" "pass" # 2. Valid YAML if ! python3 -c "import yaml; yaml.safe_load(open('$MANIFEST'))" 2>/dev/null; then check "Valid YAML syntax" "fail" echo " Cannot continue with invalid YAML" exit 1 fi check "Valid YAML syntax" "pass" # 3. Required fields CONTENT=$(python3 -c " import yaml, json with open('$MANIFEST') as f: d = yaml.safe_load(f) print(json.dumps(d)) " 2>/dev/null) for field in id title version description; do val=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('$field',''))" 2>/dev/null) if [[ -n "$val" && "$val" != "None" ]]; then check "Required field '$field' present" "pass" else check "Required field '$field' present" "fail" fi done # 4. Image reference IMAGE=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('image','') or d.get('docker_image','') or '')" 2>/dev/null) if [[ -z "$IMAGE" || "$IMAGE" == "None" ]]; then check "Container image specified" "fail" else check "Container image specified" "pass" # Check trusted registry TRUSTED=false for reg in "docker.io" "ghcr.io" "quay.io" "registry.hub.docker.com"; do if echo "$IMAGE" | grep -q "$reg"; then TRUSTED=true break fi done # Also allow short-form Docker Hub images (no registry prefix) if ! echo "$IMAGE" | grep -q "/"; then TRUSTED=true # single-name images are Docker Hub official fi if [[ "$TRUSTED" == "true" ]]; then check "Image from trusted registry" "pass" else check "Image from trusted registry ($IMAGE)" "warn" fi # Check no :latest if echo "$IMAGE" | grep -q ":latest$"; then check "No :latest tag (pin specific version)" "fail" elif ! echo "$IMAGE" | grep -q ":"; then check "No version tag specified (should pin version)" "warn" else check "Version tag pinned" "pass" fi fi # 5. Security checks PRIVILEGED=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('privileged', False))" 2>/dev/null) if [[ "$PRIVILEGED" == "True" ]]; then check "No privileged mode" "fail" else check "No privileged mode" "pass" fi HOST_NET=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('host_network', d.get('network_mode','')))" 2>/dev/null) if [[ "$HOST_NET" == "host" ]]; then check "No host networking" "fail" else check "No host networking" "pass" fi # 6. Check for hardcoded secrets in env vars ENV_VARS=$(echo "$CONTENT" | python3 -c " import sys,json d=json.load(sys.stdin) env = d.get('environment', d.get('env', {})) if isinstance(env, dict): for k,v in env.items(): print(f'{k}={v}') elif isinstance(env, list): for e in env: print(e) " 2>/dev/null || echo "") SECRET_PATTERNS="password|secret|api_key|private_key|token" if echo "$ENV_VARS" | grep -iqE "$SECRET_PATTERNS"; then check "No hardcoded secrets in environment" "warn" else check "No hardcoded secrets in environment" "pass" fi # 7. Memory limit MEM=$(echo "$CONTENT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('memory', d.get('mem_limit', d.get('resources',{}).get('memory',''))))" 2>/dev/null) if [[ -n "$MEM" && "$MEM" != "None" && "$MEM" != "" ]]; then check "Memory limit specified ($MEM)" "pass" else check "Memory limit specified" "warn" fi echo "" echo "Results: $PASS passed, $FAIL failed, $WARN warnings" if [[ "$FAIL" -gt 0 ]]; then echo "STATUS: REJECTED — fix failures before resubmitting" exit 1 else echo "STATUS: APPROVED (with $WARN warnings)" exit 0 fi