Skip to content

feat(dataviewer): add OWASP security middleware stack#439

Merged
WilliamBerryiii merged 9 commits into
mainfrom
feat/118-security-fastapi
Apr 15, 2026
Merged

feat(dataviewer): add OWASP security middleware stack#439
WilliamBerryiii merged 9 commits into
mainfrom
feat/118-security-fastapi

Conversation

@katriendg
Copy link
Copy Markdown
Collaborator

Description

Added a layered OWASP security middleware stack to the dataviewer FastAPI backend, addressing clickjacking, MIME-sniffing, CSRF scoping, request body bombs, brute force via rate limiting, and information disclosure through exception handler filtering. The implementation follows the existing ASGI middleware pattern and integrates with the authentication layer while keeping all thresholds configurable via environment variables.

Closes #118

Type of Change

  • 🐛 Bug fix (non-breaking change fixing an issue)
  • ✨ New feature (non-breaking change adding functionality)
  • 💥 Breaking change (fix or feature causing existing functionality to change)
  • 📚 Documentation update
  • 🏗️ Infrastructure change (Terraform/IaC)
  • ♻️ Refactoring (no functional changes)

Component(s) Affected

  • infrastructure/terraform/prerequisites/ - Azure subscription setup
  • infrastructure/terraform/ - Terraform infrastructure
  • infrastructure/setup/ - OSMO control plane / Helm
  • workflows/ - Training and evaluation workflows
  • training/ - Training pipelines and scripts
  • docs/ - Documentation

Testing Performed

  • Terraform plan reviewed (no unexpected changes)
  • Terraform apply tested in dev environment
  • Training scripts tested locally with Isaac Sim
  • OSMO workflow submitted successfully
  • Smoke tests passed (smoke_test_azure.py)

Documentation Impact

  • No documentation changes needed
  • Documentation updated in this PR
  • Documentation issue filed

Bug Fix Checklist

Complete this section for bug fix PRs. Skip for other contribution types.

  • Linked to issue being fixed
  • Regression test included, OR
  • Justification for no regression test:

Checklist


Changes

Security Middleware

A new middleware.py module introduced two ASGI middleware classes that compose into the existing Starlette stack.

  • SecurityHeadersMiddleware injects five OWASP baseline headers (X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, Cross-Origin-Opener-Policy) on every HTTP response, with a conditional CSP applied only to non-API paths to avoid breaking frontend dev-server proxies
  • ContentSizeLimitMiddleware enforces request body size limits with a dual-check strategy — validates the Content-Length header upfront and tracks streaming bytes for chunked transfers, returning HTTP 413 when exceeded
    • Configurable via MAX_REQUEST_BODY_BYTES (default 10 MB)

Application-Level Hardening

Custom exception handlers in main.py prevent information leakage by returning generic error responses to clients while logging full tracebacks server-side.

  • CORS hardened from wildcard allow_methods=["*"] and allow_headers=["*"] to explicit allow-lists of methods and headers
  • CSRF cookie scoped with path="/" for correct cross-route coverage
  • Enhanced /health endpoint returns a structured response with an API and storage connectivity probe, returning HTTP 503 when storage is unreachable
  • Rate limiter instantiated globally via slowapi and registered with the application state

Detection Router

Rate limiting, CSRF enforcement, and structured logging applied to the detection API.

  • Per-endpoint @limiter.limit() decorators on run_detection, get_detections, and clear_detections with environment-configurable thresholds (RATE_LIMIT_DETECT, RATE_LIMIT_DETECTIONS)
  • CSRF dependency added to the clear_detections DELETE endpoint
  • Migrated print(..., file=sys.stderr) calls to structured logger using %-style formatting
  • Renamed request parameter to request_body to disambiguate from the rate limiter's Request dependency

Test Coverage

A new test_security_middleware.py provides 28+ test methods across 8 test classes covering every new security behavior at both TestClient integration and raw ASGI unit test levels.

  • Security headers, content size limits, exception handlers, CSRF cookie scoping, CORS hardening, enhanced health check, detection security, and direct ASGI middleware tests
  • test_health.py refactored to class-based tests with storage probe validation
  • test_api_endpoints.py updated for the new structured health response

Supporting

  • Added slowapi==0.1.9 dependency in pyproject.toml
  • Documented new environment variables in .env.example
  • Added "redoc" and "slowapi" to .cspell/general-technical.txt
  • Enabled host: true in vite.config.ts for devcontainer port forwarding

Related Issues

Closes #118

- Add SecurityHeadersMiddleware (CSP, X-Frame-Options, COOP, Referrer-Policy, Permissions-Policy)
- Add ContentSizeLimitMiddleware with configurable MAX_REQUEST_BODY_BYTES (default 10 MB)
- Harden CORS with explicit allow_methods and allow_headers lists
- Add configurable rate limiting via slowapi (RATE_LIMIT_DETECT, RATE_LIMIT_DETECTIONS)
- Add custom exception handlers to prevent stack trace and path leakage
- Enhance health check with storage connectivity probe (503 when degraded)
- Add CSRF protection on DELETE clear_detections endpoint
- Add path=/ to CSRF cookie for correct scoping
- Replace debug print() calls with logger in detection router
- Add 28 security middleware tests and update health check test assertions
- Set Vite dev server host to 0.0.0.0 for devcontainer port forwarding

🤖 - Generated by Copilot
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 9, 2026

Dependency Review

The following issues were found:
  • ✅ 0 vulnerable package(s)
  • ✅ 0 package(s) with incompatible licenses
  • ✅ 0 package(s) with invalid SPDX license definitions
  • ⚠️ 1 package(s) with unknown licenses.
See the Details below.

Snapshot Warnings

⚠️: No snapshots were found for the head SHA ba54e4c.
Ensure that dependencies are being submitted on PR branches and consider enabling retry-on-snapshot-warnings. See the documentation for more information and troubleshooting advice.

License Issues

data-management/viewer/backend/uv.lock

PackageVersionLicenseIssue Type
limits5.8.0NullUnknown License

OpenSSF Scorecard

PackageVersionScoreDetails
pip/deprecated 1.3.1 UnknownUnknown
pip/limits 5.8.0 UnknownUnknown
pip/slowapi 0.1.9 UnknownUnknown
pip/wrapt 2.1.2 UnknownUnknown

Scanned Files

  • data-management/viewer/backend/uv.lock

@codecov-commenter
Copy link
Copy Markdown

codecov-commenter commented Apr 9, 2026

Codecov Report

❌ Patch coverage is 99.09910% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 65.18%. Comparing base (9f138a9) to head (ba54e4c).

Files with missing lines Patch % Lines
...gement/viewer/backend/src/api/routers/detection.py 93.33% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main     #439      +/-   ##
==========================================
+ Coverage   64.80%   65.18%   +0.38%     
==========================================
  Files         251      253       +2     
  Lines       15441    15541     +100     
  Branches     2108     2118      +10     
==========================================
+ Hits        10006    10130     +124     
+ Misses       5146     5122      -24     
  Partials      289      289              
Flag Coverage Δ
pester 82.24% <ø> (ø)
pytest 92.40% <ø> (ø)
pytest-dataviewer 65.12% <99.09%> (+1.24%) ⬆️
pytest-fuzz 1.56% <0.00%> (-0.03%) ⬇️
vitest 51.77% <ø> (ø)
Files with missing lines Coverage Δ
data-management/viewer/backend/src/api/main.py 100.00% <100.00%> (+3.77%) ⬆️
...ta-management/viewer/backend/src/api/middleware.py 100.00% <100.00%> (ø)
...-management/viewer/backend/src/api/rate_limiter.py 100.00% <100.00%> (ø)
...gement/viewer/backend/src/api/routers/detection.py 88.63% <93.33%> (+47.61%) ⬆️

... and 2 files with indirect coverage changes

🚀 New features to boost your workflow:
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/routers/detection.py Fixed
Comment thread data-management/viewer/backend/src/api/middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
Comment thread data-management/viewer/backend/tests/test_security_middleware.py Fixed
@katriendg katriendg requested a review from a team April 9, 2026 13:50
Copy link
Copy Markdown
Contributor

@nguyena2 nguyena2 left a comment

Choose a reason for hiding this comment

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

main.py (L90) and detection.py (L22) each instantiate their own Limiter(key_func=get_remote_address). These are independent objects with separate in-memory counter stores. If this is the intended behavior, then no changes needed. If not, then suggest creating a shared module and importing it into both.

@katriendg
Copy link
Copy Markdown
Collaborator Author

@nguyena2 pretty relevant catch here, thanks! I've updated into a shared module and updated the tests.

@WilliamBerryiii WilliamBerryiii merged commit 239edb9 into main Apr 15, 2026
32 checks passed
@WilliamBerryiii WilliamBerryiii deleted the feat/118-security-fastapi branch April 15, 2026 18:37
WilliamBerryiii pushed a commit that referenced this pull request May 8, 2026
🤖 I have created a release *beep* *boop*
---


##
[0.8.0](v0.7.4...v0.8.0)
(2026-05-08)


### ⚠ BREAKING CHANGES

* **dataviewer:** bump frontend stack to React 19, Vite 8, Tailwind v4,
MSAL 5, ESLint 10
([#524](#524))

### ✨ Features

* **agents:** add automated validation for high-risk Dependabot bumps
([#574](#574))
([8c3686a](8c3686a)),
closes
[#573](#573)
* **data:** add camera selector to annotation workspace and fix AV1
frame extraction
([#591](#591))
([c809d2f](c809d2f))
* **data:** seed dataviewer frontend test foundation and per-section
codecov flags
([#594](#594))
([c06c4e3](c06c4e3))
* **dataviewer:** add OWASP security middleware stack
([#439](#439))
([239edb9](239edb9))
* **infrastructure:** add conversion pipeline Terraform module
([#542](#542))
([244531e](244531e))
* **infrastructure:** upgrade OSMO to chart 1.2.1 / image 6.2 with
secure auth and skrl 2.0.0 compatibility
([#492](#492))
([edfd7a5](edfd7a5))
* **pipeline:** add ACSA setup for ROS2 bag sync to Blob
([#451](#451))
([c271a54](c271a54))
* **workflows:** add advisory Dependabot PR reviewer agentic workflow
([#498](#498))
([d4bb140](d4bb140))
* **workflows:** trigger AW Dependabot PR reviewer after PR Validation
([#580](#580))
([7ab3d16](7ab3d16))


### 🐛 Bug Fixes

* **ci:** correct stale version comment for
actions/create-github-app-token
([#506](#506))
([b2e9a54](b2e9a54))
* **ci:** restore data-pipeline and training broken tests by domain
folder restructure
([#547](#547))
([06d8472](06d8472))
* **docs:** update remaining stale 'Coming soon' labels in
docs/README.md
([#507](#507))
([02439d6](02439d6))
* **docs:** update stale coming soon label for Training section
([#472](#472))
([46db49b](46db49b))
* **evaluation:** scope SIL AzureML validation code path and script
reference
([#387](#387))
([9f138a9](9f138a9))
* **infrastructure:** OSMO workflow execution, PostgreSQL public access,
and quickstart corrections
([#477](#477))
([9ed2da6](9ed2da6))
* **scripts:** exclude CHANGELOG.md from changed-files msdate check
([#644](#644))
([8133bdc](8133bdc))
* **workflows:** allow dependabot[bot] to activate AW Dependabot PR
Review
([#586](#586))
([39dc022](39dc022))
* **workflows:** correct branches filter on AW Dependabot PR Review
workflow_run trigger
([#584](#584))
([fe06b52](fe06b52))
* **workflows:** normalize validate.yaml placeholder env/compute values
([#510](#510))
([340ff44](340ff44))
* **workflows:** recompile aw-dependabot-pr-review lock file
([#576](#576))
([d77c167](d77c167))
* **workflows:** switch AW Dependabot PR Review to pull_request_target
([#589](#589))
([3f1edd1](3f1edd1))


### 📚 Documentation

* **docs:** Fix deployment guide links
([#614](#614))
([0070b04](0070b04))
* document dependency-pinning-artifacts directory purpose
([#508](#508))
([50e0010](50e0010))


### 📦 Build System

* **training:** standardize on Python 3.12 across manifests, containers,
and runtime scripts
([#541](#541))
([7ad014a](7ad014a))


### 🔧 Operations

* **build:** add Copilot cloud agent setup-steps workflow
([#593](#593))
([c912668](c912668))


### 🔧 Miscellaneous

* **build:** exclude auto-generated CHANGELOG.md from cspell and seed
dictionary
([#582](#582))
([de1dd57](de1dd57))
* **build:** redesign codecov flags and split pytest CI per component
([#520](#520))
([357e745](357e745))
* **dataviewer:** bump frontend stack to React 19, Vite 8, Tailwind v4,
MSAL 5, ESLint 10
([#524](#524))
([50f8ad4](50f8ad4))
* **dataviewer:** repoint stale src/dataviewer references to
data-management/viewer
([#504](#504))
([88fa1b4](88fa1b4)),
closes
[#503](#503)
* **deps-dev:** bump basic-ftp from 5.3.0 to 5.3.1
([#618](#618))
([ca10f2a](ca10f2a))
* **deps-dev:** bump globals from 15.15.0 to 17.5.0 in
/data-management/viewer/frontend
([#527](#527))
([0e0b2ae](0e0b2ae))
* **deps-dev:** bump ip-address from 10.1.0 to 10.2.0
([#616](#616))
([816c9cf](816c9cf))
* **deps-dev:** bump lint-staged from 16.4.0 to 17.0.2 in the
root-npm-dependencies group across 1 directory
([#626](#626))
([0e2f293](0e2f293))
* **deps-dev:** bump pydantic from 2.13.3 to 2.13.4 in the
python-dependencies group across 1 directory
([#629](#629))
([c24f1c1](c24f1c1))
* **deps-dev:** bump the python-dependencies group across 1 directory
with 2 updates
([#514](#514))
([8410f4b](8410f4b))
* **deps:** bump azure-core from 1.39.0 to 1.40.0 in /evaluation in the
inference-dependencies group across 1 directory
([#597](#597))
([6141db4](6141db4))
* **deps:** bump cryptography from 46.0.6 to 46.0.7 in
/data-management/viewer
([#424](#424))
([5fb6d58](5fb6d58))
* **deps:** bump cryptography from 46.0.6 to 46.0.7 in
/data-management/viewer/backend
([#423](#423))
([b516ad5](b516ad5))
* **deps:** bump lucide-react from 0.469.0 to 1.8.0 in
/data-management/viewer/frontend
([#528](#528))
([1bdfc1e](1bdfc1e))
* **deps:** bump nginx from `8aa63af` to `5616878` in
/data-management/viewer/frontend
([#511](#511))
([9e7e20e](9e7e20e))
* **deps:** bump nginx from 1.27-alpine to 1.29-alpine in
/data-management/viewer/frontend
([#484](#484))
([0e5c3dd](0e5c3dd))
* **deps:** bump node from `435f353` to `e49fd70` in
/data-management/viewer/frontend
([#560](#560))
([2884649](2884649))
* **deps:** bump react-is from 18.3.1 to 19.2.5 in
/data-management/viewer/frontend
([#530](#530))
([d51318c](d51318c))
* **deps:** bump tensordict from 0.11.0 to 0.12.1 in /evaluation in the
inference-dependencies group across 1 directory
([#456](#456))
([b24e733](b24e733))
* **deps:** bump the dataviewer-backend-dependencies group across 1
directory with 2 updates
([#531](#531))
([171a1da](171a1da))
* **deps:** bump the dataviewer-backend-dependencies group across 1
directory with 5 updates
([#516](#516))
([4f9a577](4f9a577))
* **deps:** bump the dataviewer-backend-dependencies group across 1
directory with 5 updates
([#602](#602))
([6c27ab5](6c27ab5))
* **deps:** bump the dataviewer-dependencies group across 1 directory
with 2 updates
([#529](#529))
([8646971](8646971))
* **deps:** bump the dataviewer-dependencies group across 1 directory
with 3 updates
([#601](#601))
([d28fb50](d28fb50))
* **deps:** bump the dataviewer-dependencies group across 1 directory
with 3 updates
([#632](#632))
([4ca5f3e](4ca5f3e))
* **deps:** bump the dataviewer-dependencies group across 1 directory
with 5 updates
([#515](#515))
([109ee81](109ee81))
* **deps:** bump the dataviewer-frontend-patch-minor group across 1
directory with 6 updates
([#630](#630))
([04d5dfd](04d5dfd))
* **deps:** bump the dataviewer-frontend-patch-minor group across 1
directory with 9 updates
([#563](#563))
([c08f450](c08f450))
* **deps:** bump the docusaurus-dependencies group across 1 directory
with 4 updates
([#627](#627))
([f5825fc](f5825fc))
* **deps:** bump the docusaurus-dependencies group across 1 directory
with 6 updates
([#599](#599))
([b859344](b859344))
* **deps:** bump the github-actions group across 1 directory with 4
updates
([#459](#459))
([2609c52](2609c52))
* **deps:** bump the github-actions group across 1 directory with 4
updates
([#517](#517))
([f54bf5d](f54bf5d))
* **deps:** bump the inference-dependencies group across 1 directory
with 11 updates
([#562](#562))
([087f53a](087f53a))
* **deps:** bump the inference-dependencies group across 1 directory
with 2 updates
([#628](#628))
([4a3be47](4a3be47))
* **deps:** bump the pip group across 2 directories with 1 update
([#494](#494))
([a14b6b0](a14b6b0))
* **docs:** update stale Python 3.11 references to 3.12
([#575](#575))
([6f85c95](6f85c95))
* **scripts:** remove redundant SC1091 disables in OSMO deploy scripts
([#509](#509))
([ae1cb82](ae1cb82))


### 🔒 Security

* **build:** pin dependencies and hash-verify downloads
([#465](#465))
([0289f49](0289f49))
* **build:** remediate dependency security advisories
([#479](#479))
([7196d6d](7196d6d))
* **deps-dev:** bump basic-ftp from 5.2.1 to 5.2.2
([#454](#454))
([cb158f1](cb158f1))
* **deps-dev:** bump basic-ftp from 5.2.2 to 5.3.0
([#495](#495))
([e983b8b](e983b8b))
* **deps-dev:** bump hypothesis from 6.152.3 to 6.152.4 in the
python-dependencies group
([#598](#598))
([83384d2](83384d2))
* **deps-dev:** bump markdownlint-cli2 from 0.22.0 to 0.22.1 in the
root-npm-dependencies group
([#559](#559))
([32bde35](32bde35))
* **deps-dev:** bump picomatch from 2.3.1 to 2.3.2 in /docs/docusaurus
([#455](#455))
([66f86ca](66f86ca))
* **deps-dev:** bump postcss from 8.5.10 to 8.5.12 in
/data-management/viewer/frontend
([#569](#569))
([a652dba](a652dba))
* **deps-dev:** bump the python-dependencies group with 2 updates
([#457](#457))
([749d231](749d231))
* **deps-dev:** bump the python-dependencies group with 2 updates
([#485](#485))
([71b44fd](71b44fd))
* **deps-dev:** bump the python-dependencies group with 3 updates
([#564](#564))
([9fc52fd](9fc52fd))
* **deps-dev:** bump typescript from 6.0.2 to 6.0.3 in /docs/docusaurus
in the docusaurus-dependencies group
([#513](#513))
([5694dbc](5694dbc))
* **deps:** bump azureml/openmpi4.1.0-ubuntu22.04 from 20260303.v5 to
20260409.v4 in /evaluation/sil/docker
([#480](#480))
([25d4df8](25d4df8))
* **deps:** bump cryptography from 46.0.6 to 46.0.7 in /evaluation in
the uv group across 1 directory
([#538](#538))
([92c5b2e](92c5b2e))
* **deps:** bump diffusers from 0.35.2 to 0.38.0 in /training/il/lerobot
([#638](#638))
([6261d19](6261d19))
* **deps:** bump follow-redirects from 1.15.11 to 1.16.0 in
/docs/docusaurus
([#469](#469))
([0458908](0458908))
* **deps:** bump gitpython and mako for lerobot IL training
([#623](#623))
([9f8022b](9f8022b))
* **deps:** bump node from 24.14.1-slim to 25.9.0-slim in
/data-management/viewer/frontend
([#482](#482))
([1532d09](1532d09))
* **deps:** bump packaging from 26.0 to 26.1 in /evaluation in the
inference-dependencies group
([#483](#483))
([f4afb6c](f4afb6c))
* **deps:** bump pillow from 12.1.1 to 12.2.0
([#467](#467))
([39fb663](39fb663))
* **deps:** bump python from 3.11-slim to 3.14-slim in
/data-management/viewer/backend
([#481](#481))
([7af9dfc](7af9dfc))
* **deps:** bump the dataviewer-backend-dependencies group across 1
directory with 15 updates
([#428](#428))
([e4446a2](e4446a2))
* **deps:** bump the dataviewer-backend-dependencies group in
/data-management/viewer/backend with 4 updates
([#487](#487))
([0f57c5b](0f57c5b))
* **deps:** bump the dataviewer-backend-dependencies group in
/data-management/viewer/backend with 8 updates
([#566](#566))
([d6e7869](d6e7869))
* **deps:** bump the dataviewer-dependencies group across 1 directory
with 5 updates
([#464](#464))
([24c208d](24c208d))
* **deps:** bump the dataviewer-dependencies group in
/data-management/viewer with 2 updates
([#486](#486))
([90149f3](90149f3))
* **deps:** bump the dataviewer-dependencies group in
/data-management/viewer with 6 updates
([#565](#565))
([f0bb36b](f0bb36b))
* **deps:** bump the dataviewer-frontend-patch-minor group across 1
directory with 10 updates
([#613](#613))
([e481f83](e481f83))
* **deps:** bump the github-actions group across 1 directory with 4
updates
([#534](#534))
([5478ab6](5478ab6))
* **deps:** bump the github-actions group with 2 updates
([#488](#488))
([4e6ce98](4e6ce98))
* **deps:** bump the github-actions group with 3 updates
([#567](#567))
([48c38dc](48c38dc))
* **deps:** bump the github-actions group with 3 updates
([#634](#634))
([00cfb49](00cfb49))
* **deps:** bump the github-actions group with 6 updates
([#603](#603))
([73eb79a](73eb79a))
* **deps:** bump the training-dependencies group across 1 directory with
23 updates
([#463](#463))
([d5a8656](d5a8656))
* **deps:** bump yaml from 2.8.2 to 2.8.3 in
/data-management/viewer/frontend
([#453](#453))
([10449df](10449df))
* pytest harness, dependabot advisories, and OSSF Scorecard remediations
([#501](#501))
([e8756e8](e8756e8))
* **scripts:** pin and hash-verify all shell script downloads
([#468](#468))
([0c2bb9c](0c2bb9c))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

---------

Co-authored-by: physical-ai-toolchain-release[bot] <267194360+physical-ai-toolchain-release[bot]@users.noreply.github.com>
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
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.

feat(dataviewer): add security middleware stack to FastAPI backend

5 participants