Userz

Security model

What we encrypt, where customer data lives, and the guarantees the sandbox provides.

Sandboxing

Every AI agent run executes in a brand-new, single-use sandbox with its own kernel-level isolation boundary. No shared state with any other run, no shared state with the host. A kernel-level exploit in the agent or one of its tools cannot escape to the host or to another customer's run.

Per-run guarantees

  • Non-root execution, dropped capabilities, read-only root filesystem, restricted syscall surface.
  • No platform credentials inside the sandbox: it cannot call back into Userz's own control plane.
  • Resource caps: 2 CPU / 4 GiB RAM / 8 GiB ephemeral storage / 20 minute wall clock.
  • Outbound traffic is restricted to an allowlist (source-control providers + the AI provider you've chosen). Anything else is dropped at the network boundary.

Secret handling

We use two-layer envelope encryption. Your AI provider keys, repository tokens, chat-bot tokens, and per-App signing secrets are each encrypted with a key unique to your organization, which is itself wrapped by a master key held in our secret store and never written to a database.

AI provider keys are decrypted ONLY in memory, just before the run that needs them, and mounted into the sandbox as files. They never reach the sandbox process via environment variables (which would leak via /proc/<pid>/environ) and are wiped on completion.

Repo access scope

GitHub. The Userz GitHub App requests three permissions only:

  • contents: write: to push the agent's branch.
  • pull_requests: write: to open the draft PR.
  • metadata: read: required by GitHub for any App.

We do not request workflow, secrets, actions, administration, or any organization-level scope. We mint a fresh 1-hour installation token scoped to the single repo for each agent run. A leaked token cannot enumerate or modify any other repo in your org.

GitLab. Customers supply a per-project access token (recommended) or an OAuth token with the api scope filtered to a single project. The token is encrypted at rest with a key scoped to your organization, decrypted only in memory at the moment a run starts, never lands in plaintext on disk, and never appears in logs. Self-hosted GitLab is supported via a per-Repo baseUrl override.

Diff guardrails

Before the agent's branch is pushed, we run a post-flight check on the diff and refuse to push if any forbidden path was touched:

  • .env, .env.*: any environment file
  • .github/workflows/**: CI definitions
  • .git/**: git internals (defends against symlink shenanigans)
  • infra/**: IaC
  • scripts/release/**

Rejected diffs surface in the dashboard as pr_failed with the file + rule that tripped the guardrail.

Prompt-injection defenses

End-user feedback is treated as untrusted input throughout. Layered defenses:

  1. Hard limits: 8 KB cap on text, 500-line cap on console logs, 10 attachments max, 8 MB body cap on the ingest endpoint.
  2. Pattern pre-filter: known-injection markers (<system> tags, "ignore all previous instructions", role-override phrasing, very long URLs, large base64 blobs) feed soft signals into the next stage.
  3. Classifier: a separate small classifier model labels the report benign | spam | malicious_injection | abuse | off_topic. It runs in its own context window with no shared state with the coding agent.
  4. Wrapped delivery: the agent's user prompt embeds the feedback inside <UNTRUSTED_USER_REPORT> tags with explicit system-prompt instructions that anything inside is data, not instructions.
  5. Tool restriction: the agent is invoked with a read/edit/search/write toolset only. Shell access is restricted to git operations. No general-purpose shell.
  6. Diff guardrail + human review: covered above.
No prompt-injection defense is perfect. The agent's tool restrictions, the sandbox isolation, the scoped repo token, and the explicit human-in-the-loop PR review are the load-bearing protections, the classifier is harm-reduction, not a guarantee.

Authentication

  • Dashboard sessions use industry-standard session cookies: HttpOnly, SameSite=Lax, Secure in production. 30-day expiry, 24-hour activity refresh. Google OAuth and email sign-in are supported.
  • Customer API keys (sk_…): high-entropy random tokens, salted and slow-hashed at rest, prefix-indexed for O(1) lookup, constant-time comparison on verify.
  • Widget public mode: App's pub_… key + Origin allowlist enforced per-App.
  • Widget private mode: your backend signs an HS256 JWT with the App's per-App signing secret. The widget includes it in X-Userz-User-Token and we verify signature + exp + app-id binding.
  • Webhooks: every inbound webhook (Stripe, GitHub, GitLab, Slack, Discord) is signature-verified with the provider's published scheme, with a strict timestamp tolerance window and a replay-guard cache that rejects duplicates.

Where customer data lives

  • Primary datastore: feedback text, console logs, batch summaries, audit events, org/app/repo metadata, and encrypted secrets.
  • Private object storage: screenshot attachments and full sandbox run logs. Lifecycle-deleted after 30 days unless customer-marked for retention.
  • Ephemeral cache: session cache, queue state, rate-limit buckets, webhook replay guard. No persistent customer data, reconstructible at any time.
  • Source code: only inside the per-run sandbox; cloned via a short-lived scoped token and deleted when the sandbox terminates. Never persisted on our side.