Elevate requirements to organizational/architectural policy
- Security: no IAM in service repos, no custom auth, no direct external calls - Architecture: no cross-cloud SDKs, no cross-service DB access, no hardcoded tenant/env config - DevOps: Foxtrot-compatible Helm (no custom ingress), no infra provisioning in service repos, no pinned infra versions - Cost: resource tagging, no unbounded allocation, no per-tenant infra - Updated checker and demo to match - These are NOT static code analysis — they catch organizational policy violations that SonarQube/Checkstyle miss
This commit is contained in:
178
.tests/check.sh
178
.tests/check.sh
@@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env bash
|
||||
# Deterministic compliance checker
|
||||
# Runs against a diff or directory and checks all requirement categories
|
||||
# AI SDLC Standards — Deterministic Compliance Checker
|
||||
# Checks organizational and architectural policy, NOT static code analysis.
|
||||
# Usage: ./check.sh <path-to-repo> [--diff <base-branch>]
|
||||
set -euo pipefail
|
||||
|
||||
@@ -18,106 +18,136 @@ fi
|
||||
RED='\033[0;31m'
|
||||
YELLOW='\033[1;33m'
|
||||
GREEN='\033[0;32m'
|
||||
CYAN='\033[0;36m'
|
||||
NC='\033[0m'
|
||||
|
||||
pass() { echo -e " ${GREEN}✓${NC} $1"; }
|
||||
warn() { echo -e " ${YELLOW}⚠${NC} $1"; WARNINGS=$((WARNINGS + 1)); }
|
||||
fail() { echo -e " ${RED}✗${NC} $1"; VIOLATIONS=$((VIOLATIONS + 1)); }
|
||||
|
||||
get_changed_files() {
|
||||
if $DIFF_MODE; then
|
||||
git -C "$REPO" diff --name-only "$BASE_BRANCH" -- '*.java' '*.go' '*.js' '*.ts' '*.py' '*.kt' '*.sql' '*.yaml' '*.yml' '*.tf' '*.json' 2>/dev/null || true
|
||||
else
|
||||
find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.js' -o -name '*.ts' -o -name '*.py' -o -name '*.kt' \) ! -path '*/test/*' ! -path '*/node_modules/*' ! -path '*/.git/*' 2>/dev/null
|
||||
fi
|
||||
}
|
||||
|
||||
FILES=$(get_changed_files)
|
||||
|
||||
echo "═══════════════════════════════════════"
|
||||
echo " AI SDLC Standards Compliance Check"
|
||||
echo "═══════════════════════════════════════"
|
||||
echo ""
|
||||
|
||||
# ── SEC-001: Input Validation ──
|
||||
echo "▸ SEC-001: Input Validation"
|
||||
RAW_ACCESS=$(echo "$FILES" | xargs grep -ln 'request\.getParameter\|req\.body\b\|request\.body\b' 2>/dev/null | grep -v test || true)
|
||||
if [[ -n "$RAW_ACCESS" ]]; then
|
||||
for f in $RAW_ACCESS; do fail "Raw request access: $f"; done
|
||||
# ── SECURITY ──
|
||||
echo -e "${CYAN}Security${NC}"
|
||||
|
||||
# SEC-001: No IAM Resources in Service Repos
|
||||
echo "▸ SEC-001: No IAM in Service Repos"
|
||||
IAM_FILES=$(find "$REPO" -name '*.tf' -exec grep -l 'aws_iam_\|google_project_iam\|azurerm_role' {} \; 2>/dev/null || true)
|
||||
if [[ -n "$IAM_FILES" ]]; then
|
||||
for f in $IAM_FILES; do fail "IAM resources in service repo: $f"; done
|
||||
else
|
||||
pass "No raw request body access found"
|
||||
pass "No IAM resource definitions found"
|
||||
fi
|
||||
|
||||
# ── SEC-002: No Raw SQL ──
|
||||
echo "▸ SEC-002: No Raw SQL"
|
||||
SQL_CONCAT=$(echo "$FILES" | xargs grep -Pln '(SELECT|INSERT|UPDATE|DELETE|FROM|WHERE).*["\x27]\s*\+\s*|String\.format.*?(SELECT|INSERT|UPDATE|DELETE)' 2>/dev/null | grep -v test || true)
|
||||
if [[ -n "$SQL_CONCAT" ]]; then
|
||||
for f in $SQL_CONCAT; do fail "SQL concatenation: $f"; done
|
||||
else
|
||||
pass "No SQL string concatenation found"
|
||||
# SEC-002: No Custom Auth / JWT Handling
|
||||
echo "▸ SEC-002: No Custom Auth Mechanisms"
|
||||
CUSTOM_AUTH=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -l 'io\.jsonwebtoken\|jwt\.decode\|jwt\.sign\|JWTVerifier\|PyJWT' {} \; 2>/dev/null || true)
|
||||
LOGIN_ENDPOINTS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -l '"/login"\|"/authenticate"\|"/signin"' {} \; 2>/dev/null || true)
|
||||
if [[ -n "$CUSTOM_AUTH" ]]; then
|
||||
for f in $CUSTOM_AUTH; do fail "Custom JWT/auth library: $f"; done
|
||||
fi
|
||||
if [[ -n "$LOGIN_ENDPOINTS" ]]; then
|
||||
for f in $LOGIN_ENDPOINTS; do fail "Custom login endpoint: $f"; done
|
||||
fi
|
||||
if [[ -z "$CUSTOM_AUTH" && -z "$LOGIN_ENDPOINTS" ]]; then
|
||||
pass "No custom auth mechanisms found"
|
||||
fi
|
||||
|
||||
# ── SEC-003: Auth Annotations ──
|
||||
echo "▸ SEC-003: Authentication Annotations"
|
||||
ENDPOINTS=$(echo "$FILES" | xargs grep -ln '@RequestMapping\|@GetMapping\|@PostMapping\|@PutMapping\|@DeleteMapping' 2>/dev/null || true)
|
||||
for f in $ENDPOINTS; do
|
||||
MISSING=$(grep -B5 '@\(Request\|Get\|Post\|Put\|Delete\)Mapping' "$f" | grep -c '@ReltioSecured\|@PublicEndpoint\|@PreAuthorize' || true)
|
||||
TOTAL=$(grep -c '@\(Request\|Get\|Post\|Put\|Delete\)Mapping' "$f" || true)
|
||||
if [[ "$MISSING" -lt "$TOTAL" ]]; then
|
||||
warn "Endpoint without explicit auth: $f ($MISSING/$TOTAL annotated)"
|
||||
fi
|
||||
done
|
||||
if [[ -z "$ENDPOINTS" ]]; then pass "No endpoint files in scope"; fi
|
||||
|
||||
# ── SEC-004: Secrets in Code ──
|
||||
echo "▸ SEC-004: Secrets in Code"
|
||||
SECRETS=$(echo "$FILES" | xargs grep -Piln '(password|secret|api_key|apikey|token)\s*=\s*["\x27][^"\x27]{8,}' 2>/dev/null | grep -v test | grep -v '\.env\.example' || true)
|
||||
if [[ -n "$SECRETS" ]]; then
|
||||
for f in $SECRETS; do fail "Possible hardcoded secret: $f"; done
|
||||
# SEC-003: No Direct External Network Calls
|
||||
echo "▸ SEC-003: No Direct External Calls"
|
||||
EXT_CALLS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -Pn 'https?://(?!localhost|127\.0\.0\.1|10\.|192\.168\.|172\.(1[6-9]|2[0-9]|3[01])\.)(?!.*reltio\.(com|net|io))' {} \; 2>/dev/null | grep -v '^\s*//' | head -5 || true)
|
||||
if [[ -n "$EXT_CALLS" ]]; then
|
||||
echo "$EXT_CALLS" | while read -r line; do fail "Direct external call: $line"; done
|
||||
else
|
||||
pass "No hardcoded secrets detected"
|
||||
pass "No direct external network calls"
|
||||
fi
|
||||
|
||||
# ── OPS-002: Structured Logging ──
|
||||
echo "▸ OPS-002: Structured Logging"
|
||||
RAW_LOG=$(echo "$FILES" | xargs grep -ln 'System\.out\.print\|System\.err\.print' 2>/dev/null | grep -v test || true)
|
||||
if [[ -n "$RAW_LOG" ]]; then
|
||||
for f in $RAW_LOG; do warn "Raw stdout/stderr logging: $f"; done
|
||||
echo ""
|
||||
|
||||
# ── ARCHITECTURE ──
|
||||
echo -e "${CYAN}Architecture${NC}"
|
||||
|
||||
# ARCH-001: No Cross-Cloud Dependencies
|
||||
echo "▸ ARCH-001: No Cross-Cloud SDK Imports"
|
||||
CLOUD_IMPORTS=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' ! -path '*/platform/*' -exec grep -ln 'com\.amazonaws\.\|com\.google\.cloud\.\|com\.azure\.\|@aws-sdk\|@google-cloud\|@azure' {} \; 2>/dev/null || true)
|
||||
if [[ -n "$CLOUD_IMPORTS" ]]; then
|
||||
for f in $CLOUD_IMPORTS; do fail "Direct cloud SDK import: $f"; done
|
||||
else
|
||||
pass "No raw stdout/stderr logging"
|
||||
pass "No direct cloud SDK imports"
|
||||
fi
|
||||
|
||||
# ── OPS-003: Resource Limits ──
|
||||
echo "▸ OPS-003: Resource Limits"
|
||||
K8S_DEPLOYS=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -l 'kind: Deployment' 2>/dev/null || true)
|
||||
for f in $K8S_DEPLOYS; do
|
||||
if ! grep -q 'resources:' "$f"; then
|
||||
fail "K8s deployment missing resource limits: $f"
|
||||
else
|
||||
pass "Resource limits present: $f"
|
||||
fi
|
||||
done
|
||||
# ARCH-002: No Cross-Service Database Access
|
||||
echo "▸ ARCH-002: No Cross-Service DB Access"
|
||||
CROSS_DB=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' -o -name '*.sql' \) ! -path '*/test/*' -exec grep -ln '[a-z_]*_service\.\|cross_schema\|foreign_schema' {} \; 2>/dev/null || true)
|
||||
if [[ -n "$CROSS_DB" ]]; then
|
||||
for f in $CROSS_DB; do warn "Possible cross-service DB access: $f"; done
|
||||
else
|
||||
pass "No cross-service database access detected"
|
||||
fi
|
||||
|
||||
# ── OPS-004: Rollback Safety ──
|
||||
echo "▸ OPS-004: Rollback Safety"
|
||||
MIGRATIONS=$(find "$REPO" -path '*/migration*' -name '*.sql' 2>/dev/null || true)
|
||||
for f in $MIGRATIONS; do
|
||||
DANGEROUS=$(grep -Pic 'DROP\s+COLUMN|RENAME\s+COLUMN|ALTER\s+.*TYPE' "$f" || true)
|
||||
if [[ "$DANGEROUS" -gt 0 ]]; then
|
||||
fail "Destructive migration: $f (DROP/RENAME/ALTER TYPE)"
|
||||
fi
|
||||
done
|
||||
if [[ -z "$MIGRATIONS" ]]; then pass "No migration files in scope"; fi
|
||||
# ARCH-003: No Hardcoded Tenant/Environment Config
|
||||
echo "▸ ARCH-003: No Hardcoded Tenant/Environment Config"
|
||||
HARDCODED=$(find "$REPO" -type f \( -name '*.java' -o -name '*.go' -o -name '*.ts' -o -name '*.py' \) ! -path '*/test/*' -exec grep -Pln 'https?://(prod|staging|dev)\.\w+\.(com|net|io)|\.equals\("(acme|tenant|customer)-' {} \; 2>/dev/null || true)
|
||||
if [[ -n "$HARDCODED" ]]; then
|
||||
for f in $HARDCODED; do fail "Hardcoded tenant/environment config: $f"; done
|
||||
else
|
||||
pass "No hardcoded tenant/environment configuration"
|
||||
fi
|
||||
|
||||
# ── COST-001: Resource Tagging ──
|
||||
echo ""
|
||||
|
||||
# ── DEVOPS ──
|
||||
echo -e "${CYAN}DevOps${NC}"
|
||||
|
||||
# OPS-001: Foxtrot-Compatible Helm Chart (no custom ingress)
|
||||
echo "▸ OPS-001: No Custom Ingress (Foxtrot Routing)"
|
||||
CUSTOM_INGRESS=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -l 'kind: Ingress' 2>/dev/null || true)
|
||||
if [[ -n "$CUSTOM_INGRESS" ]]; then
|
||||
for f in $CUSTOM_INGRESS; do fail "Custom Ingress resource (Foxtrot manages routing): $f"; done
|
||||
else
|
||||
pass "No custom Ingress definitions"
|
||||
fi
|
||||
|
||||
# OPS-002: No Infrastructure Provisioning in Service Repos
|
||||
echo "▸ OPS-002: No Infrastructure in Service Repos"
|
||||
INFRA_FILES=$(find "$REPO" -name '*.tf' -o -name 'Pulumi.*' | grep -v '.terraform' 2>/dev/null || true)
|
||||
CFN_FILES=$(find "$REPO" -name '*.template.yaml' -o -name '*.template.json' | xargs grep -l 'AWSTemplateFormatVersion\|AWS::' 2>/dev/null || true)
|
||||
if [[ -n "$INFRA_FILES" ]]; then
|
||||
for f in $INFRA_FILES; do fail "Infrastructure-as-code in service repo: $f"; done
|
||||
fi
|
||||
if [[ -n "$CFN_FILES" ]]; then
|
||||
for f in $CFN_FILES; do fail "CloudFormation template in service repo: $f"; done
|
||||
fi
|
||||
if [[ -z "$INFRA_FILES" && -z "$CFN_FILES" ]]; then
|
||||
pass "No infrastructure provisioning files"
|
||||
fi
|
||||
|
||||
# OPS-004: No Pinned Infrastructure Versions
|
||||
echo "▸ OPS-004: No Pinned Infrastructure Versions"
|
||||
PINNED=$(find "$REPO" -name '*.yaml' -o -name '*.yml' | xargs grep -Pn 'image:\s*(postgres|mysql|redis|mongo|elasticsearch|kafka|rabbitmq|zookeeper):\d' 2>/dev/null || true)
|
||||
if [[ -n "$PINNED" ]]; then
|
||||
echo "$PINNED" | while read -r line; do fail "Pinned infra version: $line"; done
|
||||
else
|
||||
pass "No pinned infrastructure image versions"
|
||||
fi
|
||||
|
||||
echo ""
|
||||
|
||||
# ── COST ──
|
||||
echo -e "${CYAN}Cost${NC}"
|
||||
|
||||
# COST-001: Resource Tagging
|
||||
echo "▸ COST-001: Resource Tagging"
|
||||
TF_FILES=$(find "$REPO" -name '*.tf' 2>/dev/null || true)
|
||||
for f in $TF_FILES; do
|
||||
if grep -q 'resource\s' "$f" && ! grep -q 'tags' "$f"; then
|
||||
TF_RESOURCES=$(find "$REPO" -name '*.tf' -exec grep -l 'resource\s' {} \; 2>/dev/null || true)
|
||||
for f in $TF_RESOURCES; do
|
||||
if ! grep -q 'tags' "$f"; then
|
||||
warn "Terraform resource without tags: $f"
|
||||
fi
|
||||
done
|
||||
if [[ -z "$TF_FILES" ]]; then pass "No Terraform files in scope"; fi
|
||||
if [[ -z "$TF_RESOURCES" ]]; then pass "No Terraform resources in scope"; fi
|
||||
|
||||
echo ""
|
||||
echo "═══════════════════════════════════════"
|
||||
|
||||
Reference in New Issue
Block a user