Skip to main content

Security Engineering

Security Model & Controls

Engineering documentation of how OmniSentient protects your repositories and data. Every claim is sourced from the running codebase. Gaps are disclosed as gaps, not hidden.

Last reviewed: August 31, 2026  ·  Version: v1.2

  • Authentication

    GitHub OAuth → server-side JWT

  • Session storage

    HttpOnly cookie, never JS-visible

  • Data isolation

    org_id-scoped queries, server-side

  • Crypto in transit

    TLS via Vercel Edge, HSTS preloaded

What data does OmniSentient access?
  • Your GitHub repository code — read-only, scoped only to repositories you explicitly install the app on
  • Your GitHub profile — username, email, org membership (used for authentication and billing)
  • Your repository metadata — branch names, commit SHAs, pull request titles (for scan context)
  • Raw source is stored only if you switch on Code Intelligence for a repository. It is indexed as verbatim chunks in code_embeddings, deleted on a 12-month schedule, and removed immediately when the repository is deleted. Automated remediation alone reads manifests, not source.

Security Architecture

Four trust zones with explicit data-flow direction. The application server is the sole trust authority — the browser and external services hold no elevated credentials.

Credential isolation: The SUPABASE_KEY is explicitly excluded from the Jinja2 template context processor. A prior version injected it site-wide, which would have exposed a full-access database credential to any rendered page. The exclusion is enforced in create_app.py.

Authentication & Session Model

GitHub OAuth 2.0 with server-side-only code exchange. The client never receives a GitHub access token.

OAuth Flow

1
CSRF state token generated
Stateless HMAC-signed token: nonce.timestamp.HMAC-SHA256. Short-lived HttpOnly nonce cookie binds state to this browser. Works across serverless instances — no shared session storage needed.
✓ Prevents CSRF on OAuth callback · ✓ Stateless — safe for serverless
2
Redirect to GitHub OAuth
Scopes: user:email read:user read:org — minimum required. allow_signup=false — only existing GitHub accounts can proceed. Redirect URI from BASE_URL env, not request headers.
✓ Principle of least privilege (read-only scopes) · ✓ No attacker-controlled redirect URI
3
Callback: state + code verification
HMAC verified AND TTL checked before the authorization code is touched. next redirect param validated as same-origin path — full-URL accepted only if hostname matches BASE_URL.
✓ Blocks CSRF · ✓ Blocks open-redirect attacks
4
Server-side code exchange only
Authorization code → GitHub access token exchange via server POST (https://github.com/login/oauth/access_token). Access token used server-side to fetch profile, then discarded. Never sent to browser.
✓ Client never sees GitHub access token — cannot be exfiltrated
5
JWT issued as HttpOnly cookie
Custom HS256 JWT: sub, github_user, org_id, iat, exp, nbf, iss, aud, jti. Cookie: HttpOnly, Secure, SameSite=Lax, name=omni_session.
✓ Token invisible to JavaScript — XSS cannot steal it

JWT Verification Chain

# jwt_service.verify_jwt() — checks in this exact order: 1. Algorithm lock — header decoded BEFORE sig check rejects alg:none, RS256→HS256 confusion attacks 2. HMAC-SHA256 sig — hmac.compare_digest (constant-time, timing-safe) 3. exp + nbf claims — 60s clock-skew leeway 4. iss + aud claims — "omnisentient-backend" / "omnisentient-client" 5. jti revocation — db.is_token_revoked(jti, org_id) 6. Idle timeout — last_seen > 30 min → SESSION_IDLE_EXPIRED

Session Properties

PropertyValueEnforced in
Absolute lifetime12 hours (JWT_EXPIRY_SECONDS env)jwt_service.py
Idle timeout30 minutes (last_seen claim)jwt_service.py
HttpOnlyYes — JavaScript cannot access the cookie valueset_session_cookie_flask()
Secure flagSet on HTTPS; relaxed on local HTTP dev only_cookie_secure()
SameSiteLax — blocks cross-site form POST CSRFset_session_cookie_flask()
Token revocationDB-backed per jti + org_id — immediate invalidationjwt_service.verify_jwt()
AlgorithmHS256 — custom implementation, no PyJWT libraryjwt_service.py
Known limitation: HS256 is symmetric — the signing secret also verifies tokens. An RS256 upgrade path (asymmetric, public-key verification) is documented in jwt_service.py but not yet implemented. The __Host- cookie prefix (prevents domain-level injection on shared hosts) is noted in source as recommended but not active.

Identity Resolution

Wave-4 audit (June 2026) found five parallel identity extractors with divergent logic — any one could be exploited while another was hardened. All were deleted and replaced with a single entry point: services.auth.identity.get_current_user(). All code is required to call this function. No alternative resolution paths exist.

Role assignment: Roles are assigned by org admins through the team management dashboard. The backend validates role assignment against the requester's own role — a developer cannot assign admin. Role data is stored in the database; client-supplied role values in requests are never trusted.

Authorization & Access Control

The backend never trusts client-supplied org, project, or role values. All access is validated against the database before data is returned.

Role Hierarchy

# auth_guard.py — ROLE_HIERARCHY ROLE_HIERARCHY = ['auditor', 'viewer', 'developer', 'security_lead', 'admin', 'owner'] # Higher index = more privilege. Unknown roles resolve to -1 (denied).
RoleCapabilitiesWho assigns
auditorRead-only forensic audit ledger and compliance exportadmin / owner
viewerRead-only dashboard — no write or scan triggeradmin / owner
developerTrigger scans, view findings, create pull requestsadmin / owner
security_leadSecurity configuration, incident management, scan policyadmin / owner
adminBilling, team management, integration config, log deletionowner only
ownerFull access — org-level controls, member removalGitHub App installer

Enforcement Principles

validate_project_access() queries the database to confirm the requesting user holds at least the required role for the specific project UUID. Client-supplied org/project are treated as unverified claims until DB-confirmed. Input parameters validated against ^[A-Za-z0-9_-]{1,64}$ before any database operation.

Cross-org access returns 403, not 404 — org existence is not disclosed. All 403 responses return the same generic message — no timing-based enumeration possible via response time.

The dev-bypass route (/auth/dev-login) is gated on settings.is_local_dev: requires ENV=development AND absence of VERCEL_ENV. Vercel preview deployments are publicly reachable but classified as non-local — they cannot trigger the bypass.

Data Protection

What Data We Store

Data typeStored?RetentionEncrypted at rest?
GitHub user ID + usernameYes — required for authUntil account deletionPlatform (Supabase AES-256)
Org membership + roleYes — required for RBACUntil revokedPlatform (Supabase AES-256)
Scan findings + resultsYes — core product featureConfigurable per orgPlatform (Supabase AES-256)
Audit event logYes — append-onlyRetained per org policyPlatform (Supabase AES-256)
Raw repository source codeNo — not storedNot applicableNot applicable
GitHub access tokensNo — discarded after OAuthNot applicableNot applicable
JWT session tokensNo — stateless; jti onlyjti revocation tablePlatform (Supabase AES-256)

Encryption in Transit

All traffic is TLS-only via the Vercel Edge network. The application sets Strict-Transport-Security: max-age=31536000; includeSubDomains; preload on all non-local-dev responses, including preview deployments. A prior version applied HSTS in production only — preview deployments were left without it.

Encryption at Rest

Application-layer encryption is not implemented. Database-level encryption is provided by Supabase's platform (PostgreSQL on AWS with AES-256 at rest). All data is stored in a single Supabase project.

Limitation: A compromised database credential exposes plaintext data — field-level application-layer encryption is not implemented. This is a known gap on the security roadmap.

Secret Management

SecretPurposeIf absent in production
JWT_SECRETHMAC signing key for session tokensCRITICAL log · startup proceeds with insecure fallback (local dev guard prevents this in prod)
SECRET_KEYFlask session signingStartup validation fails — app does not start
SUPABASE_KEYService-role DB access — server-side onlyAll database operations fail
GITHUB_WEBHOOK_SECRETHMAC verification for GitHub webhooksAll webhooks return 401
INTERNAL_JOB_TOKENWorker-to-worker authFail-closed: all internal requests rejected

Credential Scrubbing in Logs

utils/security.py:mask_sensitive_data() applies regex patterns to all log output before writing, redacting GitHub tokens (ghp_*, github_pat_*), bearer tokens, AWS access keys, Slack tokens, Stripe keys, PEM private key headers, and generic api_key/secret patterns. Prompts sent to Gemini AI are scrubbed via SecurityUtilities.scrub_log() before leaving the application boundary.

Repository Access Scope

GitHub App installation tokens are scoped to the specific repositories selected during installation — not the user's entire GitHub account. Revoking the GitHub App installation immediately removes access at the GitHub API level, with no action required from OmniSentient.

Application Security Controls

Security Headers

HeaderValueStatus
Strict-Transport-Securitymax-age=31536000; includeSubDomains; preloadImplemented
Content-Security-PolicyPer-request nonce (secrets.token_hex(16)). Explicit CDN allowlist. Contains unsafe-inline + unsafe-eval — see limitation below.Partial
X-Content-Type-OptionsnosniffImplemented
X-Frame-OptionsSAMEORIGINImplemented
Referrer-Policystrict-origin-when-cross-originImplemented
Cache-Controlno-store on /api/* and /dashboard/*Implemented
Permissions-PolicyNot configuredNot implemented
CSP limitation: unsafe-inline + unsafe-eval in script-src means browsers use these permissions for scripts without nonces instead of blocking them. The per-request nonce is applied to known inline scripts, but the fallback weakens the protection. Strict CSP (nonce-only) requires a full inline-script audit and is on the roadmap.

Webhook Signature Verification

GitHub webhook payloads: HMAC-SHA256 against GITHUB_WEBHOOK_SECRET before any payload parsing. Slack interactions: verify_slack_signature() before any field is read. Both are fail-closed — invalid signature = 401, no further processing. Webhook idempotency: DB unique constraint on delivery_id; duplicates return 202 without reprocessing, and quota is charged once only.

Internal Service Authentication

Worker-to-worker calls supply X-Internal-Job-Token header, validated via hmac.compare_digest (constant-time, timing-safe). INTERNAL_JOB_TOKEN supports comma-separated values for zero-downtime rotation. Fail-closed: an absent header or unset environment variable is always a rejection.

Application Posture Control

SECURITY_MODE=lockdown blocks all HTTP write operations (POST/PUT/PATCH/DELETE) at the application entry point — no redeploy needed. SystemPostureController reads from the governance store at runtime. /static/reports returns 404 unconditionally — compliance reports are never served from a static path.

Prompt Injection Detection

utils/security.py:is_prompt_injection() applies regex patterns against common jailbreak phrases before LLM prompt construction. This is a detection layer, not a complete defense — adversarial bypass remains possible. No semantic sandboxing or output filtering for LLM-generated content is implemented.

Known Missing Controls

ControlStatusReal-world impact
Rate limitingNot implementedNo application-layer throttle. An authenticated user could exhaust expensive AI scan or dependency check quotas without back-pressure. Vercel Edge may apply platform-level network limits.
CORS headersNot implementedNo explicit CORS policy. Browser same-origin policy applies for browser clients. Server-to-server API callers are not affected by CORS.
Input validation libraryNot implementedNo schema-level enforcement (Pydantic, Marshmallow). Validation is manual per-endpoint — inconsistency risk as the API grows.
CSRF token on API writesPartialSameSite=Lax provides partial protection. CSRF token exists in the Flask session context but is not validated against API write requests.

Infrastructure & Deployment

PropertyImplementation
Hosting providerVercel — serverless Python functions + Edge Network
TLSManaged by Vercel — automatic cert provisioning, renewal, and rotation
Environment isolationProduction, preview, and development environments are fully isolated by Vercel. Security headers apply identically to preview and production.
Secret managementEnvironment variables via Vercel project settings, scoped per environment. Never in source control.
Startup secret validationsettings.validate() runs at cold-start. Missing required secrets log CRITICAL. App proceeds but affected features will fail safely.
Mock endpointsGated on is_local_dev AND DEBUG=true. is_local_dev requires ENV=development AND no VERCEL_ENV set. A stray DEBUG=true on Vercel cannot expose them.
Proxy header handlingProxyFix(x_for=1, x_proto=1, x_host=1) — trusts first X-Forwarded-For entry only. IPs validated via ipaddress.ip_address() before use.
Third-party dependenciesPython packages installed from PyPI at build time. No private registries. Dependency pinning via requirements.txt.

Observability & Audit

All logs are structured JSON with per-request context propagated via Python contextvars (thread-safe and async-safe): request_id, tenant_id, user_id, role, path, method. Security events use log_security_event() and are tagged security_event: true for filtering. X-Request-ID is echoed in every response to enable trace correlation with client-side error reports.

An append-only audit ledger (audit_logs table) records organizational events with timestamp, actor identity, event type, and correlation ID. Every row carries the SHA-256 hash of the row before it, written by a Postgres trigger, so altering or removing a row breaks the chain from that point on and the break is detectable by anyone holding an earlier export. Users with the auditor role can query this via /api/auditor/* with time-range and correlation-ID filtering.

Where this stops: Our own backend connects as the Postgres service role, which bypasses row-level security. We do not claim we cannot write to the ledger. We claim you can tell if we did. FORCE ROW LEVEL SECURITY is set on incidents, incident_events, incident_notifications, which constrains other callers rather than the platform itself. The guarantee is detection, not prevention — verify it yourself with the verification guide and the published signing key.
Incident response: If you are an existing customer experiencing a security incident, contact your org's admin role holder first. They can trigger lockdown mode (SECURITY_MODE=lockdown) and access the audit ledger immediately without waiting for OmniSentient support. For platform-level incidents, use the responsible disclosure process below.

Threat Model

Realistic threats for a GitHub App processing code at scale. Mitigations reflect actual code. Gaps are documented, not hidden. Click any card to expand the technical residual risk.

S Spoofing T Tampering R Repudiation I Info Disclosure D DoS E Privilege Escalation

Security Lifecycle

Security Review History

Jun 2026
Wave-4 Identity Audit
Found five parallel identity extractors. Consolidated to single entry point. Deleted all alternatives. Dev-bypass gate hardened to block Vercel preview activations. SUPABASE_KEY removed from template context processor.
Ongoing
Wave-based Security Reviews
Security changes are batched and documented as "wave" audits in source code comments, enabling historical traceability without a separate issue tracker.
Ongoing
Structured Security Event Logging
Security events are tagged security_event: true in structured JSON logs and queryable via the audit ledger API.

Dependency Scanning

OmniSentient includes a DependencyGuardian class that scans monitored repositories' dependencies against the OSV.dev vulnerability database. This is a feature we provide to customers — it does not scan OmniSentient's own Python dependencies.

Gap: Automated vulnerability scanning of OmniSentient's own Python dependencies is not implemented. No formal SBOM is generated for this application. Both are on the security roadmap.

Patch Response

OmniSentient does not publish a formal SLA for security patch response. As a general operating principle:

  • Critical severity (active exploit, data exposure): target same-day investigation and 48-hour patch
  • High severity: target 7-day patch
  • Medium/low severity: addressed in next scheduled release
Disclaimer: These are operational targets, not contractual SLAs. We will communicate timelines transparently when we receive a valid report.

Security Roadmap

Documented gaps with engineering priorities. These are not commitments — they reflect current engineering judgement. This list is updated with each wave audit.

ItemGap addressedPriorityTarget
API rate limitingPer-user/IP sliding window on expensive endpoints (AI scans, dependency checks). Planned: Redis-backed.HighWave 5
Strict CSP (nonce-only)Remove unsafe-inline / unsafe-eval. Requires full inline-script audit to add nonces.HighWave 5
RS256 JWT algorithmAsymmetric keys — public key verification without distributing the signing secret. Upgrade path in jwt_service.py.MediumWave 6
CSRF token validation on API writesX-CSRF-Token header checked on state-changing API requests to complement SameSite=Lax.MediumWave 6
__Host- cookie prefixPrevents domain-level cookie injection. Pre-conditions already met (HTTPS + no Domain attr). Path noted in source.MediumWave 6
Self-dependency SBOMScan OmniSentient's own Python dependencies against CVE databases. Publish SBOM.MediumWave 6
Permissions-Policy headerRestrict unused browser APIs (camera, microphone, geolocation, payment).LowWave 7
Input validation libraryReplace manual per-endpoint validation with Pydantic schema enforcement for consistent error handling.LowWave 7
Subresource Integrity (SRI)integrity attributes on CDN-loaded scripts/styles to prevent CDN-level supply chain attacks.LowWave 7

Report a Vulnerability

If you discover a security issue, please report it responsibly before public disclosure. We take reports seriously and will keep you informed.

Expected Response Timeline

Acknowledgement

< 48 hours

Status updates

every 7 business days

Public disclosure

within 90 days

Patch (critical)

< 48 hours

Patch (high)

< 7 days

These are operational targets, not contractual SLAs. We will communicate if circumstances require longer timelines.

What reviewers ask first

  • Where does our code go?

    Dependency manifests only, by default.

    Automated remediation reads manifests, not source. Source is read and stored only if you switch on Code Intelligence, per repository.

    Privacy Policy →
  • Who else touches it?

    Four subprocessors, all named.

    Supabase, Vercel, Google Cloud and GitHub. What each one receives, and where it sits, is listed rather than summarised.

    Subprocessor Disclosure →
  • What happens if you are breached?

    72 Hours to notification.

    4 Hours to acknowledgement, 14 business days to a published root-cause report.

    Security Policy →
  • Can I prove any of this myself?

    Yes, offline, without asking us.

    Every forensic export is Ed25519-signed. The public key is published and the verification runs in seven lines.

    Verification Guide →

Documents

Scope

✓ In scope

  • Authentication and authorization bypass
  • Cross-tenant data exposure
  • Remote code execution
  • SQL injection or database access
  • Session token theft or forgery
  • Security header bypass
  • Server-side request forgery (SSRF)
  • Secrets or credentials exposed in responses

✗ Out of scope

  • Denial of service via high request volume
  • Issues requiring physical device access
  • Social engineering of OmniSentient staff
  • Self-XSS (requires a user to attack themselves)
  • Clickjacking on low-sensitivity pages
  • Missing security headers on marketing pages
  • Best-practice recommendations without demonstrated impact

How to Report

Do not open a public GitHub issue — this exposes the issue to all users before a fix is available.

  1. Describe the vulnerability: what it is, the affected component, how to reproduce it step by step, and the potential impact.
  2. Include evidence: URL, endpoint, or code path; HTTP request/response if applicable; proof-of-concept code if you have one.
  3. Do not exploit live user data beyond what is necessary to demonstrate the vulnerability.
  4. Submit via GitHub's Security → Report a vulnerability tab on the OmniSentient repository, or via the email in SECURITY.md in the repository root.

We will acknowledge within 72 hours and work with you on a coordinated disclosure timeline. We ask for a reasonable embargo before public disclosure.

OmniSentient does not currently operate a paid bug bounty program. We will credit researchers who report valid vulnerabilities with their consent.