# Gap Analysis & Improvement Roadmap

## The Iron Vault - Aperion FSAL v1.0.0

This document identifies security gaps, missing features, and areas for improvement
based on industry best practices, security research, and production deployment standards.

---

## 🔴 CRITICAL GAPS (Security)

### 1. Missing Directory fsync After Rename

**Current State:** `atomic_write()` fsyncs the file but NOT the parent directory.

**Risk:** On power loss, the rename operation may not be persisted to disk,
leading to data loss even though the file content was fsynced.

**Fix Required:**
```python
# After os.replace(tmp_path, p)
dir_fd = os.open(p.parent, os.O_DIRECTORY)
try:
    os.fsync(dir_fd)
finally:
    os.close(dir_fd)
```

**Reference:** [python-atomicwrites documentation](https://python-atomicwrites.readthedocs.io/en/latest/)

---

### 2. TOCTOU Race Conditions in Path Validation

**Current State:** Path is validated, then used in a separate operation.

**Risk:** Between validation and use, an attacker could swap files/directories,
especially in multi-user environments.

**Example Vulnerable Pattern:**
```python
p = resolve_safe_path(path, root=root)  # Check
if not p.exists():  # Check
    raise FileNotFoundError(str(p))
with p.open("rb") as f:  # Use - race window!
```

**Fix Required:** Use EAFP (Easier to Ask Forgiveness than Permission):
```python
try:
    with p.open("rb") as f:
        return f.read()
except FileNotFoundError:
    raise
except PermissionError:
    raise SandboxViolation(...) from None
```

**Reference:** [CWE-367: TOCTOU Race Condition](https://cwe.mitre.org/data/definitions/367.html)

---

### 3. Symlink Resolution Creates Escape Vector

**Current State:** `Path.resolve()` follows symlinks before sandbox check.

**Risk:** A symlink at `sandbox/link -> /etc/passwd` resolves to `/etc/passwd`,
which then fails the sandbox check. But this leaks information (the target exists).

**Deeper Risk:** If an attacker can create symlinks INSIDE the sandbox that
point outside, they could potentially exploit timing windows.

**Fix Required:** Use `os.O_NOFOLLOW` flag where possible, or validate the
path exists within sandbox WITHOUT resolving symlinks first.

```python
# Check symlink BEFORE resolving
if candidate.is_symlink():
    raise SandboxViolation("Symlinks not allowed")
```

---

### 4. No Rate Limiting

**Current State:** No protection against API abuse.

**Risk:** Denial of service through:
- Rapid read/write requests exhausting disk I/O
- Large file uploads consuming memory
- Directory listing with massive entry counts

**Fix Required:** Add `slowapi` or `fastapi-limiter`:
```python
from slowapi import Limiter
from slowapi.util import get_remote_address

limiter = Limiter(key_func=get_remote_address)
app.state.limiter = limiter

@app.post("/v1/fs/write")
@limiter.limit("100/minute")
def fs_write(body: WriteIn, ...):
```

---

### 5. No Request Size Limits

**Current State:** Base64 content in `WriteIn.content_b64` has no size limit.

**Risk:** Memory exhaustion attack by sending massive base64 payloads.

**Fix Required:**
```python
class WriteIn(PathIn):
    content_b64: str = Field(
        ...,
        max_length=100_000_000,  # ~75MB decoded
        description="Base64-encoded file content"
    )
```

Also add Uvicorn limit:
```python
uvicorn.run(..., limit_concurrency=100, limit_max_requests=10000)
```

---

## 🟠 HIGH PRIORITY GAPS

### 6. No Structured Logging / Audit Trail

**Current State:** Using Python's basic logging.

**Gap:** No structured JSON logging, no correlation IDs, no audit trail for
security-relevant operations.

**Fix Required:**
```python
import structlog

logger = structlog.get_logger()

# In endpoint:
logger.info(
    "file_written",
    path=body.path,
    size=len(content),
    client_ip=request.client.host,
    correlation_id=request.headers.get("X-Correlation-ID"),
)
```

---

### 7. No Security Headers

**Current State:** Only CORS middleware configured.

**Gap:** Missing security headers (HSTS, X-Content-Type-Options, etc.)

**Fix Required:**
```python
from starlette.middleware.base import BaseHTTPMiddleware

class SecurityHeadersMiddleware(BaseHTTPMiddleware):
    async def dispatch(self, request, call_next):
        response = await call_next(request)
        response.headers["X-Content-Type-Options"] = "nosniff"
        response.headers["X-Frame-Options"] = "DENY"
        response.headers["X-XSS-Protection"] = "1; mode=block"
        response.headers["Strict-Transport-Security"] = "max-age=31536000"
        return response
```

---

### 8. Dockerfile Should Use Gunicorn

**Current State:** Docker runs Uvicorn directly.

**Gap:** Single worker, no process management, poor crash recovery.

**Fix Required:**
```dockerfile
CMD ["gunicorn", "-w", "4", "-k", "uvicorn.workers.UvicornWorker", \
     "aperion_fsal.service.app:app", "--bind", "0.0.0.0:4848"]
```

Add to dependencies:
```toml
dependencies = [
    ...
    "gunicorn>=21.0.0",
]
```

---

### 9. No Request Timeout Protection

**Current State:** No timeout on operations.

**Risk:** Slow clients or large file operations could tie up workers indefinitely.

**Fix Required:**
```python
# In uvicorn/gunicorn config
timeout = 30  # seconds

# Or use anyio.fail_after in handlers
import anyio
async with anyio.fail_after(30):
    content = await read_file(...)
```

---

### 10. API Documentation Exposed in Production

**Current State:** `/docs` and `/redoc` always available.

**Risk:** Information disclosure about API structure.

**Fix Required:**
```python
docs_url = "/docs" if os.environ.get("APERION_ENV") == "dev" else None
redoc_url = "/redoc" if os.environ.get("APERION_ENV") == "dev" else None

app = FastAPI(..., docs_url=docs_url, redoc_url=redoc_url)
```

---

## 🟡 MEDIUM PRIORITY GAPS

### 11. No File Locking for Concurrent Access

**Current State:** Multiple concurrent writes to same file could corrupt.

**Gap:** No advisory or mandatory locking.

**Fix Required:** Use `fcntl.flock()` or `filelock` library.

---

### 12. No Content-Type Validation

**Current State:** Any base64 content accepted for any path.

**Gap:** No MIME type detection or extension validation.

**Potential Fix:** Optional content-type validation with `python-magic`.

---

### 13. No Disk Space Checks

**Current State:** Writes proceed until disk full error.

**Gap:** Could fill disk causing system-wide issues.

**Fix Required:**
```python
import shutil

def check_disk_space(path: Path, required_bytes: int) -> bool:
    usage = shutil.disk_usage(path)
    return usage.free > required_bytes * 1.1  # 10% buffer
```

---

### 14. Token Comparison Not Constant-Time

**Current State:** `if token != fsal_token:` uses regular string comparison.

**Risk:** Timing attack could reveal token value character-by-character.

**Fix Required:**
```python
import hmac

if not hmac.compare_digest(token.encode(), fsal_token.encode()):
    raise HTTPException(status_code=403, ...)
```

---

### 15. No Health Check for Sandbox Writability

**Current State:** `/healthz` checks config but not actual writability.

**Fix Required:**
```python
@app.get("/healthz")
def healthz():
    # Test actual write capability
    try:
        test_path = get_sandbox_root() / ".healthcheck"
        test_path.write_bytes(b"ok")
        test_path.unlink()
        writable = True
    except Exception:
        writable = False
    
    return {"ok": writable, ...}
```

---

## 🟢 NICE-TO-HAVE IMPROVEMENTS

### 16. Async File Operations

**Current State:** Synchronous file I/O blocks the event loop.

**Improvement:** Use `aiofiles` for non-blocking operations:
```python
import aiofiles

async def read_async(path: Path) -> bytes:
    async with aiofiles.open(path, 'rb') as f:
        return await f.read()
```

---

### 17. Metrics Endpoint (Prometheus)

**Improvement:** Add `/metrics` for observability:
```python
from prometheus_client import Counter, Histogram

REQUESTS = Counter('fsal_requests_total', 'Total requests', ['method', 'endpoint'])
LATENCY = Histogram('fsal_request_latency_seconds', 'Request latency')
```

---

### 18. Streaming Support for Large Files

**Current State:** Entire file loaded into memory.

**Improvement:** Support chunked/streaming responses for large files.

---

### 19. OpenTelemetry Tracing

**Improvement:** Add distributed tracing for integration with Aperion.

---

### 20. Configurable File Size Limits

**Current State:** No file size limits.

**Improvement:** Environment variable for max file size:
```bash
FSAL_MAX_FILE_SIZE=104857600  # 100MB
```

---

## Summary Priority Matrix

| Priority | Count | Category |
|----------|-------|----------|
| 🔴 Critical | 5 | Security vulnerabilities |
| 🟠 High | 5 | Production hardening |
| 🟡 Medium | 5 | Robustness |
| 🟢 Nice-to-have | 5 | Enhancements |

---

## Recommended Implementation Order

1. **Phase 1 (Security):** Fix #1, #4, #5, #14 (directory fsync, rate limit, request size, constant-time compare)
2. **Phase 2 (Hardening):** Fix #2, #3, #8, #10 (TOCTOU, symlinks, gunicorn, docs protection)
3. **Phase 3 (Production):** Fix #6, #7, #9, #15 (structured logging, headers, timeouts, health)
4. **Phase 4 (Robustness):** Fix #11-13 (locking, validation, disk space)
5. **Phase 5 (Enhancements):** Items #16-20

---

*Last updated: 2026-02-08*
*Based on security research from OWASP, PortSwigger, and Python security best practices*
