diff --git a/scripts/check-environment-readiness.sh b/scripts/check-environment-readiness.sh index 8a8095a..dca5713 100755 --- a/scripts/check-environment-readiness.sh +++ b/scripts/check-environment-readiness.sh @@ -131,6 +131,35 @@ valid_nonempty_csv() { done } +valid_public_rate_limit_policy() { + local json=$1 + + command -v jq >/dev/null 2>&1 || return 1 + + printf '%s' "$json" | jq -e ' + type == "object" and + length > 0 and + ([ + "registration_email", + "registration_ip", + "magic_link_email", + "magic_link_ip", + "password_login_email", + "password_login_ip", + "email_change_email", + "email_change_ip", + "support_request", + "support_request_ip", + "content_removal_notice", + "content_removal_notice_ip" + ] | all(. as $action | + ($json[$action] | type == "object") and + ($json[$action].limit | type == "number" and floor == . and . > 0) and + ($json[$action].window_seconds | type == "number" and floor == . and . > 0) + )) + ' --argjson json "$json" >/dev/null 2>&1 +} + failures=0 warnings=0 @@ -202,6 +231,17 @@ else missing "support inbox" "SUPPORT_INBOX_ADDRESS" fi +rate_limit_policies_json=$(value RATE_LIMIT_POLICIES_JSON) +if [[ "$deployment_env" == test && "$rate_limit_policies_json" == "{}" ]]; then + local_only "public rate limits" "all shared counters are disabled for this isolated test deployment" +elif [[ -z "$rate_limit_policies_json" ]]; then + missing "public rate limits" "RATE_LIMIT_POLICIES_JSON" +elif valid_public_rate_limit_policy "$rate_limit_policies_json"; then + ready "public rate limits" "authentication and anonymous-intake policies are enabled" +else + invalid "public rate limits" "required public policies are missing, malformed, or disabled" +fi + if all_empty GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET; then missing "Google sign-in" "GOOGLE_OAUTH_CLIENT_ID and GOOGLE_OAUTH_CLIENT_SECRET" elif all_set GOOGLE_OAUTH_CLIENT_ID GOOGLE_OAUTH_CLIENT_SECRET; then diff --git a/scripts/quality.sh b/scripts/quality.sh index 7bcbe0b..1913768 100755 --- a/scripts/quality.sh +++ b/scripts/quality.sh @@ -671,6 +671,20 @@ grep -Fx 'GOOGLE_OAUTH_AUTHORIZED_PARTY_IDS=quality-production-android-client' \ "$production_env" >/dev/null ./scripts/validate-production-env.sh "$production_env" help.test >/dev/null ./scripts/check-environment-readiness.sh "$production_env" --require-release >/dev/null +disabled_rate_limits_env="$scan_dir/production.disabled-rate-limits.env" +cp "$production_env" "$disabled_rate_limits_env" +sed -i 's|^RATE_LIMIT_POLICIES_JSON=.*|RATE_LIMIT_POLICIES_JSON={}|' \ + "$disabled_rate_limits_env" +if ./scripts/validate-production-env.sh \ + "$disabled_rate_limits_env" help.test >/dev/null 2>&1; then + echo "Production validation accepted disabled shared rate limits." >&2 + exit 1 +fi +if ./scripts/check-environment-readiness.sh \ + "$disabled_rate_limits_env" --require-release >/dev/null 2>&1; then + echo "Environment readiness accepted disabled shared rate limits." >&2 + exit 1 +fi if PRODUCTION_CODEX_SESSION_ID=00000000-0000-0000-0000-000000000001 \ PRODUCTION_WNH_FIREBASE_APPLICATION_ID=1:987654321:android:quality \ PRODUCTION_WNH_FIREBASE_API_KEY=quality-public-client-key \ diff --git a/scripts/validate-production-env.sh b/scripts/validate-production-env.sh index 3b785aa..209e5a2 100755 --- a/scripts/validate-production-env.sh +++ b/scripts/validate-production-env.sh @@ -92,6 +92,38 @@ valid_nonempty_csv() { done } +valid_public_rate_limit_policy() { + local json=$1 + + command -v jq >/dev/null 2>&1 || { + echo "Required command is unavailable for rate-limit validation: jq" >&2 + return 1 + } + + printf '%s' "$json" | jq -e ' + type == "object" and + length > 0 and + ([ + "registration_email", + "registration_ip", + "magic_link_email", + "magic_link_ip", + "password_login_email", + "password_login_ip", + "email_change_email", + "email_change_ip", + "support_request", + "support_request_ip", + "content_removal_notice", + "content_removal_notice_ip" + ] | all(. as $action | + ($json[$action] | type == "object") and + ($json[$action].limit | type == "number" and floor == . and . > 0) and + ($json[$action].window_seconds | type == "number" and floor == . and . > 0) + )) + ' --argjson json "$json" >/dev/null 2>&1 +} + reject_marker() { local key=$1 local value=$2 @@ -145,6 +177,7 @@ smtp_tls=$(optional_value SMTP_TLS) smtp_ssl=$(optional_value SMTP_SSL) email_from_address=$(require_value EMAIL_FROM_ADDRESS) support_inbox_address=$(optional_value SUPPORT_INBOX_ADDRESS) +rate_limit_policies_json=$(require_value RATE_LIMIT_POLICIES_JSON) google_oauth_client_id=$(optional_value GOOGLE_OAUTH_CLIENT_ID) google_oauth_client_secret=$(optional_value GOOGLE_OAUTH_CLIENT_SECRET) google_oauth_authorized_party_ids=$(optional_value GOOGLE_OAUTH_AUTHORIZED_PARTY_IDS) @@ -374,6 +407,11 @@ if [[ -n "$support_inbox_address" && exit 1 fi +valid_public_rate_limit_policy "$rate_limit_policies_json" || { + echo "RATE_LIMIT_POLICIES_JSON must enable every documented public authentication and intake policy with positive integer limit and window_seconds values; {} is reserved for isolated tests." >&2 + exit 1 +} + if [[ -n "$google_oauth_client_id" || -n "$google_oauth_client_secret" ]]; then [[ -n "$google_oauth_client_id" && -n "$google_oauth_client_secret" ]] || { echo "Google OAuth client ID and secret must either both be set or both be empty." >&2