v2.2.1
Patch release: optional WebAuthn verification, compliance and audit CLIs, docs, and CI baselines.
Added
- WebAuthn (optional extra):
asap-protocol[webauthn]enables real registration/assertion verification whenASAP_WEBAUTHN_RP_IDandASAP_WEBAUTHN_ORIGINare set; otherwise behavior matches v2.2.0. See v2.2.0 → v2.2.1 migration. asap compliance-check: Runs Compliance Harness v2 against an agentHTTP(S)base URL;--output {text,json},--exit-on-fail,--timeout,--asap-version. Documented in docs/cli.md and docs/ci-compliance.md (Actions example with--exit-on-fail).asap audit export: Exports hash-chained audit rows from SQLite or an
in-memory store;--verify-chainfails on tampering. Documented in
docs/cli.md and docs/audit.md.apps/example-agent: Minimal installable example; CI runs Harness v2 and
fails on score < 1.0 (regression guard).
Security
python-dotenv:tool.uv.override-dependenciespins ≥ 1.2.2 (CVE-2026-28684)
on transitive installs.apps/web: Dependency updates closing npm audit findings (Next.js 16.2.4;
micromatchsubtree forced topicomatch≥ 2.3.2).
Changed
- CLI package layout: Typer subcommands live under
asap.cli/src/asap/cli/
(replacing the monolithiccli.pymodule);asapconsole script unchanged. ResolvedAgent.run(): Tightens the contract from "tri-branch dict[str, Any]" to
"the TaskResponse.result dict, or empty dict if result is None". A protocol violation
(server responds with anything other than a TaskResponse envelope) now raises
TypeErrorso it surfaces at the call site instead of silently coercing into a dict.
Return type annotation remainsdict[str, Any]; no caller API change. Closes the
deferred follow-up from v2.1 PR-73 review.- Dependency pins:
cryptography,authlib,joserfc,pyjwt,webauthn(extra),
andpydanticnow carry explicit upper bounds to the next major version so breaking
upstream releases can't auto-install. Policy + bump procedure documented in
SECURITY.md §Dependency policy. - ID generation:
asap.state.stores.sqlite,asap.economics.storage,
asap.economics.sla, andasap.integrations.a2hnow generate entity IDs through
asap.models.ids.generate_id()(ULID) instead of ad-hocuuid.uuid4(). The
slowapi storage-URI suffix inasap.transport.rate_limitremainsuuid.uuid4().hex
because it is a backend namespace, not a domain identifier; an inline comment
documents the distinction. asap.economics.storageaggregate dispatch: The six-branchcast(list[UsageAggregate], ...)
clusters inInMemoryMeteringStorage.aggregateandSQLiteMeteringStorage.aggregateare
consolidated into a single_dispatch_aggregate(events, group_by)helper. Eight
scattered casts become three, all documented in one place.asap.transport.serverhelper types:_validate_envelopeand_dispatch_to_handler
now return a discriminableEnvelopeOrError = JSONResponse | tuple[Envelope, str]
union instead oftuple[Envelope | None, JSONResponse | str]. Callers narrow via
isinstance(result, JSONResponse)and no longer needcast()at the three call
sites. Internal-only refactor; public API unchanged.WebAuthnVerifierImpl.start_webauthn_*: Returns the full
PublicKeyCredentialCreation/RequestOptionsdict from
webauthn.helpers.options_to_json_dict(previously a bare base64url
challenge). Integrators building a browser adapter getrp,user,
pubKeyCredParams,allowCredentials, anduserVerificationout of the
box and no longer need to hand-assemble the options envelope.default_webauthn_verifier(): The returned verifier is now cached
per-process (keyed byextra_installed / rp_id / origin). Pending
WebAuthn challenges persist across requests when the defensive fallback in
agent_routes._webauthn_verifieris hit — previously each fallback call
rebuilt an emptyInMemoryWebAuthnCredentialStore, silently discarding
anystart_webauthn_assertionstate. Tests can reset the cache with
asap.auth.self_auth.reset_default_webauthn_verifier_cache().
Fixed
- Swallowed exceptions in
finish_webauthn_assertion: Bare
except Exception:blocks now catch the specific
InvalidAuthenticationResponse/ValueError/TypeErrorclasses and
emit structured warnings (asap.webauthn.assertion.invalid,
.malformed_challenge,.challenge_mismatch,.unknown_credential)
viaasap.observability.get_loggerso SIEM rules can detect
cloned-authenticator and replay patterns. WebAuthnCeremonyErrorpayload: Publicdetailis now a stable,
PII-free identifier (webauthn_registration_state_missing,
webauthn_registration_verification_failed).host_idand the upstream
library reason are logged internally instead of leaking into the error
surface.audit export --verify-chain: Replaces fragile string-matching on
"AUDIT_CHAIN_BROKEN"with a dedicatedasap.economics.audit.AuditChainBroken
exception. Invalid--since/--untilISO-8601 values now surface as
typer.BadParameterinstead of an unhandled traceback.- CSV export determinism:
asap audit export --format csvnow writes
rows withlineterminator="\n"for reproducible diffs on Linux CI. - Test fixtures:
tests/cli/test_compliance_check.pyuvicorn fixtures
now stop the server cooperatively viaserver.should_exit = True+
thread.join, preventing the leaked-socket flakes flagged under
pytest-xdiston long suites.
Known limitations
- No reference HTTP enrollment route for the WebAuthn registration
ceremony.WebAuthnVerifierImpl.start_webauthn_registration/
.finish_webauthn_registrationare Python-only helpers in v2.2.1;
integrators must expose their own adapter route (see
docs/security/self-authorization-prevention.md).
A first-classPOST /asap/agent/webauthn/register/{begin,finish}is
tracked for v2.3 (Adoption Multiplier), where new endpoint surface area
is permitted.
Full Changelog: v0.1.0...v2.2.1