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.
/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/**: IaCscripts/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:
- Hard limits: 8 KB cap on text, 500-line cap on console logs, 10 attachments max, 8 MB body cap on the ingest endpoint.
- 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. - 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. - 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. - 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.
- Diff guardrail + human review: covered above.
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-Tokenand 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.