Skip to content

feat: add security headers and Cache-Control: no-store to API responses - #254

Merged
nutellasando merged 3 commits into
pesu-dev:devfrom
nutellasando:feat/security-headers
Oct 20, 2026
Merged

nutellasando merged 3 commits into
pesu-dev:devfrom
nutellasando:feat/security-headers

Conversation

@nutellasando

@nutellasando nutellasando commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

📌 Description

  • What is the purpose of this PR?
    Adds standard HTTP security headers across all API responses and applies Cache-Control: no-store to the /authenticate endpoint.

  • What problem does it solve, or what feature does it add?
    The /authenticate endpoint returns sensitive student profile data without instructions preventing intermediate caches, proxies, or browsers from storing it. The API also previously lacked baseline security headers recommended by the OWASP REST Security Cheat Sheet. This PR adds a security_headers_middleware to:

    • Add X-Content-Type-Options: nosniff, Referrer-Policy: no-referrer, and X-Frame-Options: DENY to every response.
    • Add Cache-Control: no-store specifically to /authenticate responses (both success and error outcomes).
  • Any relevant motivation, background, or context?
    Offline integration tests have been added with monkeypatching to maintain 100% test coverage without requiring live credentials.

ℹ️ Fixes / Related Issues
Fixes: #191

🧱 Type of Change

  • 🐛 Bug fix – Non-breaking fix for a functional/logic error
  • ✨ New feature – Adds functionality without breaking existing APIs
  • ⚠️ Breaking change – Introduces backward-incompatible changes (API, schema, etc.)
  • 📝 Documentation update – README, docstrings, OpenAPI tags, etc.
  • 🧪 Test suite change – Adds/updates unit, functional, or integration tests
  • ⚙️ CI/CD pipeline update – Modifies GitHub Actions, pre-commit, or Docker build
  • 🧹 Code quality / Refactor – Improves structure, readability, or style (no functional changes)
  • 🐢 Performance improvement – Speeds up auth, profile retrieval, or reduces I/O
  • 🕵️ Debug/logging enhancement – Adds or improves logging/debug support
  • 🔧 Developer tooling – Scripts, benchmarks, local testing improvements
  • 🔒 Security fix – Addresses auth/session/data validation vulnerabilities
  • 🧰 Dependency update – Updates libraries in pyproject.toml, uv.lock

🧪 How Has This Been Tested?

  • Unit Tests (tests/unit/)
  • Functional Tests (tests/functional/)
  • Integration Tests (tests/integration/)
  • Manual Testing

⚙️ Test Configuration:

  • OS: Linux
  • Python: 3.14 via uv
  • Docker build tested

✅ Checklist

  • My code follows the CONTRIBUTING.md guidelines
  • I've performed a self-review of my changes
  • I've added/updated necessary comments and docstrings
  • I've updated relevant docs (README or endpoint docs)
  • No new warnings introduced
  • I've added tests to cover my changes
  • All tests pass locally (scripts/run_tests.py)
  • I've run linting and formatting (pre-commit run --all-files)
  • Docker image builds and runs correctly
  • Changes are backwards compatible (if applicable)
  • Feature flags or .env vars updated (if applicable)
  • I've tested across multiple environments (if applicable)
  • Benchmarks still meet expected performance (scripts/benchmark/benchmark_requests.py)

🛠️ Affected API Behaviour

  • app/app.py – Modified /authenticate route logic
  • app/pesu.py – Updated PESU Academy login or profile handling
  • app/exceptions/ – Error types, their status codes or messages
  • app/metrics/ – Metrics collection, exposition or the /metrics token
  • app/docs/ – OpenAPI (Swagger) examples and descriptions

🧩 Models

  • app/models/request.py – Input validation or request schema changes
  • app/models/response.py – Authentication response formatting
  • app/models/profile.py – Profile response schema
  • app/models/upstream.py – Parsing of PESU Academy's login and profile responses

🐳 DevOps & Config

  • Dockerfile – Changes to base image or build process
  • .github/workflows/*.yml – CI/CD pipeline or deployment updates
  • pyproject.toml / uv.lock – Dependency version changes
  • .pre-commit-config.yaml – Linting or formatting hook changes
  • .github/scripts/check_version_bump.py – The CI version check

📊 Benchmarks & Analysis

  • scripts/benchmark/benchmark_requests.py – Performance or latency measurement changes
  • scripts/benchmark/analyze_benchmark.py – Benchmark result analysis changes
  • scripts/run_tests.py – Custom test runner logic or behavior updates

🧠 Additional Notes (if applicable)

Closes #191.

Live PESU credential tests were skipped locally due to missing local secrets; offline integration tests were added to verify the headers while preserving 100% test coverage.
Handled exceptions (4xx, 502) pass through the middleware and carry headers, while unhandled 500 exceptions are rendered above Starlette HTTP middleware as per framework design.

What Changed:

  1. Explicit Issue Linking: Set Fixes: #191 under Fixes / Related Issues and explicitly added Closes #191 under Additional Notes.
  2. Proper Markdown Formatting: Restored valid markdown list items (- [x] and - [ ]) and standard markdown blockquotes (>) without broken raw HTML tags or missing line breaks.

@nutellasando
nutellasando requested review from a team and aditeyabaral as code owners October 18, 2026 12:00

@aditeyabaral aditeyabaral left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for picking this up! The middleware does what #191 asks: I checked 200/400/401/404/405/307/308, HEAD /health, /openapi.json and the playground with TestClient, and the headers are where they should be.

One change before merge (inline on app/app.py): the README says every response carries the security headers, but an unhandled 500 currently carries none, including Cache-Control: no-store on /authenticate. Please set them on that response too, so the README holds.

The rest are smaller and inline. Two points that aren't tied to a line:

  • Commit message: the commit has no body. The contribution guide asks for Conventional Commits with a body that explains why, so please add one when you push the fixes.
  • PR template: "🧰 Dependency update" is ticked, but the only pyproject.toml/uv.lock change is the version bump. Please untick it.

If another PR merges into dev before this one, you'll need to bump the version again from the new one.

Comment thread app/app.py Outdated
Comment thread app/app.py Outdated
Comment thread app/app.py
Comment thread README.md Outdated
Comment thread tests/integration/test_app_integration.py Outdated
Comment thread tests/integration/test_app_integration.py Outdated
Comment thread tests/integration/test_app_integration.py Outdated
…he-Control

- Attach security headers and Cache-Control: no-store directly in unhandled_exception_handler so 500 responses from ServerErrorMiddleware carry them.
- Document Cache-Control: no-store in authenticate OpenAPI schema.
- Add integration tests for unhandled 500 responses on general routes and /authenticate.
- Relocate Response headers section in README above endpoint descriptions.

@aditeyabaral aditeyabaral left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks, everything from the last round is addressed, and I've resolved those threads. I checked the new behaviour with TestClient: an unhandled 500 on /authenticate now carries all four headers, the OpenAPI schema documents Cache-Control on every /authenticate response, and the full suite passes with live credentials at 100% coverage.

One small thing is left, inline: the docstring on _security_headers. Once that's in, this is good to go.

Comment thread app/app.py Outdated
Co-authored-by: Aditeya Baral <aditeya.baral@gmail.com>
@nutellasando
nutellasando added this pull request to the merge queue Oct 20, 2026
Merged via the queue into pesu-dev:dev with commit 2e75fa8 Oct 20, 2026
7 checks passed
@nutellasando
nutellasando deleted the feat/security-headers branch October 20, 2026 06:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add security headers and Cache-Control: no-store to API responses

2 participants