# IndieAuth Endpoint Discovery Security Analysis

## Executive Summary

This document analyzes the security implications of implementing IndieAuth endpoint discovery correctly, contrasting it with the fundamentally flawed approach of hardcoding endpoints.

## The Critical Error: Hardcoded Endpoints

### What Was Wrong

```ini
# FATALLY FLAWED - Breaks IndieAuth completely
TOKEN_ENDPOINT=https://tokens.indieauth.com/token
```

### Why It's a Security Disaster

1. **Single Point of Failure**: If the hardcoded endpoint is compromised, ALL users are affected
2. **No User Control**: Users cannot change providers if security issues arise
3. **Trust Concentration**: Forces all users to trust a single provider
4. **Not IndieAuth**: This isn't IndieAuth at all - it's just OAuth with extra steps
5. **Violates User Sovereignty**: Users don't control their own authentication

## The Correct Approach: Dynamic Discovery

### Security Model

```
User Identity URL → Endpoint Discovery → Provider Verification
     (User Controls)     (Dynamic)        (User's Choice)
```

### Security Benefits

1. **Distributed Trust**: No single provider compromise affects all users
2. **User Control**: Users can switch providers instantly if needed
3. **Provider Independence**: Each user's security is independent
4. **Immediate Revocation**: Users can revoke by changing profile links
5. **True Decentralization**: No central authority

## Threat Analysis

### Threat 1: Profile URL Hijacking

**Attack Vector**: Attacker gains control of user's profile URL

**Impact**: Can redirect authentication to attacker's endpoints

**Mitigations**:
- Profile URL must use HTTPS
- Verify SSL certificates
- Monitor for unexpected endpoint changes
- Cache endpoints with reasonable TTL

### Threat 2: Endpoint Discovery Manipulation

**Attack Vector**: MITM attack during endpoint discovery

**Impact**: Could redirect to malicious endpoints

**Mitigations**:
```python
def discover_endpoints(profile_url: str) -> dict:
    # CRITICAL: Enforce HTTPS
    if not profile_url.startswith('https://'):
        raise SecurityError("Profile URL must use HTTPS")

    # Verify SSL certificates
    response = requests.get(
        profile_url,
        verify=True,  # Enforce certificate validation
        timeout=5
    )

    # Validate discovered endpoints
    endpoints = extract_endpoints(response)
    for endpoint_url in endpoints.values():
        if not endpoint_url.startswith('https://'):
            raise SecurityError(f"Endpoint must use HTTPS: {endpoint_url}")

    return endpoints
```

### Threat 3: Cache Poisoning

**Attack Vector**: Attacker poisons endpoint cache with malicious URLs

**Impact**: Subsequent requests use attacker's endpoints

**Mitigations**:
```python
class SecureEndpointCache:
    def store_endpoints(self, profile_url: str, endpoints: dict):
        # Validate before caching
        self._validate_profile_url(profile_url)
        self._validate_endpoints(endpoints)

        # Store with integrity check
        cache_entry = {
            'endpoints': endpoints,
            'stored_at': time.time(),
            'checksum': self._calculate_checksum(endpoints)
        }
        self.cache[profile_url] = cache_entry

    def get_endpoints(self, profile_url: str) -> dict:
        entry = self.cache.get(profile_url)
        if entry:
            # Verify integrity
            if self._calculate_checksum(entry['endpoints']) != entry['checksum']:
                # Cache corruption detected
                del self.cache[profile_url]
                raise SecurityError("Cache integrity check failed")
        return entry['endpoints']
```

### Threat 4: Redirect Attacks

**Attack Vector**: Malicious redirects during discovery

**Impact**: Could redirect to attacker-controlled endpoints

**Mitigations**:
```python
def fetch_with_redirect_limit(url: str, max_redirects: int = 5):
    redirect_count = 0
    visited = set()

    while redirect_count < max_redirects:
        if url in visited:
            raise SecurityError("Redirect loop detected")
        visited.add(url)

        response = requests.get(url, allow_redirects=False)

        if response.status_code in (301, 302, 303, 307, 308):
            redirect_url = response.headers.get('Location')

            # Validate redirect target
            if not redirect_url.startswith('https://'):
                raise SecurityError("Redirect to non-HTTPS URL blocked")

            url = redirect_url
            redirect_count += 1
        else:
            return response

    raise SecurityError("Too many redirects")
```

### Threat 5: Token Replay Attacks

**Attack Vector**: Intercepted token reused

**Impact**: Unauthorized access

**Mitigations**:
- Always use HTTPS for token transmission
- Implement token expiration
- Cache token verification results briefly
- Use nonce/timestamp validation

## Security Requirements

### 1. HTTPS Enforcement

```python
class HTTPSEnforcer:
    def validate_url(self, url: str, context: str):
        """Enforce HTTPS for all security-critical URLs"""

        parsed = urlparse(url)

        # Development exception (with warning)
        if self.development_mode and parsed.hostname in ['localhost', '127.0.0.1']:
            logger.warning(f"Allowing HTTP in development for {context}: {url}")
            return

        # Production: HTTPS required
        if parsed.scheme != 'https':
            raise SecurityError(f"HTTPS required for {context}: {url}")
```

### 2. Certificate Validation

```python
def create_secure_http_client():
    """Create HTTP client with proper security settings"""

    return httpx.Client(
        verify=True,  # Always verify SSL certificates
        follow_redirects=False,  # Handle redirects manually
        timeout=httpx.Timeout(
            connect=5.0,
            read=10.0,
            write=10.0,
            pool=10.0
        ),
        limits=httpx.Limits(
            max_connections=100,
            max_keepalive_connections=20
        ),
        headers={
            'User-Agent': 'StarPunk/1.0 (+https://starpunk.example.com/)'
        }
    )
```

### 3. Input Validation

```python
def validate_endpoint_response(response: dict, expected_me: str):
    """Validate token verification response"""

    # Required fields
    if 'me' not in response:
        raise ValidationError("Missing 'me' field in response")

    # URL normalization and comparison
    normalized_me = normalize_url(response['me'])
    normalized_expected = normalize_url(expected_me)

    if normalized_me != normalized_expected:
        raise ValidationError(
            f"Token 'me' mismatch: expected {normalized_expected}, "
            f"got {normalized_me}"
        )

    # Scope validation
    scopes = response.get('scope', '').split()
    if 'create' not in scopes:
        raise ValidationError("Token missing required 'create' scope")

    return True
```

### 4. Rate Limiting

```python
class DiscoveryRateLimiter:
    """Prevent discovery abuse"""

    def __init__(self, max_per_minute: int = 60):
        self.requests = defaultdict(list)
        self.max_per_minute = max_per_minute

    def check_rate_limit(self, profile_url: str):
        now = time.time()
        minute_ago = now - 60

        # Clean old entries
        self.requests[profile_url] = [
            t for t in self.requests[profile_url]
            if t > minute_ago
        ]

        # Check limit
        if len(self.requests[profile_url]) >= self.max_per_minute:
            raise RateLimitError(f"Too many discovery requests for {profile_url}")

        # Record request
        self.requests[profile_url].append(now)
```

## Implementation Checklist

### Discovery Security

- [ ] Enforce HTTPS for profile URLs
- [ ] Validate SSL certificates
- [ ] Limit redirect chains to 5
- [ ] Detect redirect loops
- [ ] Validate discovered endpoint URLs
- [ ] Implement discovery rate limiting
- [ ] Log all discovery attempts
- [ ] Handle timeouts gracefully

### Token Verification Security

- [ ] Use HTTPS for all token endpoints
- [ ] Validate token endpoint responses
- [ ] Check 'me' field matches expected
- [ ] Verify required scopes present
- [ ] Hash tokens before caching
- [ ] Implement cache expiration
- [ ] Use constant-time comparisons
- [ ] Log verification failures

### Cache Security

- [ ] Validate data before caching
- [ ] Implement cache size limits
- [ ] Use TTL for all cache entries
- [ ] Clear cache on configuration changes
- [ ] Protect against cache poisoning
- [ ] Monitor cache hit/miss rates
- [ ] Implement cache integrity checks

### Error Handling

- [ ] Never expose internal errors
- [ ] Log security events
- [ ] Rate limit error responses
- [ ] Implement proper timeouts
- [ ] Handle network failures gracefully
- [ ] Provide clear user messages

## Security Testing

### Test Scenarios

1. **HTTPS Downgrade Attack**
   - Try to use HTTP endpoints
   - Verify rejection

2. **Invalid Certificates**
   - Test with self-signed certs
   - Test with expired certs
   - Verify rejection

3. **Redirect Attacks**
   - Test redirect loops
   - Test excessive redirects
   - Test HTTP redirects
   - Verify proper handling

4. **Cache Poisoning**
   - Attempt to inject invalid data
   - Verify cache validation

5. **Token Manipulation**
   - Modify token before verification
   - Test expired tokens
   - Test tokens with wrong 'me'
   - Verify proper rejection

## Monitoring and Alerting

### Security Metrics

```python
# Track these metrics
security_metrics = {
    'discovery_failures': Counter(),
    'https_violations': Counter(),
    'certificate_errors': Counter(),
    'redirect_limit_exceeded': Counter(),
    'cache_poisoning_attempts': Counter(),
    'token_verification_failures': Counter(),
    'rate_limit_violations': Counter()
}
```

### Alert Conditions

- Multiple discovery failures for same profile
- Sudden increase in HTTPS violations
- Certificate validation failures
- Cache poisoning attempts detected
- Unusual token verification patterns

## Incident Response

### If Endpoint Compromise Suspected

1. Clear endpoint cache immediately
2. Force re-discovery of all endpoints
3. Alert affected users
4. Review logs for suspicious patterns
5. Document incident

### If Cache Poisoning Detected

1. Clear entire cache
2. Review cache validation logic
3. Identify attack vector
4. Implement additional validation
5. Monitor for recurrence

## Conclusion

Dynamic endpoint discovery is not just correct according to the IndieAuth specification - it's also more secure than hardcoded endpoints. By allowing users to control their authentication infrastructure, we:

1. Eliminate single points of failure
2. Enable immediate provider switching
3. Distribute security responsibility
4. Maintain true decentralization
5. Respect user sovereignty

The complexity of proper implementation is justified by the security and flexibility benefits. This is what IndieAuth is designed to provide, and we must implement it correctly.

---

**Document Version**: 1.0
**Created**: 2024-11-24
**Classification**: Security Architecture
**Review Schedule**: Quarterly