schema.org source: one over-long field or a NaN or infinite salary throws away the whole posting #592
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#592
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-09-29 in a read-only review of postulo at
95348908d, reading the schema.org source and feeding it out-of-range JSON-LD values.Checked again on 2026-10-01 against
mainat0a5cad587: still present.plugins/builtin/andplugins/base.py:44-46have not changed; the salary and currency fields the review form checks against are now atjobs/models.py:900-912.The schema.org source copies what a posting states into
JobPostingDatawithout the bounds that model enforces, and_decimallets a non-finite number through to a comparison that raises. One bad value therefore makes the source raise,parse_pagelogs and skips it, and the capture falls back to page metadata, losing every field the posting stated correctly. The page-metadata source already cuts its fields to 500 characters; this one does not. Its currency is cut to three characters but never checked, so a misspelt one reaches the review form, where the person has to correct it.plugins/builtin/__init__.py:313-328_from_posting: passestitle,_text(posting.get("hiringOrganization"))andlocationunbounded, whileplugins/base.py:44-46declares each withmax_length=MAX_FIELD_CHARS(500), which raises rather than cuts.plugins/builtin/__init__.py:130-137_decimal:Decimal("NaN")parses, thenamount > 0, outside thetry, raisesInvalidOperation.Infinity(and a JSON1e400, whichjson.loadsreads as infinity) passes the comparison and is refused by pydantic as not finite. Nothing appliespatterns.LARGEST(patterns.py:158), which the text reader uses (patterns.py:524).plugins/builtin/__init__.py:195_one_salary:str(...).strip()[:3]with no check againstCURRENCY_CODES, unlikepatterns.py:463-472.boards/linkedin.py:50-59andboards/greenhouse.py:47-63returntext_of(...)unbounded, andBoardSource.parse(__init__.py:434) buildsJobPostingDatafrom it.JobPostingwhose title, company name orjobLocationaddress runs past 500 characters raisesValidationError("String should have at most 500 characters"); avalueof"NaN"orNaNraisesInvalidOperation;"Infinity"or1e400raisesValidationError("Input should be a finite number"). In each caseregistry.parse_pageanswered withpage-metadata, and the locationLisbon, PTand the posted date the JSON-LD stated came back empty. A currency of"US Dollar"came back asUSand"$"as$; a value of1e30came back as1E+30. Those three pass the capture and fail the review form, againstcurrency_codeandmax_digits=12(jobs/models.py:897-909).NaN, should cost that field, not the posting. On a board page with a recipe, a long recipe field fails the board source the same way._from_posting, cuttitle,company_nameandlocationtoMAX_FIELD_CHARSasPageMetadataSource._readdoes (__init__.py:558-562), and do the same inBoardSource.parsebefore buildingJobPostingData. In_decimal, returnNoneunlessamount.is_finite()and0 < amount < patterns.LARGEST, with the comparison inside thetry. In_one_salary, keep the stated currency only when it is an ISO code inCURRENCY_CODESor one ofpatterns.SYMBOLS(throughpatterns._code, made public); otherwise leave it empty, as the text reader does for a bare$. Sharingpatterns' currency check rather than copying it keeps the two readers from drifting.Done when
tests/test_capture.pyfeedSchemaOrgSource.parsea JSON-LD posting with a 600-character title, with aNaN, anInfinityand a1e30salary, and with currencies"US Dollar"and"$", and assert that each returns a posting with the other fields intact, the long field cut to 500 characters, no salary for the three bad amounts, and an empty currency for both. They fail today.BoardSourcea LinkedIn page whose location element holds over 500 characters and asserts that the board source still answers.