Repository navigation
feat(vault): browser vault — save/restore session profile via /api/vault/sessions - #23
Merged
Merged
Conversation
added 2 commits
September 4, 2026 16:55
…ult/sessions - ceki_sdk/_vault.py: ClientVault HTTP CRUD (list/get/create/update/delete), BrowserVault.save (profile.export → vault envelope) and restore (session.configure profile+fingerprint), cookie sanitization to the settable CDP subset, flat-profile → per-origin envelope normalization. - Client.rent(vault=...) restores the vault profile before applying fingerprint/masking; the browser is bound to the vault session id so a later browser.vault.save() overwrites it (PUT). - Browser.vault surface wired in __init__; CLI rent --vault SESSION_ID (one-shot + daemon path) and README vault section + example script. - tests/test_vault.py: 18 unit tests (mocked httpx + mocked relay); live dev-API roundtrip verified separately.
Task 11622 (Vault 7). Adds the `ceki vault` subcommand surface on top of ClientVault/BrowserVault from Vault 6 (SDK): - vault list [--json] [--per-page N] - vault get ID [--json] [-o FILE] (decrypted profile) - vault save FILE [--id ID] [--label L] - vault save --session SID [--id ID] [--no-session-storage] (live snapshot) - vault apply ID --session SID | --schedule N - vault delete ID Vault commands run over plain HTTP (no relay session), using the same Client/api_url + basic-auth overrides as connect(). Also fixes `ceki rent --vault` not forwarding the vault id through daemon IPC. Tests: tests/test_cli_vault.py (parser + mocked handler coverage).
Co-Authored-By: Claude Code <noreply@anthropic.com>
Cosmetic only — no logic change.
- unused imports (AsyncMock/MagicMock/os/PropertyMock) removed - import block sorted (I001) - two over-long lines wrapped (E501) - duplicate test_dispatch_webrtc_answer_no_ice_servers definition dropped (identical duplicate; kept the fuller variant with wait_dc_open) No logic change; 163 tests still pass.
… W292) - _browser.py: wrap 3 over-long log.debug lines - _webrtc.py: drop redundant aiortc.RTCIceCandidate import in add_ice_candidate (_parse_ice_candidate imports it itself), sort imports, wrap long type hints - examples/vault_roundtrip.py: trailing newline No behavior change; ruff check . clean, vault+p2p tests green.
ceki rent without CEKI_API_KEY used to reach the daemon path and exit with a confusing code=daemon (6) when a daemon was running. Require the key up front so the CLI exits cleanly with code=auth (2), matching the test expectation and the one-shot fallback behavior. Fixes test_missing_api_key_exits_2 (41 cli tests pass).
… preserve -1011 reason Two real defects surfaced by the mock-relay tests (which measure actual SDK behavior): 1. Client.rent() waited for _p2p_ready for up to 15s even when no P2P signaling had started (_p2p is None — e.g. relay/extension that never answers a webrtc.offer). Every rent paid a 15s stall on the WS path, growing to ~45s per rent/cdp test. Only wait when P2P was actually initiated (self._p2p is not None). 2. Relay error -1011 (heartbeat_timeout) / -1018 was routed through browser._on_session_ended(), which set reason="completed" when the message had no "reason" field — clobbering the real terminal reason and never calling _on_error (which knows heartbeat_timeout). Route -1011/-1018 through _on_error (correct reason + exception), then run the session.ended cleanup hook. _on_session_ended also now refuses to overwrite an already-set precise reason with a generic "completed". Tests: test_rent_flow 7/7, test_browser_cdp 4/4, test_multi_session 2/2, test_error_1011_heartbeat_timeout pass; vault 40/40 still green.
…p-race tests - Browser.send(): if the P2P DataChannel doesn't open within a short grace (3s instead of 30s), fall back to WS for the whole session (set _p2p_fallback) — previously every subsequent send() re-paid the 30s wait_dc_open() stall, and send(timeout=5) tests always timed out. - tests/test_browser_errors.py: wait up to 5s (was 0.05s/1s) for the client's cdp WS message before asserting — the fixed 0.05s sleep was a race under CI load (cdp not yet written to relay.received). - tests/test_daemon.py: -1011 now routes through _on_error (preserves reason) then the cleanup hook; updated the test to assert that. All previously-failing CI tests green locally: rent_flow 7/7, browser_cdp 4/4, browser_errors 4/4, multi_session 2/2, daemon 1011 ok; vault 40/40 still pass.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vault 6: Python SDK — работа с vault
Адаптация Python SDK под vault (спека Vault 1-5, бэкенд
/api/vault/sessions).Что сделано
Новый модуль
ceki_sdk/_vault.py:ClientVault— HTTP CRUD:list(),get(id)(decrypted profile),create(data, label),update(id, data),delete(id).BrowserVault— два сахарных слоя на живом браузере:browser.vault.save(label=...)—profile.export()→ нормализация в per-origin envelope → POST/PUT в vault (PUT, если сессия привязана к rent).browser.vault.restore(id | envelope)— GET vault →session.configure(profile=...): куки сразу, localStorage/sessionStorage буферизуются расширением и флашатся при первой навигации до origin; fingerprint уходит отдельным полем configure.sanitize_cookies— вычёркивает CDP-only поля (priority/size/sourcePort/...), которые браузер не принимает вNetwork.setCookies.normalize_profile_for_vault/minimal_vault_profile— конвертация плоскогоprofile.export()в envelope и обратно к extension-контракту.Интеграция:
Client.rent(..., vault=id | envelope)— restore профиля прямо при аренде (до fingerprint/masking); браузер биндится на vault-сессию (_vault_session_id).ceki rent --vault SESSION_ID(CLI, один shot + daemon path).examples/vault_roundtrip.py.Тесты:
tests/test_vault.py— 18 unit-тестов (mocked httpx + mocked relay WS). Live-раунд-трип против dev-API (list/get/create/update/delete) проверен вручную.Примечания
Authorization: Bearer <api_key>— для vault-операций нужен user-token.dev— MR создан вmaster(каноничный target для этого репо, как и все прошлые релизы).fingerprintиз vault не кладётся внутрьprofileconfigure — применяется отдельнымfingerprint-полем (extension обрабатывает его своим путём, чтобы не было двойного применения).