You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Addresses four findings from a pentest conducted on 2026-05-07 against initiative-dev.<redacted>.
Findings addressed
Severity
ID
Finding
🔴 Critical
CRIT-001
CORS origin reflection with allow_credentials=True
🔴 Critical
CRIT-002
Stored XSS + DoS via task title/description
🟡 Medium
MED-001
No Content-Security-Policy header
🔵 Low
LOW-001
Pydantic validation errors expose internal field paths
Changes
backend/app/core/config.py
Added ALLOWED_ORIGINS: list[str] | str | None env var (defaults to APP_URL)
Added cors_origins property that builds the explicit allowlist — APP_URL is always included, additional origins can be added via ALLOWED_ORIGINS=https://a.com,https://b.com
backend/app/main.py
CRIT-001: allow_origins=["*"] → allow_origins=settings.cors_origins. Wildcard combined with allow_credentials=True allowed any website to silently read and mutate authenticated users' data.
MED-001: Added add_security_headers HTTP middleware that injects Content-Security-Policy on every response. Covers the SPA served from the same origin.
LOW-001: Added RequestValidationError handler that returns {"detail": "Invalid request data"} instead of full Pydantic field paths and input values.
backend/app/schemas/task.py
CRIT-002: Added _strip_html() (removes all HTML tags — for plain-text fields) and _sanitize_html() (strips dangerous elements/attributes — for rich-text fields).
Validators applied to TaskBase.title (strip all HTML) and TaskBase.description / TaskUpdate.description (sanitize dangerous content: <script>, <iframe>, event handler attributes, javascript: URIs).
Confirmed attack: <img src=x onerror=…> in a task title was stored verbatim and crashed the React renderer for all users viewing that project.
…sclosure
Addresses findings from pentest 2026-05-07 (initiative-dev.morels.me):
CRIT-001 – CORS origin reflection with credentials
`allow_origins=["*"]` combined with `allow_credentials=True` allowed any
website to make authenticated requests using the victim's session cookie.
Replace wildcard with `settings.cors_origins` — an explicit allowlist
derived from `APP_URL` and the new `ALLOWED_ORIGINS` env var.
CRIT-002 – Stored XSS + DoS via task title/description
Task `title` and `description` fields accepted raw HTML/JS. A payload like
`<img src=x onerror=…>` in a task title crashed the React renderer for all
users viewing that project (DoS confirmed). Without a CSP, the same vector
could execute arbitrary JS.
Add `field_validator` sanitisers to `TaskBase` and `TaskUpdate`:
- `title`: plain text only — all HTML tags stripped via regex
- `description`: dangerous elements stripped (script, iframe, object, …),
event handler attributes removed, javascript:/data: URIs blocked
MED-001 – No Content-Security-Policy header
Add an HTTP middleware that injects a CSP on every response. Covers the
frontend SPA served from the same origin as the API.
LOW-001 – Pydantic validation errors expose internal field paths
FastAPI's default RequestValidationError handler returns full Pydantic field
paths and input values. Add a custom handler that returns a single generic
message ("Invalid request data") instead.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
We reviewed changes in 20adae1...9d00d31 on this pull request. Below is the summary for the review, and you can see the individual issues we found as inline review comments.
Some issues found as part of this review are outside of the diff in this pull request and aren't shown in the inline review comments due to GitHub's API limitations. You can see those issues on the DeepSource dashboard.
AI Review is run only on demand for your team. We're only showing results of static analysis review right now. To trigger AI Review, comment @deepsourcebot review on this thread.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
The reason will be displayed to describe this comment to others. Learn more.
Method doesn't use the class instance and could be converted into a static method
The method doesn't use its bound instance. Decorate this method with @staticmethod decorator, so that Python does not have to instantiate a bound method for every instance of this class thereby saving memory and computation. Read more about staticmethods here.
Three bugs introduced in the previous commit:
1. RequestValidationError handler returned a plain string instead of a list,
breaking existing tests that iterate over `detail` expecting error objects
with custom codes (PROPERTY_OPTIONS_REQUIRED, PROPERTY_DUPLICATE_OPTION_VALUE).
Fix: strip only the `input` and `url` keys from each error entry rather than
collapsing everything to a single string — application-level error codes are
preserved for callers, raw user input is no longer echoed back.
2. _sanitize_html() stripped the <script> open/close tags but left the JS
content between them (`window.__desc_xss=1` survived tag removal).
Fix: use a paired regex that matches tag-content-closing-tag as a unit so
the entire block is removed in one pass.
3. test_empty_string_stays_empty expected TaskBase(title="") to raise
ValidationError, but there is no min_length constraint on `title` so Pydantic
allows it. Replaced with test_html_only_title_becomes_empty_string which
verifies the validator does not crash on degenerate input.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
The reason will be displayed to describe this comment to others. Learn more.
I think my only feedback for this is that sanitizing fields most definitely impacts more than just tasks. We should probably address this on a more systemic level, given its an app that ingests a LOT of data by its nature. If you want for now you can just remove that part of the PR since systemic stuff is a lot more of a pain haha.
Ideally id like to make _strip_html / _sanitize_html to typed annotations: PlainText, RichText, RawText, ect, and then do a DB migration from str and maybe get a sanitizer library (I'm think nh3? RUST FOR LYFE)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Addresses four findings from a pentest conducted on 2026-05-07 against
initiative-dev.<redacted>.Findings addressed
allow_credentials=TrueChanges
backend/app/core/config.pyALLOWED_ORIGINS: list[str] | str | Noneenv var (defaults toAPP_URL)cors_originsproperty that builds the explicit allowlist —APP_URLis always included, additional origins can be added viaALLOWED_ORIGINS=https://a.com,https://b.combackend/app/main.pyallow_origins=["*"]→allow_origins=settings.cors_origins. Wildcard combined withallow_credentials=Trueallowed any website to silently read and mutate authenticated users' data.add_security_headersHTTP middleware that injectsContent-Security-Policyon every response. Covers the SPA served from the same origin.RequestValidationErrorhandler that returns{"detail": "Invalid request data"}instead of full Pydantic field paths and input values.backend/app/schemas/task.py_strip_html()(removes all HTML tags — for plain-text fields) and_sanitize_html()(strips dangerous elements/attributes — for rich-text fields).TaskBase.title(strip all HTML) andTaskBase.description/TaskUpdate.description(sanitize dangerous content:<script>,<iframe>, event handler attributes,javascript:URIs).<img src=x onerror=…>in a task title was stored verbatim and crashed the React renderer for all users viewing that project.backend/app/schemas/task_sanitization_test.py(new)cors_originsconfig property.Deployment note
After merging, set in
.env:No other config changes or migrations required.
Testing
cd backend pytest app/schemas/task_sanitization_test.py -v