SEC2-022: Uploaded photographs are kept and forwarded with their embedded metadata, unlike every other picture Postulo stores #964
Labels
No labels
accessibility
authentication
breaking change
bug
documentation
enhancement
interface
internationalisation
observability
performance
security
tier
1
tier
2
tier
3
tier/4
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo#964
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-10-16 in a read-only security review of
postuloat56fc9d531, checked by a second, independent pass.Severity: Low · Category: Data Exposure · Reference: CWE-212 (Improper Removal of Sensitive Information Before Storage or Transfer); OWASP A01:2021 (Broken Access Control)
Location:
src/postulo/documents/forms.py:536-540(help text "Postulo keeps it exactly as uploaded") and:695-709(clean_file)src/postulo/documents/integrity.py:95-104(_picture_problem: decodes to check, keeps the bytes)src/postulo/documents/views.py:1033-1070(email_then_freeze: uploads attached byte-for-byte),src/postulo/documents/archiving.py(copies to stores)Issue
core/pictures.as_storedre-encodes every avatar, logo and CV photo, and says why: "Re-encoding is the point rather than a side effect: it is what removes the metadata a phone's picture carries -- where it was taken, and when" (pictures.py:199-206). The uploaded-document path acceptspng,jpgandjpeg(integrity.EXTENSIONS), decodes the picture only to prove it is one (_picture_problem), and stores the original bytes unchanged; the form promises as much. Those files are then attached to mail the person sends to an employer (email_then_freezereadsupload.fileand attaches it as is), copied to every connected store, and included in the account archive.A scanned diploma or certificate uploaded today is very often a phone photograph, and a phone photograph carries GPS coordinates, the capture time and the device model in EXIF. Nothing in this path strips, flags or mentions it; the threat model's sharpest asset is the home address, and a photograph taken at home carries it precisely.
Uploaded PDF, DOCX and ODT files likewise carry author and producer metadata and are forwarded the same way; that is what the help text promises, and it is recorded here for context rather than as part of this issue.
Impact
The account holder leaks their location and device to every employer and store they send a photographed document to, without being told. No cross-account effect.
Fix
Do not re-encode: a certificate re-encoded as JPEG loses quality, and the "file you gave it" promise, the stored SHA-256 (#663) and the never-edited rule (#217) all argue for keeping the pixels byte-identical. Instead:
UploadedDocumentFormthat strips metadata losslessly inclean_filebefore the file is stored: for JPEG, drop the APP1 (Exif/XMP) and APP13 (IPTC) segments by walking the markers, keeping APP2 (ICC colour profile); for PNG, drop theeXIf,tEXt,iTXtandzTXtchunks;Image.getexif(), tag 0x8825) is present.Done when a test uploads a JPEG carrying a GPS IFD and a PNG carrying an
eXIfchunk and asserts the stored bytes decode to identical pixels andImage.getexif()is empty.Related
#878 (open) - metadata of exported PDF/DOCX/ODT, not of uploaded files; a different surface. #482 and #264 (closed) - re-encoding of avatars and logos, the rule this path does not follow. #663 and #217 (closed) - the stored checksum and the never-edited rule the fix must keep.