Orca's security model, credential handling, trust boundaries, and operational guidance for the full ecosystem.
If you find a security issue, do not open a public GitHub issue. Use GitHub's private vulnerability reporting instead: navigate to the Security tab of this repository and select Report a vulnerability. Include a description of the issue, reproduction steps, and the affected component or service. We will acknowledge receipt within 48 hours and provide a fix timeline within 7 days.
Orca is a multi-service ecosystem. Each component runs as an independent process, communicates over HTTP, and is containerized separately.
| Service | Default port | Role |
|---|---|---|
| Orca UI | 5173 | React SPA — user-facing frontend |
| Orca Web (BFF) | 8003 | Backend-for-frontend — the only authenticated, public-facing API gateway |
| OrcaMind | 8000 | Meta-learning engine — internal service |
| OrcaLab | 8001 | Experiment management — internal service |
| OrcaNet | 8002 | Cross-domain transfer + LLM reasoning — internal service |
Infrastructure services used by the ecosystem:
| Service | Purpose |
|---|---|
| PostgreSQL 15 | Shared metadata registry; user accounts and sessions |
| Redis 7 | Cache and task queues |
| MinIO | S3-compatible artifact storage (model checkpoints, datasets) |
| MLflow | Experiment tracking (metadata in PostgreSQL, artifacts in MinIO) |
| Prefect | Workflow orchestration for OrcaLab experiment flows |
The Orca Web BFF is the only service that should receive traffic from outside the private deployment network. All user authentication happens here. OrcaMind, OrcaLab, OrcaNet, PostgreSQL, Redis, MinIO, MLflow, and Prefect must sit behind a private network boundary. Exposing any of them directly to the public internet is a misconfiguration.
In the Docker Compose stack, all services share a single bridge network (orca-dev-network). In Kubernetes, network policies must enforce equivalent isolation.
Warning: The default bridge network does not restrict inter-service traffic. Services can reach each other without authentication (see Internal service authentication gap below). Do not deploy this stack to production without adding network-layer controls.
Orca Web authenticates users with JSON Web Tokens using the HS256 algorithm. The JWT secret is loaded from the JWT_SECRET_KEY environment variable at startup.
Access tokens are short-lived (15 minutes by default) and are returned in the response body after a successful login or token refresh. Clients include them as Authorization: Bearer <token> on every protected request. The get_current_user dependency in orca_web/api/deps.py validates the token signature, confirms the type claim is "access", and verifies the user still exists and is active.
Refresh tokens are long-lived (7 days by default) and are delivered as an httponly cookie scoped to the /api/v1/auth path. A per-token JTI (JWT ID) is stored in the user_sessions PostgreSQL table. On each call to POST /auth/refresh, the old JTI is revoked and a new refresh token is issued — stolen refresh tokens cannot be silently reused. SessionRepository.revoke_all_for_user() invalidates every active session for a user, and should be called on password change or suspected compromise.
The secure flag on the refresh cookie is False in the default configuration and must be set to True in any deployment that terminates TLS. See the hardening checklist at the end of this document.
Orca Web supports OAuth 2.0 login via Google and GitHub, implemented with authlib. OAuth providers are registered only when the corresponding client ID environment variable is non-empty, so unconfigured providers are unavailable at runtime. After the provider callback, the BFF upserts the user record (matching by OAuth sub claim, then by email) and issues the same JWT pair as for local login. OAuth users have no password_hash.
Passwords are hashed with bcrypt via passlib. Plaintext passwords are never stored, logged, or compared directly. The verify_password function performs a constant-time comparison.
Users have a role column with two valid values: "user" (default) and "admin". Profile access is enforced by GET /users/{user_id}, which rejects requests where the caller is neither the target user nor an admin. Role-gating for other endpoints follows the same pattern via the get_current_user dependency.
The live experiment metric stream at /orcalab/ws/experiments/{id}/live accepts the JWT access token either as a ?token=<jwt> query parameter or in the Sec-WebSocket-Protocol header (for environments where query parameters are not available). Invalid or missing tokens receive close code 4001.
OrcaMind, OrcaLab, and OrcaNet expose unauthenticated HTTP APIs. They trust all inbound requests. This is a known gap; API key authentication for inter-service calls is planned. Until it is implemented, network isolation is the only defense layer for these services. No request that originates outside the private deployment network should reach them.
All secrets are sourced from environment variables. No secret appears in any configuration file that is (or could be) committed to source control. Dev-only defaults in config.py and docker-compose.dev.yml are clearly marked as placeholders and must be replaced before any production deployment.
| Secret | Environment variable | Used by |
|---|---|---|
| JWT signing key | JWT_SECRET_KEY |
Orca Web |
| Database connection | DATABASE_URL |
All services |
| Redis connection | REDIS_URL |
Orca Web, OrcaMind |
| MinIO access key | MINIO_ACCESS_KEY |
OrcaMind |
| MinIO secret key | MINIO_SECRET_KEY |
OrcaMind |
| MLflow tracking URI | MLFLOW_TRACKING_URI |
OrcaMind, OrcaLab |
| OpenAI API key | OPENAI_API_KEY |
OrcaNet |
| Anthropic API key | ANTHROPIC_API_KEY |
OrcaNet |
| Google OAuth client ID | GOOGLE_CLIENT_ID |
Orca Web |
| Google OAuth client secret | GOOGLE_CLIENT_SECRET |
Orca Web |
| GitHub OAuth client ID | GITHUB_CLIENT_ID |
Orca Web |
| GitHub OAuth client secret | GITHUB_CLIENT_SECRET |
Orca Web |
What is never logged: JWT tokens, refresh tokens, passwords, OAuth secrets, database connection strings, MinIO credentials, LLM API keys.
What is logged: HTTP method, path, status code, and elapsed time (request logging middleware); upstream service health check results; WebSocket relay start and end events with experiment ID and user ID (no token values); aggregator errors with the failing URL and exception type.
Privacy note: IP addresses and device information may constitute personal data under GDPR, CCPA, and similar regulations. Enable these fields only with appropriate legal basis and retention policies.
PostgreSQL (shared registry): task definitions and metadata, task embeddings, model configurations, experiment records, performance metrics, transfer mappings, and search space definitions. None of these tables store raw dataset content; dataset_uri holds a pointer to the artifact in MinIO.
PostgreSQL (Orca Web): user accounts (email, bcrypt-hashed password, OAuth provider/sub), active refresh token sessions (JTI, expiry, revoked flag, optional IP address and device info), activity log entries, and user bookmarks.
MinIO: ML model artifacts (checkpoints, weights), dataset files, and MLflow run artifacts. Access requires valid MinIO credentials. Bucket permissions are the deployer's responsibility.
Redis: Prefect task queues, transient cache. No long-lived sensitive data is designed to persist here beyond queue lifetime.
OrcaNet sends task descriptions, domain labels, task types, and similarity context to a configured LLM provider for transfer reasoning and retrieval reranking. The active provider is a user-selectable runtime option; OpenAI and Anthropic are currently supported, with additional providers planned.
No user personal information (email addresses, usernames, passwords, OAuth tokens) is included in LLM prompts. The data sent consists of ML task metadata derived from the shared registry.
Raw dataset content is not stored in PostgreSQL. Model checkpoints and dataset files in MinIO are keyed by URI, not replicated into the registry. No PR content, diff, or user-authored code from external repositories is processed by Orca.
CORS is configured in orca_web/api/middleware.py from the CORS_ORIGINS environment variable (comma-separated list of allowed origins). When origins are specified, allow_credentials=True is set and the list is used exactly as provided. When CORS_ORIGINS is empty or unset, the middleware falls back to allow_origins=["*"] with allow_credentials=False. The wildcard fallback is suitable only for local development; production deployments must set CORS_ORIGINS to the exact frontend origin.
The BFF exposes GET /health without authentication. It checks connectivity to PostgreSQL, Redis, OrcaMind, OrcaLab, and OrcaNet and returns a degraded status if any backing service is unreachable. This endpoint leaks service topology information and should not be exposed to the public internet in production.
All Orca service Dockerfiles use a two-stage build pattern:
- A
builderstage installs Python dependencies usinguvinto an isolated virtual environment at/opt/venv. - A
runtimestage starts frompython:3.11-slim, copies only/opt/venvand application source from the builder, and creates a dedicated non-root user (orca, uid 1001, gid 1001) to run the process.
No secrets are baked into any image. All credentials are injected at runtime via environment variables. Files copied into the runtime image are owned by the orca user. The USER orca directive ensures the process never runs as root.
Each container declares a health check that probes its own /health endpoint. Docker Compose and Kubernetes use these probes to manage service startup order and readiness.
These checks run on every pull request and must pass before merging:
- Ruff: linter and formatter (enforces style, import ordering, and common error patterns)
- Mypy: type checking in strict mode for
orca-shared; standard mode for other packages - pytest: minimum 80% coverage threshold per package (
fail_under = 80)
Pre-commit hooks run Ruff and Mypy on every commit. Unit tests run as a pre-push hook across packages/orcamind/tests/unit and packages/orca-shared/tests.
Runtime dependencies are declared in each package's pyproject.toml with minimum version constraints. Development dependencies (testing, linting, type-checking) are declared under [project.optional-dependencies] dev and are not installed in production images.
| Dependency | Purpose | Used by |
|---|---|---|
fastapi / uvicorn |
HTTP server framework | All services |
pydantic / pydantic-settings |
Schema validation and settings | All services |
sqlalchemy / asyncpg |
Async database access | All services |
alembic |
Database schema migrations | Orca Web, OrcaMind, OrcaLab |
httpx |
Async HTTP client (inter-service calls) | Orca Web, OrcaNet |
python-jose[cryptography] |
JWT encoding and decoding | Orca Web |
passlib[bcrypt] |
Password hashing | Orca Web |
authlib |
OAuth 2.0 client | Orca Web |
redis |
Redis client | Orca Web, OrcaMind |
websockets |
WebSocket relay (upstream OrcaLab) | Orca Web |
torch / pytorch-lightning |
Deep learning framework | OrcaMind, OrcaNet |
faiss-cpu |
Vector similarity search | OrcaMind, OrcaNet |
sentence-transformers |
Text embeddings | OrcaNet |
langchain / langchain-openai / langchain-anthropic |
LLM agent framework | OrcaNet |
prefect |
Workflow orchestration | OrcaLab |
optuna |
Hyperparameter search | OrcaLab |
mlflow |
Experiment tracking | OrcaMind, OrcaLab |
minio |
MinIO / S3 client | OrcaMind |
hydra-core / omegaconf |
Hierarchical configuration | OrcaMind, OrcaLab, OrcaNet |
- Environment variables set by the deployer
- Docker Compose or Kubernetes configuration files
- Database contents (assuming the private network boundary is intact)
- Configuration files in the repository (Hydra configs,
pyproject.toml)
HTTP request bodies and parameters (BFF): All user-supplied input arrives at the BFF and is validated by Pydantic schemas before any processing occurs. Invalid input is rejected with a structured error response. SQLAlchemy's parameterized queries prevent SQL injection; raw string interpolation into queries does not occur.
OAuth callback payloads: Provider responses are validated by authlib using the provider's published OIDC metadata or explicit token URL. The callback only exchanges a short-lived authorization code, never a reused credential.
LLM API responses: OrcaNet parses LLM provider responses through Pydantic validators. Responses are treated as structured data, never executed. Unexpected fields or type mismatches raise validation errors.
Upstream service responses (internal): Because internal services are unauthenticated (see above), a compromised internal service could return malicious responses to the BFF. The BFF parses upstream JSON and forwards it to the browser; it does not execute it.
LLM provider data retention. Task metadata sent to OpenAI or Anthropic is subject to those providers' terms of service and data processing agreements. Orca does not control how providers store, process, or retain that data. Review the relevant provider's policies independently before sending sensitive task information.
Secrets in task datasets. If a dataset registered with Orca contains secrets or personally identifiable information, those values may be included in task metadata forwarded to the configured LLM provider. Orca does not scan or redact dataset content before sending it to external APIs.
MinIO bucket exposure. Orca does not configure MinIO bucket policies. A misconfigured MinIO instance with public bucket access would expose all stored artifacts. Bucket policy management is the deployer's responsibility.
Internal service compromise. If an attacker reaches OrcaMind, OrcaLab, or OrcaNet through a network boundary failure, those services have no additional authentication layer. Containment depends entirely on network isolation being intact.
Denial of service via large inputs. Orca enforces no global limit on task dataset size, experiment batch size, or LLM prompt length in the current implementation. Resource consumption bounds are the deployer's responsibility at the infrastructure level.
For any deployment where the BFF is reachable from outside a trusted private network:
- Set
JWT_SECRET_KEYto a cryptographically random value of at least 32 bytes; never use the defaultdev-secret-change-in-prod - Terminate TLS at a reverse proxy (nginx, Caddy, ALB, or equivalent) in front of the BFF; set
secure=Trueon the refresh token cookie (orca_web/api/routers/auth.py,_set_refresh_cookie) and route WebSocket connections to/orcalab/ws/experiments/{id}/liveover secure WebSockets (wss://) - Set
CORS_ORIGINSto the exact production frontend URL; do not rely on the wildcard fallback - Place OrcaMind, OrcaLab, OrcaNet, PostgreSQL, Redis, MinIO, MLflow, and Prefect behind a private network boundary with no inbound access from the public internet
- Verify the status of internal service authentication; until inter-service API key auth is implemented, track and ensure network isolation is enforced (see Internal service authentication)
- Replace all dev-mode credential placeholders:
POSTGRES_PASSWORD,MINIO_ROOT_USER,MINIO_ROOT_PASSWORD, and any service-levelMINIO_ACCESS_KEY/MINIO_SECRET_KEYvalues - Store LLM API keys (
OPENAI_API_KEY,ANTHROPIC_API_KEY) and OAuth secrets in a secrets manager or your deployment platform's secrets store — not in Compose files or.envfiles committed to source control - Enable PostgreSQL TLS and use connection strings with
sslmode=require - Set a Redis
AUTHpassword if Redis is reachable from any network segment outside the deployment host - Configure MinIO bucket policies to deny public access
- Pin Docker base images to specific digests for reproducible, auditable builds
- Rotate
JWT_SECRET_KEYperiodically — all active user sessions are invalidated on rotation - Monitor application logs for sustained 401/403 response rates, upstream health check failures, and WebSocket relay errors
- Review the data handling policy of your configured LLM provider before registering tasks that contain sensitive or proprietary information