From 2f55923456560a620378a0da662da2ea355ec55f Mon Sep 17 00:00:00 2001 From: Hogne <227774406+hognek@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:01:33 +0200 Subject: [PATCH] =?UTF-8?q?fix(store):=20resolve=20Kilo=20WARNING+SUGGESTI?= =?UTF-8?q?ON=20=E2=80=94=20date-safe=20canonicalisation=20+=20async=20TOC?= =?UTF-8?q?TOU?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - _canonical_manifest_bytes: add default=str to json.dumps so yaml.safe_load-produced date/datetime values don't cause signing failures on legitimate manifests (Kilo WARNING, registry.py:152) - TOCTOU re-verify: wrap disk read + Ed25519 verify in asyncio.to_thread to avoid blocking the event loop under load (Kilo SUGGESTION, store_install.py:796) - TOCTOU re-verify: fail-closed on read/parse/signature-lookup failures — _toctou_reverify now returns False (block install) when manifest.yaml is missing, unreadable, malformed, or the stored signature cannot be retrieved (CodeRabbit CRITICAL) --- tinyagentos/routes/store_install.py | 73 +++++++++++++++++------------ tinyagentos/store_signing.py | 4 +- 2 files changed, 45 insertions(+), 32 deletions(-) diff --git a/tinyagentos/routes/store_install.py b/tinyagentos/routes/store_install.py index 6f52cc973..400494b6a 100644 --- a/tinyagentos/routes/store_install.py +++ b/tinyagentos/routes/store_install.py @@ -791,6 +791,9 @@ async def install_app(request: Request): # Re-read from disk and re-verify the signature — if it no longer # verifies, the manifest was modified after the gate check and the # install is blocked. + # + # Run via asyncio.to_thread to avoid blocking the event loop on disk + # I/O + Ed25519 verification under concurrent load. _manifest_dir = getattr(manifest, "manifest_dir", None) if ( _manifest_dir is not None @@ -799,38 +802,46 @@ async def install_app(request: Request): and registry is not None and hasattr(registry, "verify_manifest_signature") ): - disk_path = _manifest_dir / "manifest.yaml" - try: - import yaml as _yaml - on_disk = _yaml.safe_load(disk_path.read_text()) if disk_path.exists() else None - except Exception: - on_disk = None - if on_disk is not None: - # Re-verify the signature against the just-read bytes. - # This catches any change — not just the narrow set of fields - # the old comparison whitelisted — and does not false-positive - # on legitimate catalog reloads (which update signatures too). + + def _toctou_reverify(): + # Fail-closed: any inability to re-read or re-verify the + # manifest blocks the install. The first gate already proved + # the manifest was valid; at this point a missing, unreadable, + # or unsigned manifest means post-verification tampering. + disk_path = _manifest_dir / "manifest.yaml" + try: + import yaml as _yaml + if not disk_path.exists(): + return False + on_disk = _yaml.safe_load(disk_path.read_text()) + except Exception: + return False + if not on_disk: + return False stored_sig = registry.get_signature(manifest_id) - if stored_sig is not None: - from tinyagentos.store_signing import verify_manifest_signature as _verify_sig - if not _verify_sig(on_disk, stored_sig, _store_pub): - progress.finish( - install_id, success=False, - error="manifest modified between signature verification and install", - ) - return JSONResponse( - { - "error": "manifest modified between signature verification and install", - "detail": ( - "The manifest on disk was modified after the initial " - "signature verification. This may indicate post-verification " - "tampering. Rebuild the catalog or reinstall the app from " - "a trusted source." - ), - "install_id": install_id, - }, - status_code=403, - ) + if stored_sig is None: + return False + from tinyagentos.store_signing import verify_manifest_signature as _verify_sig + return _verify_sig(on_disk, stored_sig, _store_pub) + + if not await asyncio.to_thread(_toctou_reverify): + progress.finish( + install_id, success=False, + error="manifest modified between signature verification and install", + ) + return JSONResponse( + { + "error": "manifest modified between signature verification and install", + "detail": ( + "The manifest on disk was modified after the initial " + "signature verification. This may indicate post-verification " + "tampering. Rebuild the catalog or reinstall the app from " + "a trusted source." + ), + "install_id": install_id, + }, + status_code=403, + ) # Non-commercial weights gate (#169): a manifest's code license (MIT etc.) # can be permissive while the model weights it downloads are not (e.g. diff --git a/tinyagentos/store_signing.py b/tinyagentos/store_signing.py index be8b49832..45d187503 100644 --- a/tinyagentos/store_signing.py +++ b/tinyagentos/store_signing.py @@ -235,7 +235,9 @@ def _canonical_manifest_bytes(manifest_dict: dict) -> bytes: keys or change scalar representations. """ stripped = {k: v for k, v in manifest_dict.items() if k != SIGNATURE_FIELD} - return json.dumps(stripped, sort_keys=True, ensure_ascii=False).encode("utf-8") + return json.dumps( + stripped, sort_keys=True, ensure_ascii=False, default=str + ).encode("utf-8") # ---------------------------------------------------------------------------