Security and trust

Badgeye has no SOC 2 report and has not been through a third-party penetration test. Below is what the software does today and what it does not.

What it does today

  • The agent runs on your machine and is outbound-only.
  • Passwords are filled by the Blindfold vault and kept out of prompts, logs and recordings (leak tests in CI).
  • A browser egress gate refuses private and internal addresses.
  • Prompt-injection and fraud rules screen mail and pages; flagged payments always need a person.
  • The audit log is append-only, hash-chained and exportable with a signature you can verify offline.
  • Roles, two-step sign-in and OIDC single sign-on for the console.
  • Typed text is captured with consent and masked on the device before it leaves: names, numbers and secrets are removed. Password, secure and one-time-code fields, private windows and paused capture produce nothing.
  • Identity modes: identified, pseudonymous (default) or anonymous, with a floor of at least 5 people before a role or team shows anything.

Not yet

  • No SOC 2 report or other audit
  • No third-party penetration test
  • Signed installers are planned
  • No data residency choice yet
  • Production email and Slack and Teams app review are pending

Controls, mapped to SOC 2 criteria

From docs/SECURITY_CONTROLS.md, read at build time. It is an engineering document, not an attestation.

1. Common criteria (security)

CriterionControlStatusGap
CC6.1 Logical access: identification and authenticationPer-person sign-in: scrypt password hashes with per-hash salt and stored parameters, constant-time compare, equal work for unknown emails; opaque random session tokens stored only as SHA-256, expiring and revocable; roles read from the person on every requestImplementedNo passkeys/WebAuthn (planned). No email verification at invite.
CC6.1Two-step sign-in (TOTP) with sealed seeds (AES-GCM), hashed one-time recovery codes, replay protection, org policy for owners/admins (optionally approvers), audited admin resetImplementedNot tested against every authenticator app.
CC6.1Single sign-on with OpenID Connect (Google Workspace, Microsoft Entra, Okta, generic): authorization code + PKCE (S256), single-use state bound to the browser by an HttpOnly cookie, nonce, issuer/audience/expiry/not-before checks, signature against the issuer's JWKS (asymmetric algorithms only, none/HMAC refused, key type must fit the algorithm), JWKS cache with rotation and a refetch cooldown, per-org email-domain allowlist, verified email required, linking only to people already invited (or auto-create if an owner switches it on, never as owner), subject-based identity after the first link, optional group-to-role mapping (approver/admin only), owner can require SSO with a named break-glass ownerImplementedNot run against a real Google/Entra/Okta tenant. Domain ownership is not verified (two orgs claiming one domain makes SSO ambiguous and is refused, but the first claim is trusted). No SCIM provisioning or deprovisioning: removing someone at the IdP does not end an existing Badgeye session before it expires (session_hours). Microsoft sends no email_verified; the tenant-specific issuer is the control.
CC6.1Brute-force defence: exponential backoff per email + IP pair, per-IP cap, stored in the database so every serverless instance agrees; the limit cannot be used to test guesses; SSO start and callback are rate limited per IPImplemented
CC6.2 Registration and removal of usersInvite by an owner/admin creates a person with a one-time temporary password that must be changed; only an owner can add an owner; MFA reset by an owner/admin is auditedImplementedNo user deprovisioning API or console action beyond ending sessions (planned). No periodic access review tooling (organizational + planned).
CC6.3 Role-based access, least privilege, segregation of dutiesRoles owner/approver/admin enforced in the API; the real signed-in person (not a picker) approves; the service token is for machines and refused from browsers; org-scoped queries; SSO settings are owner-only; signed audit export is owner/admin-onlyImplementedRoles are coarse (three). Fine-grained permissions planned.
CC6.1, CC6.6Secrets never reach the model or logs: Blindfold vault with AES-256-GCM, decrypt only inside the fill, redaction of recordings/logs/model requests, fingerprint scan before anything leaves the agent; SSO client secrets and the audit signing key are sealed with AES-GCM under a key derived from BADGEYE_AUTH_SECRETImplementedBADGEYE_AUTH_SECRET lives in the environment, not an HSM/KMS (planned). Changing it makes sealed values unreadable (re-enter the SSO secret; audit key: see below).
CC6.6 Boundary protectionBrowser egress gate (public http(s) only, DNS answers checked, per-task site pin); agent is outbound-only; OIDC discovery/JWKS/token fetches refuse non-https and (in production) non-public addresses, no redirects, 1 MB cap, 5 s timeoutImplementedDNS rebinding between the SSO address check and the connection is not closed. Run behind an egress firewall for strong guarantees.
CC6.7 Transmission and movement of dataTLS on the hosted control plane (platform); session cookie HttpOnly, SameSite=Lax, Secure in production; zero secrets on the agent-cloud wirePartialAgent mTLS and certificate rotation planned. Transport security of the hosting platform is theirs.
CC6.8 Prevention of unauthorized softwareSkill versions are immutable and promoted only on a 100% replay; signed installersPartialSigned installers are planned (roadmap gap 2).
CC7.1 Detection of vulnerabilities / configuration changesEgress/injection eval set, leak tests in CI; dependency licence reviewPartialNo automated dependency vulnerability scanning in CI, no third-party penetration test (planned).
CC7.2 Monitoring for anomaliesEvery action, sign-in, failed sign-in, SSO failure with reason, MFA reset, settings change and export is an audit row; SIEM exportImplementedNo built-in alerting rules; SIEM is the route (customer-run).
CC7.2, CC7.3 Tamper-evident recordAudit log is append-only (ORM guard + SQLite/Postgres triggers, including TRUNCATE on Postgres) and hash-chained per org; GET /v1/audit/verify recomputes the chain. Signed export: a date range as JSONL + CSV with a manifest signed with the org's Ed25519 key; badgeye audit verify <export> checks signature, every hash, every link, the CSV against the JSONL and the anchors offline (no database, no network) and names the first bad row; altering, deleting, inserting or reordering any row fails. Daily anchor: the chain head is recorded and signed once a day (scheduler job at 00:10 local, and on demand), shown on the Audit page and carried in exportsImplementedThe signing key lives with the service: an attacker who controls both the database and BADGEYE_AUTH_SECRET could re-sign a rewritten history. The defence is to pin the public key and keep exported anchors/heads outside the system (customer copies, SIEM). Key rotation and external timestamping (RFC 3161 or a public transparency log) are planned. A date range is exported as the contiguous run of rows from the first to the last row dated in the range.
CC7.4 Incident responseKill switch per org/employee; operator command to switch off SSO (badgeye sso disable --org); MFA resetPartialWritten incident-response plan and on-call are organizational.
CC8.1 Change managementVersioned database migrations (Alembic, verified against the models in tests), CI suite on SQLite and Postgres, immutable skill versions with replay-gated promotionPartialCode-review and branch-protection policy is organizational.
CC9.2 Vendor and business-partner riskModel providers are the customer's own plans; documented licencesPartialSubprocessor list and vendor reviews are organizational.
CC6.1, CC7.2 Initiator on every audit rowEvery audit row records initiator_kind (person, watcher, schedule, director, handoff, outside_input, system) and initiator_id, set once where the work starts (scheduler job, watcher event, delegated task, person) and carried in a context the model cannot change; both fields are part of the row hash for new rows, older rows keep their original hash input so existing chains and exports still verify; the Audit page and GET /v1/audit/events?nobody_watching=true list work no person startedImplementedConsole-click and API calls by a person take the person from the actor; wiring the signed-in person into every request scope is left to the API layer. Durable outbox redelivery loses the ambient initiator and records system.
CC7.2 Audit-firstAn action.attempted row is committed before any executor runs; if it cannot be written the action is not executedImplementedInstalled by the policy module's wiring on the team service.
CC6.1, CC6.3, PI1.2 Rule policyDeny-before-allow rules over website, button label, command/tool, initiator, department and amount; layers org, department, AI employee, person; the strictest effect wins; locked team rules cannot be edited or loosened by members; a malformed rule or a failed evaluation refuses; evaluated before the limits engine and can only tighten it (never lifts a limit, approval, NEVER_TYPES or human gate); every refusal is audited with the rule id; Preview shows which rule would decideImplementedFacts other than action type, system and amount (website, label, tool, command, department) come from the action payload and are only as good as the code that fills them.
PI1.2 Auto-review (hook)A second-opinion hook for in-limit writes that can only escalate to approval; failure, timeout or an unreadable answer counts as askImplementedNo reviewer model is connected by default.
CC6.1, C1.1 Postgres row-level securityRLS policies on every org-scoped table keyed on current_setting('app.org_id'), a non-owner badgeye_app role without BYPASSRLS, set_config(..., true) (SET LOCAL) per transaction so it is safe behind the Supabase transaction pooler; no setting means no rows; cross-org reads, updates, deletes and inserts fail; run_as_system is the named scope for sweepsPartialOff until the deployment connects as badgeye_app (set its password and URL); the request layer must call bind_org(session, org_id); tables added by later migrations need apply_rls again; SQLite has no RLS.

2. Availability

CriterionControlStatusGap
A1.1 CapacityServerless control plane (Vercel + Supabase); long-poll caps; per-run and per-goal caps on the agentPartialNo capacity monitoring or load testing (planned).
A1.2 Backup and recoverySupabase platform backups; schema in migrationsPartialRestore drills and RPO/RTO targets are organizational; none run yet.
A1.3 Recovery testingPlanned

3. Confidentiality

CriterionControlStatusGap
C1.1 Identify and protect confidential informationBlindfold, redaction at every boundary, leak scans of the database, wire, recordings, audit export and API answersImplementedData classification policy is organizational.
C1.2 Disposal30-day purge of recording frames; sessions revoked on logout/password changePartialNo customer-initiated erase of an org's data (planned). The audit log is append-only by design; export before offboarding.

4. Processing integrity

CriterionControlStatusGap
PI1.1-PI1.5Limits by amount/action/system, shadow mode, lead review before writes, human approval gates, replay of every action, hard fraud rules, reversalImplementedReliability against real sandbox ERPs and portals is roadmap gap 1.

5. Privacy

CriterionControlStatusGap
P1-P8Opt-in recorder with field redaction and allow/deny lists; secrets never stored in recordingsPartialPrivacy notice, DSAR handling, retention schedule and DPA are organizational.

To report a vulnerability or ask for the full controls document, request access and say so.

See what works today →