Fencing untrusted text: captured listing titles, company names and calendar locations reach the model unfenced #9
Labels
No labels
bug
documentation
enhancement
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-mcp#9
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-mcp at
0b29e70b9, comparingUNTRUSTED_FIELDSwith the API's response schemas and with where each field's text comes from.Related to #3.
fenced()wraps a string only when its JSON key is inUNTRUSTED_FIELDS, and the list leaves out names and titles on purpose ("Names and dates are left alone"). Several of those short fields are nonetheless written by third parties and can hold a full sentence aimed at the model, so they reach it outside any fence, in the same reply as the write tools.src/postulo_mcp/server.py:47-66(UNTRUSTED_FIELDS),src/postulo_mcp/server.py:380(if isinstance(payload, str) and key in UNTRUSTED_FIELDS),src/postulo_mcp/server.py:87-92(INSTRUCTIONS),tests/test_server.py:256("a title is not prose"),README.md:86-102.titleonListingRef/ListingOutand on search hits: a captured page'sog:titleor<title>(coreplugins/builtin/__init__.py:539), up to 250 characters (corejobs/models.py:873);nameonCompanyRef, taken from the same capture;locationonInterviewOut, which postulo-dav copies from a shared calendar event, up to 500 characters (postulo-davsrc/postulo_dav/sync.py:199-200);locationonListingOut, also from the capture.A captured page whose title is an instruction to change statuses or add notes arrives unmarked in
list_applications,search_postings,searchandget_application.INSTRUCTIONStell the model what fenced text is, which invites it to read unfenced text as the person's own.--writeand the model follows text it reads; the README rightly calls the fence "a seatbelt, not a cage".title,nameandlocationon listings, companies, interviews and search hits, collapse newlines and strip anything resembling a fence marker, and fence the value when it is longer than a short threshold (say 80 characters) or contains a newline. Add a sentence toINSTRUCTIONSsaying that names, titles and locations can also come from captured pages and calendars and are never instructions either. Change the assertion attests/test_server.py:256, which currently pins the gap in place.Done when
tests/test_server.pygives a listing a title and an interview a location that each carry an instruction with a newline, callssearch_postingsandlist_interviews, and finds neither text unmarked and neither newline intact.