SEC2-036: POSTULO_BACKUP_DOWNLOAD falls open to "every administrator" on any value it does not recognise, and no start-up check reads it #978

Closed
opened 2026-10-17 10:39:01 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-10-16 in a read-only security review of postulo at 56fc9d531, checked by a second, independent pass.

Severity: Low · Category: Configuration · Reference: CWE-636 (Not Failing Securely); OWASP A05:2021 (Security Misconfiguration)

Location:

  • src/postulo/core/views_backups.py:75-82 (may_download)
  • src/postulo/core/checks.py:28-34 (CHOICES has no entry for it)

Issue

rule = str(getattr(settings, "POSTULO_BACKUP_DOWNLOAD", "administrators")).strip().lower()
if rule == "console":
    return False
if rule == "operator":
    return bool(getattr(user, "is_superuser", False))
return bool(getattr(user, "is_staff", False))

operators, superuser, none, off or a trailing character all reach the last line and grant the widest rule. Every other fixed-vocabulary setting (POSTULO_EMAIL_SECURITY, POSTULO_PDF_BACKEND, POSTULO_LOG_FORMAT, POSTULO_MAP_TILES) is refused at start-up through checks.CHOICES; this one, which #810 added as the operator's way to narrow who may carry the whole database away, is not in the list.

Impact

An operator who typed the setting to restrict backup downloads to themselves or to the console believes it is restricted while every administrator can still download an archive holding every account's rows and files. The misconfiguration is silent: check --deploy passes and the Backups page shows the button.

Fix

Add "POSTULO_BACKUP_DOWNLOAD": ("administrators", "operator", "console") to checks.CHOICES (the check compares the raw value, so Operator is refused at start-up as Starttls would be, consistent with the other entries), and make may_download fail closed, which still matters for a setting changed at runtime:

if rule == "administrators":
    return bool(getattr(user, "is_staff", False))
return False  # a word the instance does not know grants nobody

Done when: tests/test_hardening_810.py asserts postulo.E001 for POSTULO_BACKUP_DOWNLOAD="operators" and a 404 on download for an administrator under that value.

#810 (closed) - introduced the setting; nothing about unknown values. #487 (closed) - the same class of start-up check for the OAuth settings.

Found on 2026-10-16 in a read-only security review of `postulo` at `56fc9d531`, checked by a second, independent pass. **Severity:** Low · **Category:** Configuration · **Reference:** CWE-636 (Not Failing Securely); OWASP A05:2021 (Security Misconfiguration) **Location:** - `src/postulo/core/views_backups.py:75-82` (`may_download`) - `src/postulo/core/checks.py:28-34` (`CHOICES` has no entry for it) ## Issue ```python rule = str(getattr(settings, "POSTULO_BACKUP_DOWNLOAD", "administrators")).strip().lower() if rule == "console": return False if rule == "operator": return bool(getattr(user, "is_superuser", False)) return bool(getattr(user, "is_staff", False)) ``` `operators`, `superuser`, `none`, `off` or a trailing character all reach the last line and grant the widest rule. Every other fixed-vocabulary setting (`POSTULO_EMAIL_SECURITY`, `POSTULO_PDF_BACKEND`, `POSTULO_LOG_FORMAT`, `POSTULO_MAP_TILES`) is refused at start-up through `checks.CHOICES`; this one, which #810 added as the operator's way to narrow who may carry the whole database away, is not in the list. ## Impact An operator who typed the setting to restrict backup downloads to themselves or to the console believes it is restricted while every administrator can still download an archive holding every account's rows and files. The misconfiguration is silent: `check --deploy` passes and the Backups page shows the button. ## Fix Add `"POSTULO_BACKUP_DOWNLOAD": ("administrators", "operator", "console")` to `checks.CHOICES` (the check compares the raw value, so `Operator` is refused at start-up as `Starttls` would be, consistent with the other entries), and make `may_download` fail closed, which still matters for a setting changed at runtime: ```python if rule == "administrators": return bool(getattr(user, "is_staff", False)) return False # a word the instance does not know grants nobody ``` Done when: `tests/test_hardening_810.py` asserts `postulo.E001` for `POSTULO_BACKUP_DOWNLOAD="operators"` and a 404 on download for an administrator under that value. ## Related #810 (closed) - introduced the setting; nothing about unknown values. #487 (closed) - the same class of start-up check for the OAuth settings.
tiagoagueda added this to the 0.6.0 milestone 2026-10-17 10:39:02 +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#978
No description provided.