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:
@@ -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=["*"],
|
||||
|
||||
@@ -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)}")
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
@@ -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=["*"],
|
||||
|
||||
Reference in New Issue
Block a user