Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,34 @@ app.add_middleware(SecurityHeadersMiddleware, override=True)

---

### Stripping Server Headers

To strip `Server` and `X-Powered-By` headers emitted by application routes (to mitigate server fingerprinting), set `strip_server_headers=True`:

```python
app.add_middleware(SecurityHeadersMiddleware, strip_server_headers=True)
```

#### ⚠️ Important Note on Uvicorn Server Header

When running behind **Uvicorn**, pure ASGI middleware runs *after* Uvicorn appends its server-level default headers (such as `Server: uvicorn`). Therefore, `strip_server_headers=True` will filter out headers from the application layer, but Uvicorn's own wire-level `Server` header will still be sent unless disabled directly in Uvicorn:

**Programmatically:**
```python
# Run uvicorn programmatically with server_header=False
import uvicorn

uvicorn.run(app, host="127.0.0.1", port=8000, server_header=False)
```

**Or via the CLI flag:**
```bash
# Pass --no-server-header flag in CLI
uvicorn app:app --no-server-header
```

---

## 🧪 Testing

Run the test suite locally with `pytest`:
Expand Down Expand Up @@ -185,3 +213,4 @@ If this project helps you secure your FastAPI applications, consider buying a co
## 📄 License

This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

34 changes: 34 additions & 0 deletions tests/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -131,3 +131,37 @@ async def ws_endpoint(websocket: WebSocket):
ws.send_text("hello-asgi")
data = ws.receive_text()
assert data == "echo:hello-asgi"


def test_strip_server_headers_enabled(create_app):
"""When strip_server_headers=True, Server and X-Powered-By should be stripped."""
app = create_app(strip_server_headers=True)
client = TestClient(app)

response = client.get("/server-header")
assert response.status_code == 200
assert "server" not in response.headers
assert "x-powered-by" not in response.headers


def test_strip_server_headers_disabled_by_default(create_app):
"""When strip_server_headers is False (default), Server and X-Powered-By should be preserved."""
app = create_app()
client = TestClient(app)

response = client.get("/server-header")
assert response.status_code == 200
assert response.headers.get("server") == "uvicorn"
assert response.headers.get("x-powered-by") == "FastAPI"


def test_strip_server_headers_via_config(create_app):
"""When strip_server_headers=True is set via SecurityHeadersConfig."""
config = SecurityHeadersConfig(strip_server_headers=True)
app = create_app(config=config)
client = TestClient(app)

response = client.get("/server-header")
assert response.status_code == 200
assert "server" not in response.headers
assert "x-powered-by" not in response.headers
Loading