Security Model
The platform serves two very different clients through a single API surface: browser users who expect seamless redirects and session cookies, and automated agents that authenticate with bearer tokens. This dual requirement drives most of the security architecture decisions described here.
OIDC Hybrid Mode
Quarkus supports three OIDC application types: web-app (browser sessions only), service (bearer tokens only), and hybrid (both). The platform uses hybrid mode so that the same endpoints accept either a browser session or a bearer token depending on what the caller provides.
quarkus.oidc.application-type=hybrid
This is the right choice for a platform that hosts both a web UI and an agent API -- but it comes with a major gotcha around path-based authentication.
Why NOT Path-Based Auth
The "obvious" approach to securing a web app is to lock down everything and carve out exceptions:
# DO NOT DO THIS -- it breaks sign-up and contact-us pages
quarkus.http.auth.permission.authenticated.paths=/*
quarkus.http.auth.permission.public.paths=/sign-up,/contact-us
quarkus.http.auth.permission.public.policy=permit
With application-type=web-app, this works fine. But in hybrid mode, the OIDC layer intercepts all requests matching authenticated.paths and redirects them to Keycloak before the permission system has a chance to check public.paths exemptions. The result: users cannot reach the sign-up page, demo-request forms, or any other public route. Revenue stops.
Do not change authenticated.paths to /*. Hybrid mode's redirect logic does not respect public.paths exemptions, and doing so will break all public pages including sign-up and contact forms.
How Auth Actually Works
Instead of path-based lockdown, the platform uses a minimal path configuration combined with annotation-based resource protection:
- Path permissions are intentionally narrow:
authenticated.paths=/login - Resource annotations do the real work:
@RolesAllowed("platform")on protected resources - Public resources like
SignUpGraphQLResourcehave no annotation, so they are accessible without authentication - OIDC hybrid mode only triggers an auth challenge when an annotated resource is accessed
This approach means that adding a new public endpoint requires no configuration changes -- just omit the @RolesAllowed annotation.
Token Validation for Agents
Agents running in the untrusted cluster authenticate to the platform API using a pre-shared token scheme:
Token = SHA-1(projectId + tokenSecret)
The agent receives its project ID as the XPRESSAI_PROJECT_ID environment variable and the token as XPRESSAI_API_TOKEN. On each request, the platform recomputes the hash from the claimed project ID and the stored secret, then compares.
SHA-1 is acceptable here because this is a pre-shared token scheme, not password hashing. The token secret provides the entropy -- SHA-1 is used only as a deterministic one-way function to derive a fixed-length token from the (projectId + secret) pair. The security relies on the secrecy of the token secret, not on the collision resistance of the hash function.
The token is derived from the project ID, not the Kubernetes namespace. These are different values -- XPRESSAI_NAMESPACE is the K8s namespace (e.g., xpressai), while XPRESSAI_PROJECT_ID is the database identifier (e.g., personal-egonzalez). Using the namespace for token validation will always fail.
Agents send three headers on every request:
| Header | Value | Purpose |
|---|---|---|
X-Task-Authorization | SHA-1 hash | Authentication |
X-Task-Namespace | K8s namespace | Routing (not auth) |
X-Agent-Name | Service name | Agent identity lookup |
File Path Sandboxing
Agents have filesystem access to an NFS mount at /data/home/. To prevent path traversal attacks, all file operations go through a path validation function that resolves the requested path and verifies it stays within the allowed directory. The pattern looks like this:
def _safe_path(requested_path):
resolved = os.path.realpath(os.path.join("/data/home/", requested_path))
if not resolved.startswith("/data/home/"):
raise PermissionError("Path outside allowed directory")
return resolved
This prevents agents from using ../ sequences or symlinks to escape their data directory.
Multi-Tenancy Isolation
The platform enforces tenant boundaries at multiple layers:
- Kubernetes namespaces: Each user and team project gets its own namespace in the untrusted cluster, providing network and resource isolation
- projectId-scoped data: All database queries filter by project ID, preventing cross-tenant data access
- NFS subpaths: Each namespace mounts a different subpath of the shared NFS volume, so agents in one project cannot access another project's files
Integration Credential Encryption
Integration credentials (API keys, OAuth tokens, database passwords) are encrypted at rest using FieldEncryptionService before being stored in the database. The encryption key is managed as a platform secret, separate from the database credentials.
When an agent needs access to an integration, the platform decrypts the credentials and injects them as environment variables in the Knative service manifest. The credentials never touch the agent's filesystem -- they exist only in the pod's environment.
Workspace-level integrations mean you configure credentials once and all projects in that workspace inherit access. This reduces credential sprawl but means a compromised workspace credential affects all projects. Per-project credential overrides are planned for a future release.
Summary
The security model can be summarized as three concentric rings:
- Authentication: OIDC hybrid mode with annotation-based protection (not path-based)
- Authorization: Project-scoped token validation for agents, role-based access for users
- Isolation: Kubernetes namespaces, NFS subpath mounts, and projectId-scoped database queries
For the full details on agent API headers and token validation, see Agent API Headers.