Safety & Secrets
Some rules are not tunable. They hold regardless of your run policy, and each ships with a test that asserts it refuses.
Opt-in navigation guard (0.14.0)
The allowlist check after each settle runs after the other page has loaded. --block-off-allowlist-navigation (MCP run_exploration.blockOffAllowlistNavigation, suite option of the same name) stops it earlier, for mission runs only (goal, usability, adversarial, coverage, exploratory, --feature):
- A page navigation the page starts itself to an origin outside the allowlist (a link,
location.href, a form GET, a popup) is answered204in the mission's browser before it loads, so the page stays put. - The run treats it as a departure: a goal run stops
blockedwith the URL in the transcript, adversarial lists it inscope.departuresand resets, coverage, exploratory and feature runs record it and never expand it. - Server redirects still pass, so the after-settle check stays the guard. Subresources and fetch/XHR load as usual, and the HTTP cache is off for a guarded run.
- It never applies to login,
journey run,source run,load,recordordemosessions. Without the flag nothing changes.allowedOriginsis the session's authorized origins (it scopes the geolocation grant), not a network guard.
Hard floors
- Secrets & PII are redacted before any Jev or LLM call. The generative model never picks a real recipient for a real send.
- Prompt-injection guard is always on — page text is untrusted data, never instructions.
- Every autonomous run is bounded by action and decision ceilings, time budgets and a no-progress detector; a page that stops answering is closed by a 60 s watchdog. Never unbounded.
- Secrets are masked in pixels in every video and screenshot, by a display-only layer that never changes the page, including secrets the app reveals mid-run (elements marked
data-jevitate-maskor similar, and credential-shaped values). A capture whose mask can't be proven is not written. Since 0.15.0 the mask uses only the browser's ownrequestAnimationFrame,MutationObserver,getBoundingClientRectandRange.getClientRects, captured before any page script, so a page that replaces them (a polyfill, an animation library, test hooks) cannot make a frame show a secret. If one was already replaced before the mask loaded, it fails closed: the viewport is painted over and screenshots and capture sessions are refused withE_PIXEL_MASK(reasonuntrusted-primitive). - API keys are checked, never shown. A key is verified with its provider by a non-billable auth call; its value is never echoed, logged or sent to a model, and key entry is never offered over MCP.
- Every executed step is recorded and auditable — even a full-autonomous run is replayable and reviewable after the fact.
- Pacing is realism and politeness, never detection-evasion.
The secret invariant
A secret has exactly one permitted path:
external password manager (fetched on demand)
→ the browser input
→ the target site's own auth call It is never placed in a model prompt, never persisted in a Journey, recording, or log, and never in any outbound request except the target's own auth call. It is origin-bound — a secret can never be typed into a different origin or an injected/redirected field. Nothing is stored at rest. Jevitate holds only a manager reference.
How the secret is supplied is configurable via SecretPolicy.secretMode:
| Mode | Behavior |
|---|---|
visible-handback | Headed browser, the human enters it, resume only on a passing postcondition |
vault-autofill | Fetched from an external manager on demand, origin-bound, nothing stored |
fail-closed | Stop rather than proceed without a secret |
The containment and origin-binding above are not tunable — only how the secret is supplied.
Fail-fast invariants
These are permanent, named contracts — each with an "asserts-it-refuses" test:
- No policy → throw. A missing or partial
RunPolicyraises rather than defaulting permissively. - Secret never serialized. Serializing or logging a secret throws. Only a reference may be stored.
- Origin mismatch → refuse. Before any fill, current origin must match the secret's bound origin.
- Unknown journey/param → reject. Only published ids and schema-valid params run — no inline steps, ever.
- Resume only on postcondition. Handback and self-heal resume require a passing check. A timeout fails closed.
- Writes never auto-heal. Write, irreversible, or real-send steps fail closed for human-approved repair.
Safety boundaries
Jevitate assumes you're testing systems you're authorized to test. Every run is restricted to
the origins you authorize, and session-ending, destructive and paid controls are refused unless
you explicitly allow them. Every write request a run fires is listed in its result
(sideEffects):
# only these origins (the --url's own origin when --allow is omitted)
jevitate explore --url http://localhost:3000/settings --allow http://localhost:3000 ...
# --allow REPLACES the start origin, so list it too. Since 0.13.0 the blocked-write hint prints
# the full list to set (start origin included), and E_UNAUTHORIZED_EXPLORE_TARGET says the start
# URL's origin must be in it. The list itself is exact: no implicit origins once --allow is given.
# sign-out, delete/revoke/rotate and paid controls are refused by default;
# add your own, or lift the default deliberately
jevitate explore ... --deny '/Archive/i' --deny 'role=button;name=Send invite'
jevitate explore ... --allow-destructive - Every refusal names its rule (0.8.0). A refused step's reason ends with the rule it
matched, for example
[rule builtin:may-cost-money, matched "Generate"], and the transcript entry carriessafety: { ruleId, pattern, control, risk, waivable }.jevitate site policy rules(--json) lists every rule and whether it can be waived. -
--allow-control <regex>(0.8.0). Exempts a control whose accessible name matches from the soft built-in "may cost money" heuristic only, for that run (--allow-control "^Generate Your First Key$"). It never lifts--deny,--paid, destructive, sign-out, read-only or origin rules, and never applies to native dialogs. An invalid, empty or match-everything regex is refused before any browser opens. Every click it permits is recorded in the result'ssafetyOverrides. It is available on explore, explore-author-journey, campaign run, sweep targets, suite items and MCP (allowControl). - Find-out goals are read-only. A goal with no
--successrefuses controls that start a write flow and blocks the app's own write requests, unless the goal asks for a change or you pass--allow-writes(--allow-write <glob>exempts one path). A goal with no--successthat asks for a change still never performs a destructive write without--allow-writesor--allow-destructive: the control is refused and its destructive request blocked. - Security resets are destructive. "Reset authenticator", "Disable two-factor",
"Turn off 2FA", "Reset password", "Unlink security key", "End all sessions" and similar controls
are refused without
--allow-destructive(or a goal that asks for that action), like "Delete" and "Revoke". A bare "Reset" or "Reset filters" is unaffected. - Dialogs are dismissed by default and logged. A native
window.confirmorpromptan action raises is recorded on the step.--dialogs acceptconfirms it, but a dialog that names a destructive, session-ending or paid action the run may not take, or matches--deny, is still dismissed. - The usability guard probe is opt-in and fail-safe.
--probe-guardsclicks destructive controls only to see whether they ask first: it aborts every write and destructive-looking request, refuses pages with a WebSocket, EventSource or service worker, and never answers a dialog. Point it at staging. - Extensions widen nothing else.
--extensionallows only the loaded extensions'chrome-extension://<id>origins; other extension origins stay refused. - Resource governance only touches its own. Orphan cleanup signals only browsers jevitate launched (found by an owner marker), never another process.
- Hang replays never re-send a paid or destructive write. Such a hang is
reported
inconclusiveunless you pass--hang-replay-writes.--paid <pattern>declares your app's own paid controls. - Environments bound Journey runs. With
--env, the run's allowlist is the environment'sbaseUrlplusallow; a step on another origin is refused before a browser opens..jevitate/environments.jsonmay not hold a secret or a session. -
demonever runs on production. It needs a named environment and refuses one flagged"production": true, when drafting and again ondemo approve. - MCP can't widen any of this. Raw browser tools are forbidden, missions only
target promoted targets, every path argument must resolve inside the project or
~/.jevitate/, and operator-only settings (shell hooks, the browser binary, which environment variable a secret comes from, log sources) are never MCP arguments. Approving or cancelling an inbox item is human-only.
--fixtures or --before/--after hooks, which also run around
every replay.
measured or modeled and never silently downgraded.