Skip to main content
Last updated 2026-08-04. This page describes what is actually built today, including the parts that aren’t. Questions, or a security review: support@sente.run. Sente holds two kinds of secrets for you: account credentials (a password, optionally a TOTP seed) and live session state (the cookies and storage of a logged-in browser). Everything below says where each one lives, which code path can read it, and what is logged when it is used.

The core design: your agent never holds the credential

The point of the product is that credentials move out of your prompts, your .env, and your agent’s context into a server-side vault. Your agent asks Sente for a logged-in account and gets back one of:
  • an interactive live view URL (human-watchable, take-over capable),
  • a CDP URL to drive with your own Playwright or Puppeteer,
  • an exported Playwright storageState to load into your own browser stack.
It never receives the password or the TOTP seed. For a connected account — one you already own and delegated — the credential goes in write-only: GET /v1/registrations/:id/credentials returns 403 CREDENTIALS_WRITE_ONLY, permanently, with no override. Rotation is re-submitting the credentials. For a Sente-created account the generated password is retrievable through that endpoint — we generated it for you and you need it — and every read is written to the audit log.

At rest

  • Passwords and TOTP seeds are encrypted with AES-256-GCM before they reach the database: a fresh 12-byte IV per value plus the GCM authentication tag, stored as ciphertext only.
  • Generated passwords are 20 characters from a CSPRNG, mixed character classes.
  • The key is a 32-byte value held in the API and worker process environment on our production host. It is an application-level key — not a KMS- or HSM-held key. That upgrade is on the roadmap; this page will say so when it ships, not before.
  • Exactly two code paths call the decrypt function: the run controller, at the moment a register/login/connect run needs to type the credential, and the credentials endpoint above — which is 403 for connected accounts. So for a connected account, only the run controller ever decrypts it.
  • API keys (sk_sente_…) are stored as SHA-256 hashes, never in plaintext, are scoped to one organization, and are revocable from the dashboard. Every identity, account, run, message, and audit row is org-scoped at the query level.

During a run: what the models see

Two different models touch your data, with very different exposure.
1

The browser-driving agent gets the password

It is Claude, via the Anthropic API. When it must type your password into a login form, it receives it — the same way a human contractor would have to. Mitigations:
  • The run executes in an isolated remote browser session on a dedicated per-identity browser profile — persistent across that identity’s own runs so logins survive, never on your machine, never shared with another identity.
  • The password is registered in the agent’s run-lifetime redaction vault before the session starts, so it is value-matched and scrubbed out of the agent’s logs and the persisted run record (step text, tool-call arguments, selector and semantic hints, captured URLs). The vault is cleared at run end.
  • Anthropic does not train on API traffic.
2

The TOTP seed never reaches any model

When a site asks for an authenticator code, the driving agent stops and reports that it is blocked. Our server computes the current RFC-6238 code from the vaulted seed and injects only that six-digit code into the session. The seed itself is never in a prompt, a log, or a response. Wrong-seed or clock-skew loops are capped at 3 injections per run, after which the run hands off to a human.
3

The email classifier treats mail as hostile input

Anyone on the internet can send mail to an @sente.run address, so the model that classifies inbound email (OTP / magic link / other) runs with a fixed system prompt, the email passed as labelled untrusted data, no tools, and schema-constrained output that is re-validated server-side. A hostile email can at worst be classified wrong — it cannot make the extractor act.

Session handoff

  • One live session per identity. Requesting a second returns 409 rather than opening a parallel browser on the same profile.
  • Freshness, not hope. getSession hands the session back only if a login was confirmed within the freshness window (10 minutes by default); otherwise it re-logs in first, from the vault plus a fresh code from the account’s own inbox, so what you get was just confirmed logged in.
  • The CDP URL is a pass-through of the upstream browser provider’s session endpoint. It is session-scoped, expires with the session, carries no account key, and is never stored on our side — a new session mints a new one.
  • A session.export is written to the audit log every time a storageState is exported.
An exported storageState or a CDP URL is a bearer credential: whoever holds it has the logged-in session it names, without a password. Treat it exactly like a secret — don’t log it, don’t commit it, don’t pass it through a third party. Its safekeeping on your side is yours.

Audit trail

Credential and session access is written to an insert-only audit table, readable at GET /v1/audit-events and in the dashboard’s Security tab. The recorded actions are: registration.create · connection.create · connection.revoke · connection.delete · credentials.read · credentials.write · session.open · session.close · session.export · run.resume · run.intervene · run.abort · identity.delete Each row carries the action, the subject, a timestamp, and a small meta object. Writers never put secret values in meta, and the endpoint never returns a credential. See Audit trail.

Blast-radius controls

  • Per-organization caps on identities, outbound sends, and runs — see Limits.
  • Webhook URLs pass an SSRF guard: http(s) only, and the host must resolve exclusively to public addresses. Loopback, RFC-1918, CGNAT, and link-local ranges (including the cloud metadata endpoint) are denied for both IPv4 and IPv6, and the check runs again immediately before every dispatch, not just at registration.
  • Runs are bounded in time: a 20-minute cap on the browser session, an 8-minute wait for a verification email, a ~10-minute hold when a run blocks for a human, and a 5-minute stale-heartbeat recovery that fails a run whose driver died.
  • A global kill switch pauses all run claiming platform-wide in one operation; queued runs stay queued rather than being lost.

Revocation and deletion

What we don’t have yet

Read this before trusting us with production.
  • No SOC 2. Small team; happy to walk through the posture on a call and answer a questionnaire.
  • No KMS-held keys — the vault key is application-level, as described above.
  • Webhook deliveries are authenticated with a shared secret header (X-Sente-Secret, one secret per organization), not an HMAC signature over the request body. Verify it with a timing-safe compare, and treat the payload as a notification to fetch by id rather than as trusted data.
  • The SSRF guard resolves, then checks. A DNS-rebinding race between the check and the request is theoretically possible; pinning the resolved address is a known follow-up.
  • SMS 2FA and CAPTCHAs stop the run. It blocks, pages you, and waits for a human in an interactive live view. Sente does not attempt to complete a CAPTCHA. See Human takeover.
  • SSO-only accounts (Google/Okta sign-in, passkeys) cannot be connected — there is no password to vault.
  • Subprocessors: Anthropic (the models), a third-party remote-browser provider (the browser the agent drives), and AWS (hosting, database, inbound and outbound email). The current list in full is available on request.

If you’re the RBAC-minded reviewer

Don’t connect your root or production account. Create a scoped account or token for the agent in the target app’s own permission system — staging scope, or a non-destructive production role — connect that, and rotate it on your schedule. Rotation is one call and invalidates nothing else. Your model never sees the secret at any point in that loop, and your .env stays empty.

Next steps

Audit trail

Every credential and session access, queryable.

Limits

The caps, and which error code each one returns.

Acceptable use

What Sente is for, and what we refuse.
Abuse reports: support@sente.run.