GT AI OS Community Edition v2.0.33

Security hardening release addressing CodeQL and Dependabot alerts:

- Fix stack trace exposure in error responses
- Add SSRF protection with DNS resolution checking
- Implement proper URL hostname validation (replaces substring matching)
- Add centralized path sanitization to prevent path traversal
- Fix ReDoS vulnerability in email validation regex
- Improve HTML sanitization in validation utilities
- Fix capability wildcard matching in auth utilities
- Update glob dependency to address CVE
- Add CodeQL suppression comments for verified false positives

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
HackWeasel
2025-12-12 17:04:45 -05:00
commit b9dfb86260
746 changed files with 232071 additions and 0 deletions

View File

@@ -0,0 +1,457 @@
"""
Capability-Based Authentication for GT 2.0 Resource Cluster
Implements JWT capability token verification with:
- Cryptographic signature validation
- Fine-grained resource permissions
- Rate limiting and constraints enforcement
- Tenant isolation validation
- Zero external dependencies
GT 2.0 Security Principles:
- Self-contained: No external auth services
- Stateless: All permissions in JWT token
- Cryptographic: RSA signature verification
- Isolated: Perfect tenant separation
"""
import jwt
import logging
from datetime import datetime, timezone
from typing import Dict, Any, List, Optional
from dataclasses import dataclass
from enum import Enum
from fastapi import HTTPException, Depends, Header
from app.core.config import get_settings
logger = logging.getLogger(__name__)
settings = get_settings()
class CapabilityError(Exception):
"""Capability authentication error"""
pass
class ResourceType(str, Enum):
"""Resource types in GT 2.0"""
LLM = "llm"
EMBEDDING = "embedding"
VECTOR_STORAGE = "vector_storage"
EXTERNAL_SERVICES = "external_services"
ADMIN = "admin"
class ActionType(str, Enum):
"""Action types for resources"""
READ = "read"
WRITE = "write"
EXECUTE = "execute"
ADMIN = "admin"
@dataclass
class Capability:
"""Individual capability definition"""
resource: ResourceType
actions: List[ActionType]
constraints: Dict[str, Any]
expires_at: Optional[datetime] = None
def allows_action(self, action: ActionType) -> bool:
"""Check if capability allows specific action"""
return action in self.actions
def is_expired(self) -> bool:
"""Check if capability is expired"""
if not self.expires_at:
return False
return datetime.now(timezone.utc) > self.expires_at
def check_constraint(self, constraint_name: str, value: Any) -> bool:
"""Check if value satisfies constraint"""
if constraint_name not in self.constraints:
return True # No constraint means allowed
constraint_value = self.constraints[constraint_name]
if constraint_name == "max_tokens":
return value <= constraint_value
elif constraint_name == "allowed_models":
return value in constraint_value
elif constraint_name == "max_requests_per_hour":
# This would be checked separately with rate limiting
return True
elif constraint_name == "allowed_tenants":
return value in constraint_value
return True
@dataclass
class CapabilityToken:
"""Parsed capability token"""
subject: str
tenant_id: str
capabilities: List[Capability]
issued_at: datetime
expires_at: datetime
issuer: str
token_version: str
def has_capability(self, resource: ResourceType, action: ActionType) -> bool:
"""Check if token has specific capability"""
for cap in self.capabilities:
if cap.resource == resource and cap.allows_action(action) and not cap.is_expired():
return True
return False
def get_capability(self, resource: ResourceType) -> Optional[Capability]:
"""Get capability for specific resource"""
for cap in self.capabilities:
if cap.resource == resource and not cap.is_expired():
return cap
return None
def is_expired(self) -> bool:
"""Check if entire token is expired"""
return datetime.now(timezone.utc) > self.expires_at
class CapabilityAuthenticator:
"""
Handles capability token verification and authorization.
Uses JWT tokens with embedded permissions for stateless authentication.
"""
def __init__(self):
self.settings = get_settings()
# In production, this would be loaded from secure storage
# For development, using the secret key
self.secret_key = self.settings.secret_key
self.algorithm = "HS256" # TODO: Upgrade to RS256 with public/private keys
logger.info("Capability authenticator initialized")
async def verify_token(self, token: str) -> CapabilityToken:
"""
Verify and parse capability token.
Args:
token: JWT capability token
Returns:
Parsed capability token
Raises:
CapabilityError: If token is invalid or expired
"""
try:
# Decode JWT token
payload = jwt.decode(
token,
self.secret_key,
algorithms=[self.algorithm],
audience="gt2-resource-cluster"
)
# Validate required fields
required_fields = ["sub", "tenant_id", "capabilities", "iat", "exp", "iss"]
for field in required_fields:
if field not in payload:
raise CapabilityError(f"Missing required field: {field}")
# Parse timestamps
issued_at = datetime.fromtimestamp(payload["iat"], tz=timezone.utc)
expires_at = datetime.fromtimestamp(payload["exp"], tz=timezone.utc)
# Check token expiration
if datetime.now(timezone.utc) > expires_at:
raise CapabilityError("Token has expired")
# Parse capabilities
capabilities = []
for cap_data in payload["capabilities"]:
try:
capability = Capability(
resource=ResourceType(cap_data["resource"]),
actions=[ActionType(action) for action in cap_data["actions"]],
constraints=cap_data.get("constraints", {}),
expires_at=datetime.fromtimestamp(
cap_data["expires_at"], tz=timezone.utc
) if cap_data.get("expires_at") else None
)
capabilities.append(capability)
except (KeyError, ValueError) as e:
logger.warning(f"Invalid capability in token: {e}")
# Skip invalid capabilities rather than rejecting entire token
continue
# Create capability token
capability_token = CapabilityToken(
subject=payload["sub"],
tenant_id=payload["tenant_id"],
capabilities=capabilities,
issued_at=issued_at,
expires_at=expires_at,
issuer=payload["iss"],
token_version=payload.get("token_version", "1.0")
)
logger.debug(f"Capability token verified for {capability_token.subject}")
return capability_token
except jwt.ExpiredSignatureError:
raise CapabilityError("Token has expired")
except jwt.InvalidTokenError as e:
raise CapabilityError(f"Invalid token: {e}")
except Exception as e:
logger.error(f"Token verification failed: {e}")
raise CapabilityError(f"Token verification failed: {e}")
async def check_resource_access(
self,
capability_token: CapabilityToken,
resource: ResourceType,
action: ActionType,
constraints: Optional[Dict[str, Any]] = None
) -> bool:
"""
Check if token allows access to resource with specific action.
Args:
capability_token: Verified capability token
resource: Resource type to access
action: Action to perform
constraints: Additional constraints to check
Returns:
True if access is allowed
Raises:
CapabilityError: If access is denied
"""
try:
# Check token expiration
if capability_token.is_expired():
raise CapabilityError("Token has expired")
# Find matching capability
capability = capability_token.get_capability(resource)
if not capability:
raise CapabilityError(f"No capability for resource: {resource}")
# Check action permission
if not capability.allows_action(action):
raise CapabilityError(f"Action {action} not allowed for resource {resource}")
# Check constraints if provided
if constraints:
for constraint_name, value in constraints.items():
if not capability.check_constraint(constraint_name, value):
raise CapabilityError(
f"Constraint violation: {constraint_name} = {value}"
)
return True
except CapabilityError:
raise
except Exception as e:
logger.error(f"Resource access check failed: {e}")
raise CapabilityError(f"Access check failed: {e}")
# Global authenticator instance
capability_authenticator = CapabilityAuthenticator()
async def verify_capability_token(token: str) -> Dict[str, Any]:
"""
Verify capability token and return payload.
Args:
token: JWT capability token
Returns:
Token payload as dictionary
Raises:
CapabilityError: If token is invalid
"""
capability_token = await capability_authenticator.verify_token(token)
return {
"sub": capability_token.subject,
"tenant_id": capability_token.tenant_id,
"capabilities": [
{
"resource": cap.resource.value,
"actions": [action.value for action in cap.actions],
"constraints": cap.constraints
}
for cap in capability_token.capabilities
],
"iat": capability_token.issued_at.timestamp(),
"exp": capability_token.expires_at.timestamp(),
"iss": capability_token.issuer,
"token_version": capability_token.token_version
}
async def get_current_capability(
authorization: str = Header(..., description="Bearer token")
) -> Dict[str, Any]:
"""
FastAPI dependency to get current capability from Authorization header.
Args:
authorization: Authorization header with Bearer token
Returns:
Capability payload
Raises:
HTTPException: If authentication fails
"""
try:
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Invalid authorization header format"
)
token = authorization[7:] # Remove "Bearer " prefix
payload = await verify_capability_token(token)
return payload
except CapabilityError as e:
logger.warning(f"Capability authentication failed: {e}")
raise HTTPException(status_code=401, detail=str(e))
except Exception as e:
logger.error(f"Authentication error: {e}")
raise HTTPException(status_code=500, detail="Authentication error")
async def require_capability(
resource: ResourceType,
action: ActionType,
constraints: Optional[Dict[str, Any]] = None
):
"""
FastAPI dependency to require specific capability.
Args:
resource: Required resource type
action: Required action type
constraints: Additional constraints to check
Returns:
Dependency function
"""
async def _check_capability(
capability_payload: Dict[str, Any] = Depends(get_current_capability)
) -> Dict[str, Any]:
try:
# Reconstruct capability token from payload
capabilities = []
for cap_data in capability_payload["capabilities"]:
capability = Capability(
resource=ResourceType(cap_data["resource"]),
actions=[ActionType(action) for action in cap_data["actions"]],
constraints=cap_data["constraints"]
)
capabilities.append(capability)
capability_token = CapabilityToken(
subject=capability_payload["sub"],
tenant_id=capability_payload["tenant_id"],
capabilities=capabilities,
issued_at=datetime.fromtimestamp(capability_payload["iat"], tz=timezone.utc),
expires_at=datetime.fromtimestamp(capability_payload["exp"], tz=timezone.utc),
issuer=capability_payload["iss"],
token_version=capability_payload["token_version"]
)
# Check required capability
await capability_authenticator.check_resource_access(
capability_token=capability_token,
resource=resource,
action=action,
constraints=constraints
)
return capability_payload
except CapabilityError as e:
logger.warning(f"Capability check failed: {e}")
raise HTTPException(status_code=403, detail=str(e))
except Exception as e:
logger.error(f"Capability check error: {e}")
raise HTTPException(status_code=500, detail="Authorization error")
return _check_capability
# Convenience functions for common capability checks
async def require_llm_capability(
capability_payload: Dict[str, Any] = Depends(
require_capability(ResourceType.LLM, ActionType.EXECUTE)
)
) -> Dict[str, Any]:
"""Require LLM execution capability"""
return capability_payload
async def require_embedding_capability(
capability_payload: Dict[str, Any] = Depends(
require_capability(ResourceType.EMBEDDING, ActionType.EXECUTE)
)
) -> Dict[str, Any]:
"""Require embedding generation capability"""
return capability_payload
async def require_admin_capability(
capability_payload: Dict[str, Any] = Depends(
require_capability(ResourceType.ADMIN, ActionType.ADMIN)
)
) -> Dict[str, Any]:
"""Require admin capability"""
return capability_payload
async def verify_capability_token_dependency(
authorization: str = Header(..., description="Bearer token")
) -> Dict[str, Any]:
"""
FastAPI dependency for ChromaDB MCP API that verifies capability token.
Returns token payload with raw_token field for service layer use.
"""
try:
if not authorization.startswith("Bearer "):
raise HTTPException(
status_code=401,
detail="Invalid authorization header format"
)
token = authorization[7:] # Remove "Bearer " prefix
payload = await verify_capability_token(token)
# Add raw token for service layer
payload["raw_token"] = token
return payload
except CapabilityError as e:
logger.warning(f"Capability authentication failed: {e}")
raise HTTPException(status_code=401, detail=str(e))
except Exception as e:
logger.error(f"Authentication error: {e}")
raise HTTPException(status_code=500, detail="Authentication error")