Live walkthrough safety — the WalkPolicy contract¶
Rung 3 of the Deep Persona ladder: "here's a URL and a test login — go use it." A persona
drives a REAL SaaS in a headless browser. That power needs a hard safety model, and the
model is most of the feature: every live session opened via walk_open runs under a
WalkPolicy (sonaloop/walk_policy.py) the harness (sonaloop/browser.py) enforces
in-session. It is the SAME harness as prototype sessions — proto_act / proto_read /
proto_close drive and end a walkthrough transparently, and
record_usability_session(fidelity='live', session_id=…) persists the verified trace.
The policy object¶
walk_policy_defaults() returns the safe defaults including the resolved denylist;
walk_open(url, persona_id, policy?, credentials?) normalizes a host patch over them:
{
"allowed_origins": ["https://app.example.com"],
"blocked_categories": ["payment", "destructive", "outbound", "account"],
"max_actions": 60,
"max_duration_s": 900
}
allowed_origins— an explicit origin allowlist (scheme + host + port; default ports normalized away). Defaults to the opening URL's own origin. Only http(s) origins are valid; a non-http(s) opening URL is rejected outright.blocked_categories— the deny-by-default risk classes, data-driven fromsonaloop/suggestions/walk_denylist.json(EN + DE terms per category):payment,destructive,outbound,account. All enabled by default; a policy may disable a category explicitly. Add a term, no code change — but never narrow the file to make a session "work": over-blocking is the intended failure mode.max_actions/max_duration_s— hard caps (60 actions / 900 s by default).- Unknown keys, unknown categories, non-positive caps and an opening URL outside the allowlist are rejected at open — the safety contract never degrades silently.
The guarantees¶
- Origin containment. Every observed page state is checked against
allowed_origins— after every action AND on every read, so direct navigations, link-clicks, redirects and delayed JS redirects that fire while the session is idle are all caught. An off-origin landing is undone (the harness navigates back to the last on-policy URL) and comes back as a structured refusal; a redirect at OPEN refuses the whole session (there is no on-policy state to fall back to). An unparseable / non-http(s) landing URL counts as off-origin. - No risky actions. A
click/selecton an element whose accessible name/text matches an enabled denylist category (case-insensitive substring), or an Enter keypress whose focused element / form-submit control matches, is refused before it runs. - Hard caps. Reaching either cap logs
{kind:'cap_reached', cap:…}and auto-closes the browser; the trippingactreturns{cap_reached, closed:true}and every lateract/readfails with the stableCAP_REACHEDerror. - Violations never crash the host loop. Origin and denylist blocks are RESULTS: the
actenvelope carriespolicy_block{rule, detail, …} next to the (unchanged or recovered) snapshot, AND the session log gets the matching entry — the block itself is evidence the replay can cite.
Credentials & the redaction scope¶
Test logins are passed ONCE, at walk_open(credentials={username?, password?}), and live
only inside the session's worker thread. Filling happens via the dedicated act
{type:'fill_credential', field:'username'|'password', ref:…} — the secret never transits
the host loop, and the action echo logs only the field name. Argument values are never
logged at open.
Redaction layer: before any snapshot or log entry is retained or returned, every exact
credential value is replaced with *** — including page echoes ("signed in as …" banners,
input values). The scope is exact-string replacement: pixel content of screenshots and
server-side TRANSFORMS of a secret (hashes, masked variants) are not covered — use
throwaway test accounts, never real ones.
Audit trail¶
The session log is the audit trail, retained past proto_close (the same retention that
grounds prototype sessions): one entry per snapshot (url, title, refs, text,
screenshot), per action (redacted echo), per policy_block and per cap_reached. Every
snapshot also writes a per-step screenshot to
data/sessions/<browser_session_id>/step-<n>.png (fail-soft), referenced from the snapshot
as <browser_session_id>/step-<n>.png relative to the sessions dir —
record_usability_session validates those paths (containment under the data dir stays
enforced) and the replay view serves them via /sessions-files/.