-
v0.9.1
Stablereleased this
2026-08-10 20:20:00 -05:00 | 89 commits to main since this releaseTwo gaps between a guarantee this project had written down and what the code actually
enforced. Both were found by a read-only audit of 0.9.0 (docs/audit.md), neither by a
failing test — the suite was green through both.Fixed
-
An
event_idcould name a path, not just a filename.calendar/eventid.pyargues
at length that the handle does not need signing, because a forged one names a calendar
andWRITABLE_CALENDARSis checked on that name before iCloud is contacted. That
argument holds only while the handle's resource field contributes a filename — and
nothing checked. A hand-built segment of../Private/target.icswas joined onto the
allowlisted collection URL verbatim, and caldav'sURL.canonical()does not remove dot
segments, so the traversal went on the wire for iCloud to normalise. Reachable for both
update_eventanddelete_event, and the confused deputy is realistic: event titles
from a subscribed feed or an emailed invite land in the model's context, and inducing a
tool call with a crafted argument is the standard shape of that attack.decode()now refuses a separator, a dot segment, or either one percent-encoded — the
check runs against the unquoted form so the encodings do not have to be enumerated,
while a double-encoded%252fstill decodes as the filename character it is. The
precondition is now recorded in the module docstring next to the argument that depends
on it, rather than left implicit.The tests could not have caught this, which is worth recording separately: the fake
attests/test_client.pyresolved an event by taking the last segment of whatever URL
it was handed, throwing the traversal away. A test asserting the refusal would have
passed against it no matter what the code did. The fake now rejects anything that is not
the collection URL plus one segment, so the new refusal cases prove something. -
The identity allowlist only guarded tool calls.
IdentityAllowlistimplemented
on_call_tooland nothing else, so any account on the Forgejo instance — which is every
account, since Forgejo has no OAuth scopes and that is the whole reason
ALLOWED_SUBJECTSexists — could complete the flow, initialize a session and read the
serverinstructions, which name the writable calendars. Calendar names are precisely
the value the 2026-08-08 history rewrite was performed to remove.The hook is now
on_message, which covers initialize, the listings and calls alike.
on_call_toolis kept as a second check behind it, so a change in how FastMCP
dispatches middleware cannot silently reopen the write surface.
Downloads
-
Source code (ZIP)
0 downloads
-
Source code (TAR.GZ)
0 downloads
-