Files
DECNET/decnet/web/router/canary/__init__.py
anti 6c4ea706f8 feat(api): canary token CRUD router (/api/v1/canary) + tests
Two sub-routers under /api/v1/canary:

blobs (operator-uploaded artifacts, deduped by sha256):
- POST   /blobs          (multipart upload; admin)
- GET    /blobs          (list with token_count; admin)
- DELETE /blobs/{uuid}   (refcount-aware; 409 when referenced; admin)

tokens (per-decky planted artifacts):
- POST   /tokens                          (generate or instrument + plant; admin)
- GET    /tokens?decky_name=&kind=&state= (filter; viewer)
- GET    /tokens/{uuid}                   (detail; viewer)
- GET    /tokens/{uuid}/preview           (instrumented bytes; admin)
- GET    /tokens/{uuid}/triggers          (paged callback log; viewer)
- DELETE /tokens/{uuid}                   (revoke + bus event; admin)

XOR validation: exactly one of blob_uuid / generator must be set.
Path validation rejects relative/NUL/newlines/.. segments. Every
body-bearing route documents 400 plus 401/403/404 as applicable.

Stdlib MIME sniffer (no python-magic dep) covers PNG/JPEG/GIF/PDF/
HTML/XML/DOCX/XLSX/JSON/YAML/TOML/text/plain; everything else falls
through to passthrough.

Tests run end-to-end through the live FastAPI app (planter docker
exec is patched); 17 cases covering dedup, refcount, lifecycle,
XOR validation, path validation, and 404 paths.
2026-04-27 13:18:00 -04:00

24 lines
887 B
Python

"""Canary tokens — operator-facing CRUD.
Mounted under ``/api/v1/canary``. Covers:
* ``POST /blobs`` — upload an artifact (multipart);
``GET /blobs``, ``DELETE /blobs/{id}`` — listing + cleanup
* ``POST /tokens`` — generate + plant a token on a target decky;
``GET /tokens``, ``GET /tokens/{id}``, ``DELETE /tokens/{id}``
— listing + detail + revoke
* ``GET /tokens/{id}/preview`` — instrumented bytes for sanity-check
* ``GET /tokens/{id}/triggers`` — paged callback log
The ``decnet canary`` worker runs the ATTACKER-facing surface (HTTP
slug + DNS); this module is the OPERATOR-facing surface only.
"""
from fastapi import APIRouter
from .api_blobs import router as blobs_router
from .api_tokens import router as tokens_router
canary_router = APIRouter(prefix="/canary")
canary_router.include_router(blobs_router)
canary_router.include_router(tokens_router)