Security fixes: wildcard CORS, JWT auth, zero-address fallback

Phase 1 security remediation from codebase analysis:

CORS fixes:
- Replace wildcard CORS with safe localhost defaults in agent-coordinator
- Replace wildcard CORS with safe localhost defaults in marketplace
- Fix 8 additional wildcard CORS instances in coordinator-api apps:
  - hermes_enhanced_app.py
  - api_gateway.py
  - modality_optimization_app.py
  - multimodal_app.py
  - gpu_multimodal_app.py
  - marketplace_enhanced_app.py
  - advanced_ai_service.py
  - adaptive_learning_app.py
- Add CORS configuration security tests

Blockchain-node auth fixes:
- JWT authentication now fails closed with clear error message
- X-Wallet-Address already gated behind TRUST_X_WALLET_ADDRESS env var
- Remove zero-address fallback from arbitration vote submission
- Add regression test for zero-address rejection in arbitration

Tests:
- Update dispute auth tests to reflect new JWT error message
- Add test_arbitration_vote_zero_address_rejected
- Add test_cors_configuration.py with 5 CORS validation tests
This commit is contained in:
aitbc
2026-05-24 19:31:26 +02:00
parent 494bd962b4
commit 13ada12b49
13 changed files with 342 additions and 63 deletions

View File

@@ -4,7 +4,7 @@ from fastapi.middleware.cors import CORSMiddleware
from aitbc.rate_limiting import RateLimitMiddleware
from .config import settings
from .config import settings, validated_cors_origins
from .exceptions import register_exception_handlers
from .lifespan import lifespan
from .middleware import register_middleware
@@ -21,7 +21,7 @@ def create_app() -> FastAPI:
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=_validated_cors_origins(settings.cors_origins),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],

View File

@@ -10,7 +10,7 @@ import uuid
from typing import Any, Dict, Optional, List
from datetime import datetime, timezone, timedelta
from fastapi import APIRouter, HTTPException, status, Request
from fastapi import APIRouter, Depends, HTTPException, status, Request
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, Field, model_validator
from sqlmodel import select, delete
@@ -52,34 +52,32 @@ def get_authenticated_address(request: Request, credentials: Optional[HTTPAuthor
# Check for X-Wallet-Address header (API key authentication)
wallet_address = request.headers.get("X-Wallet-Address")
if wallet_address:
# Validate address format (basic check)
if not wallet_address.startswith("0x") or len(wallet_address) != 42:
_logger.warning(f"Invalid wallet address format in X-Wallet-Address header: {wallet_address}")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Invalid wallet address format"
)
if os.getenv("TRUST_X_WALLET_ADDRESS", "false").lower() != "true":
_logger.warning("Rejected untrusted X-Wallet-Address header")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="X-Wallet-Address header is not trusted without explicit server configuration"
)
_logger.debug(f"Authenticated via X-Wallet-Address header: {wallet_address}")
return wallet_address
# Check for JWT Bearer token
if credentials and credentials.scheme == "Bearer":
# In a full implementation, this would validate the JWT token
# For now, we'll extract a wallet address from the token if present
# This is a placeholder for proper JWT validation
token = credentials.credentials
_logger.debug(f"JWT token provided (validation not yet implemented)")
# TODO: Implement proper JWT validation and address extraction
# For now, raise an error to require proper implementation
_logger.warning("JWT authentication attempted but not supported")
raise HTTPException(
status_code=status.HTTP_501_NOT_IMPLEMENTED,
detail="JWT authentication not yet implemented. Use X-Wallet-Address header."
status_code=status.HTTP_401_UNAUTHORIZED,
detail="JWT authentication is not supported. Use X-Wallet-Address header with TRUST_X_WALLET_ADDRESS=true for trusted internal requests."
)
# Development mode fallback
if os.getenv("DEV_MODE", "false").lower() == "true":
_logger.warning("Using development mode fallback for authentication - returning zero address")
return "0x0000000000000000000000000000000000000000"
_logger.warning("Rejected unauthenticated request in development mode")
# No valid authentication found
raise HTTPException(
@@ -1739,17 +1737,22 @@ async def submit_arbitration_vote(
try:
# Get authenticated address from request
arbitrator_address = get_authenticated_address(http_request, credentials)
# TODO: Implement actual smart contract interaction with arbitrator authorization check
# For now, validate that we have a real address (not zero address unless in dev mode)
# Reject zero address in all modes - this is a sensitive arbitration operation
if arbitrator_address == "0x0000000000000000000000000000000000000000":
_logger.warning("Vote submission attempted with zero address - may be in dev mode")
_logger.error("Vote submission attempted with zero address - rejected")
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Zero address is not allowed for arbitration operations"
)
return SubmitArbitrationVoteResponse(
success=True,
status="Submitted",
message=f"Vote submitted successfully for dispute {request.dispute_id}"
)
except HTTPException:
raise
except Exception as e:
_logger.error(f"Error submitting arbitration vote: {e}")
raise HTTPException(status_code=500, detail=f"Failed to submit vote: {str(e)}")

View File

@@ -22,7 +22,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -20,7 +20,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -21,7 +21,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -74,7 +74,16 @@ app = FastAPI(
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],

View File

@@ -466,7 +466,16 @@ app = FastAPI(
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],

View File

@@ -21,7 +21,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -21,7 +21,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -21,7 +21,16 @@ app = FastAPI(
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=[
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],

View File

@@ -5,6 +5,7 @@ Miners register GPU offerings, choose chains, and confirm deals
"""
import json
import os
import uuid
from datetime import datetime, timedelta
from typing import Dict, List, Any, Optional
@@ -14,6 +15,28 @@ from fastapi.responses import JSONResponse
from pydantic import BaseModel
import uvicorn
DEFAULT_CORS_ORIGINS = [
"http://localhost:8001",
"http://localhost:8011",
"http://localhost:8016",
"http://localhost:9001",
"http://127.0.0.1:8001",
"http://127.0.0.1:8011",
"http://127.0.0.1:8016",
"http://127.0.0.1:9001",
]
def get_cors_origins() -> List[str]:
raw_origins = os.getenv("AITBC_MARKETPLACE_CORS_ORIGINS")
if not raw_origins:
return DEFAULT_CORS_ORIGINS
origins = [origin.strip() for origin in raw_origins.split(",") if origin.strip()]
if "*" in origins:
raise ValueError("Wildcard CORS origins are not allowed when credentials are enabled")
return origins
app = FastAPI(
title="AITBC Agent-First GPU Marketplace",
description="GPU trading marketplace where miners register offerings and confirm deals",
@@ -23,7 +46,7 @@ app = FastAPI(
# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_origins=get_cors_origins(),
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],