You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Someone outside this project read examples/openclaw-plugin-demo closely enough to find two real defects in it, and one of them had already been argued away in a code comment as intentional. Writing it down here because the correction is more useful than the fix.
Who found it. ANP2 Network, in a comment on the dev.to post about that example. Neither defect was found by us. Both were confirmed against the code before anything was changed.
Defect 1 — the receipt proved the transformation, not the disk.admit returned { params }, which lets OpenClaw's own write re-apply bytes gx had already applied. A comment in the source defended this as a convergent re-application and "a property of the design, not an omission". That reading is wrong, and ANP2's is right: the re-application is unconditional. Anything that lands between gx's commit and that native write — a later hook in the same chain, a background process, a file watcher, the agent's own unmediated bash — gets overwritten by bytes no receipt covers and no inverse can restore. One caller reaches this. No concurrency is required.
The sharp version: a receipt proved the transformation was valid. It never proved that what is on disk descends from the post-image it signed. Those are different claims, and the code was quietly trading on the second while only earning the first.
They proposed two repairs. (a) rewrite the params into a verified no-op — not expressible here: writeSchema is { path, content } and nothing else, so no returned parameter can make the native write conditional on a digest, and returned params are lastDefined(acc, next), so a later plugin overwrites them anyway. (b) re-hash afterwards — that detects rather than prevents, and after_tool_call is fire-and-forget. What ships is the stronger form of (a): the call is stopped. block is the only terminal instrument in the result vocabulary, and the wrapper returns without ever calling execute(). Zero unmediated byte movement, guaranteed by OpenClaw's control flow rather than by ours.
Defect 2 — a courteous refusal that was also a routing hint.cfg.tools was write alone, spelled separately at four call sites. During a membrane outage write got blocked with a polite explanation — which OpenClaw hands to the model verbatim as the tool result — while edit and apply_patch walked straight through. The explanation was a usable hint pointing at a tool that still moved bytes. One exported MEDIATED_TOOLS now covers all three; edit and apply_patch describe a delta this plugin does not reimplement, so they are covered by refusal, not by escrow.
Why the wrong comment looked right. The negative-control suite carries its own control showing the old pass-through really was a no-op when undisturbed. That is exactly why it read as fine for as long as it did: the failure needs a disturbance, and nothing in the demo supplied one. 7 failures before the fix, 0 after, 7 again on revert.
Still not fixed, and now written down rather than narrated at runtime:bash remains an unmediated path to the filesystem and cannot be mediated by this plugin — that gap is declared statically in docs/LIMITS.md. The drift case is proved by the unit test, not by the gx-backed demo. And OpenClaw records an admitted write as blocked, so telemetry built on its blocked diagnostics and security events will miscount.
Where the code is right now. The fix is committed but has not reached this repository yet — main here still has the old plugin.ts, and src/unmediated-writes.test.ts is not here at all. If you are reading that example today, you are reading the version with defect 1 in it. It lands on the next sync.
If you read something here and think it is wrong, say so. This one was.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
Uh oh!
There was an error while loading. Please reload this page.
Someone outside this project read
examples/openclaw-plugin-democlosely enough to find two real defects in it, and one of them had already been argued away in a code comment as intentional. Writing it down here because the correction is more useful than the fix.Who found it. ANP2 Network, in a comment on the dev.to post about that example. Neither defect was found by us. Both were confirmed against the code before anything was changed.
Defect 1 — the receipt proved the transformation, not the disk.
admitreturned{ params }, which lets OpenClaw's ownwritere-apply bytesgxhad already applied. A comment in the source defended this as a convergent re-application and "a property of the design, not an omission". That reading is wrong, and ANP2's is right: the re-application is unconditional. Anything that lands betweengx's commit and that native write — a later hook in the same chain, a background process, a file watcher, the agent's own unmediatedbash— gets overwritten by bytes no receipt covers and no inverse can restore. One caller reaches this. No concurrency is required.The sharp version: a receipt proved the transformation was valid. It never proved that what is on disk descends from the post-image it signed. Those are different claims, and the code was quietly trading on the second while only earning the first.
They proposed two repairs. (a) rewrite the params into a verified no-op — not expressible here:
writeSchemais{ path, content }and nothing else, so no returned parameter can make the native write conditional on a digest, and returned params arelastDefined(acc, next), so a later plugin overwrites them anyway. (b) re-hash afterwards — that detects rather than prevents, andafter_tool_callis fire-and-forget. What ships is the stronger form of (a): the call is stopped.blockis the only terminal instrument in the result vocabulary, and the wrapper returns without ever callingexecute(). Zero unmediated byte movement, guaranteed by OpenClaw's control flow rather than by ours.Defect 2 — a courteous refusal that was also a routing hint.
cfg.toolswaswritealone, spelled separately at four call sites. During a membrane outagewritegot blocked with a polite explanation — which OpenClaw hands to the model verbatim as the tool result — whileeditandapply_patchwalked straight through. The explanation was a usable hint pointing at a tool that still moved bytes. One exportedMEDIATED_TOOLSnow covers all three;editandapply_patchdescribe a delta this plugin does not reimplement, so they are covered by refusal, not by escrow.Why the wrong comment looked right. The negative-control suite carries its own control showing the old pass-through really was a no-op when undisturbed. That is exactly why it read as fine for as long as it did: the failure needs a disturbance, and nothing in the demo supplied one. 7 failures before the fix, 0 after, 7 again on revert.
Still not fixed, and now written down rather than narrated at runtime:
bashremains an unmediated path to the filesystem and cannot be mediated by this plugin — that gap is declared statically indocs/LIMITS.md. The drift case is proved by the unit test, not by thegx-backed demo. And OpenClaw records an admitted write as blocked, so telemetry built on its blocked diagnostics and security events will miscount.Where the code is right now. The fix is committed but has not reached this repository yet —
mainhere still has the oldplugin.ts, andsrc/unmediated-writes.test.tsis not here at all. If you are reading that example today, you are reading the version with defect 1 in it. It lands on the next sync.If you read something here and think it is wrong, say so. This one was.
All reactions