SEC2-059: One malformed date or an over-long remote identifier fails the whole run, on every run #34
Labels
No labels
bug
documentation
enhancement
performance
security
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-dav#34
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
postulo-davat1d49d6e, checked by a second, independent pass.Severity: Low · Category: Input Validation · Reference: CWE-20 (Improper Input Validation); CWE-755 (Improper Handling of Exceptional Conditions); OWASP A04:2021 (Insecure Design)
Location:
src/postulo_dav/text.py:270(_STAMP:(\d{2})for month, day, hour, minute and second)src/postulo_dav/text.py:277-283(parse_stamp:dt.datetime(y, mo, d, h, mi, s, tzinfo=dt.UTC)with notry)src/postulo_dav/text.py:286-303(parse_moment: the constructors at:293and:303; only theZoneInfolookup is guarded)src/postulo_dav/sync.py:195-199(apply_event:reschedule_interview(...)with no check that the end follows the start)src/postulo_dav/sync.py:473,534,607-608(every pull parses with nothing around it)src/postulo_dav/sync.py:543-551(SyncLink.bind(..., remote_href=href, uid=card.uid, ...)uncut); coresrc/postulo/plugins/models.py:385-386(remote_href500,uid200)Issue
_STAMPmatches any two digits for the month, day, hour, minute and second, and the values gostraight into
datetime(...), which raisesValueErrorfor a month of 13 or an hour of 25.Nothing catches it:
text_to_cardandtext_to_eventspropagate it out of_sync_contacts/_sync_interviews, so a singleREV,DTSTART,DTENDorLAST-MODIFIEDofthat shape ends the run before the ETag is stored, and the next run reads the same resource and
fails the same way. The core records the exception text on the connection
(
plugins/syncing.py:126-128), and the interviews half never runs when the card is the problem(
sync.py:405-416).Two more inputs fail alike. An adopted card's href over 500 characters or UID over 200 is written
to
SyncLinkuncut; on PostgreSQL that raises atbindafter the contact was already created,the pattern #20 describes for the name and company (SQLite enforces no length, so this part
affects the PostgreSQL profile only). And
apply_eventreschedules with whatever two momentsparsed, so an event whose end precedes its start is stored as such;
Interview.ends_athas noconstraint against
starts_at.Impact
Whoever can write to the collection the connection reads (the person on a phone with a lenient
client, another user of a shared server) can stop the connection for good until the record is
found and fixed by hand; the person sees only a
ValueError: month must be in 1..12line. OnPostgreSQL the adoption case also leaves one more unlinked contact behind each run, as in #20.
Fix
One change in four parts, so that one bad resource costs that resource and not the run:
parse_stampandparse_momentreturnNoneonValueErrorfrom the constructor (one lineeach; #24's fix for unknown zones then extends the same guard).
apply_eventreschedules only whenends_at > starts_at, and otherwise notes the event.bind, cutremote_hrefto 500 anduidto 200, or skip the card with a note.tryaround each pull and adoption that turns an exception intoreport.skipped += 1and a note. #20's per-cardatomic()block is the same step seen fromthe database side; the two should land together.
Done when tests feed a card with
REV:20261301T000000Z, an event withDTSTART:20260931T250000Z, an event whoseDTENDprecedes itsDTSTART, and an adopted cardwith a 300-character UID, and assert that the run completes, the interview is not moved, and the
report names what was skipped.
Related
#20 (open): the same failure class for
Contact.nameandCompany.nameand the unlinked contactleft behind; dates and the
SyncLinkcolumns are not in it. #24 (open):parse_momentandzones; it asks for
Noneon an unknown zone, not for the constructor's ownValueError. #2(closed) item "a failure in one half not sink the other" is noted in #20 as not done.
Fixed by pull request #35, merged as
fb8bbd7ae.