oCoreoCore Docs

Security

Authentication, 2FA, passkeys, session management, encryption, rate limiting, and network security.

oCore implements defense-in-depth security across authentication, authorization, encryption, and network layers. This page consolidates all security features and best practices. Security documentation is also distributed across relevant feature pages throughout this guide.

Authentication

Password Authentication

oCore enforces strong password requirements:

RequirementDetail
Minimum length8 characters
ComplexityAt least 1 letter and 1 number
Hashing algorithmArgon2id (OWASP recommended parameters)
Parameters19 MiB memory, 2 iterations, 1 parallelism, 16-byte salt
Storage format$argon2id$v=19$m=19456,t=2,p=1$<salt>$<hash>

Why Argon2id

Argon2id is the winner of the Password Hashing Competition and is recommended by OWASP for its resistance to GPU and ASIC attacks. The memory-hard property makes brute force attacks prohibitively expensive.

JWT Token Authentication

oCore uses JSON Web Tokens (JWT) for session authentication:

PropertyValue
Token typeAccess token + Refresh token
Access token lifetimeShort-lived (configurable, default 1 hour)
Refresh token lifetimeLonger-lived (configurable, default 30 days)
Signing algorithmHMAC-SHA256
Secret requirementMinimum 32 characters (JWT_SECRET env var)

The access token is included in the Authorization: Bearer header for every API request. When it expires, the client uses the refresh token to obtain a new access token without re-entering credentials.

Two-Factor Authentication (TOTP)

Add a second factor to your account using a time-based one-time password (TOTP):

Go to Account Settings > Security and click Enable 2FA.

Scan the QR code with an authenticator app (Google Authenticator, Authy, 1Password, etc.).

  • Issuer: oCore
  • Algorithm: TOTP (RFC 6238)

Enter the 6-digit code from your authenticator to verify.

Save the 8 recovery codes displayed. Each code is single-use and can be used if you lose access to your authenticator.

Recovery Codes

Recovery codes are 8 characters each, lowercase alphanumeric. They are hashed (SHA-256) before storage -- oCore cannot recover them. Store them securely (printed or in a password manager). Each code can only be used once.

Passkeys (WebAuthn)

oCore supports passwordless authentication via WebAuthn passkeys:

  • Hardware keys -- YubiKey, Titan Key
  • Platform authenticators -- Touch ID, Face ID, Windows Hello
  • Synced passkeys -- iCloud Keychain, Google Password Manager

Go to Account Settings > Security > Passkeys and click Register Passkey.

Follow your browser's prompt to create the passkey using your preferred authenticator.

Name the passkey for identification (e.g., "MacBook Touch ID", "YubiKey 5").

Security Settings

Manage 2FA, passkeys, and security preferences.

Open in Dashboard

Session Management

Active Sessions

View and manage your active sessions:

curl https://ocore.example.com/api/user/sessions \
  -H "Authorization: Bearer $TOKEN"

Each session shows:

  • Device and browser information
  • IP address
  • Last active timestamp
  • Creation time

Revoking Sessions

Revoke a specific session:

curl -X DELETE https://ocore.example.com/api/user/sessions/{sessionId} \
  -H "Authorization: Bearer $TOKEN"

CORS Configuration

oCore configures Cross-Origin Resource Sharing (CORS) to control which domains can access the API:

  • The APP_URL environment variable defines the allowed origin
  • The documentation site domain is also allowed for auth-aware features
  • API endpoints return appropriate Access-Control-Allow-* headers

For self-hosted deployments, ensure APP_URL matches your dashboard domain to avoid CORS errors.

Rate Limiting

oCore implements rate limiting to protect against abuse:

Authentication Endpoints

EndpointLimit
Login (POST /api/auth/login)5 requests per minute per IP
Signup (POST /api/auth/signup)5 requests per minute per IP
Password reset5 requests per minute per IP
TOTP verification5 requests per minute per IP

Transfer Callback

EndpointLimit
Presign callback60 requests per minute per IP

General API

The general API does not have per-endpoint rate limiting by default but can be configured via reverse proxy settings (Traefik, Nginx).

Rate Limit Response

When rate limited, the API returns HTTP 429 with a retryAfter field indicating seconds to wait.

SSH Gateway Security

The SSH gateway provides terminal access to Odoo instances via port 2222:

How It Works

  1. User connects: ssh -p 2222 <instance-slug>@ocore.example.com
  2. oCore authenticates using the user's registered SSH public key
  3. The connection is routed to the target instance container
  4. Session is logged in the audit trail

Security Measures

MeasureDescription
Key-based auth onlyNo password authentication on the SSH gateway
User SSH keysUsers register public keys via the dashboard
Session loggingAll SSH sessions are recorded in audit logs
Instance isolationEach connection is scoped to one instance
Session limitsConfigurable max sessions per user

See SSH Access for detailed SSH management.

Encryption at Rest

SSH Credentials

Server SSH private keys and passwords are encrypted at rest:

PropertyValue
AlgorithmAES-256
Key sourceSSH_ENCRYPTION_KEY environment variable
Key requirementMinimum 32 characters
Encryption scopeSSH private keys, SSH passwords

The plaintext credentials exist only in memory during SSH operations. They are never stored in plaintext in the database.

Backup Destination Credentials

Backup destination configurations (S3 access keys, SFTP passwords, etc.) are similarly encrypted:

PropertyValue
StorageEncrypted JSON blob in database
EncryptionAES-256 using server-side key
DecryptionOn-demand when performing backup operations

Recovery Code Hashing

TOTP recovery codes are hashed with SHA-256 before storage. The plaintext codes are shown only once at TOTP setup time.

Network Security

IP Access Control

Restrict access to instances by IP address or CIDR range. See Domains and DNS for IP access rule configuration.

Firewall Recommendations

For self-hosted deployments, expose only necessary ports:

PortServiceAccess
443HTTPS (dashboard + API)Public
80HTTP (redirect to HTTPS)Public
2222SSH gatewayRestricted to known IPs recommended
5432PostgreSQLInternal only (never expose)
8080Backend APIInternal only (behind reverse proxy)

TLS/SSL

  • All public endpoints should use TLS 1.2 or higher
  • The reverse proxy handles TLS termination
  • Self-signed certificates are acceptable for internal services only
  • Use Let's Encrypt or a commercial CA for public endpoints

Security Best Practices Checklist

  • Enable 2FA for all administrator accounts
  • Use passkeys for the highest security accounts (Owner)
  • Rotate API keys regularly (quarterly recommended)
  • Set SSH_ENCRYPTION_KEY to a strong random value (64+ characters)
  • Set JWT_SECRET to a strong random value (64+ characters)
  • Configure a reverse proxy with TLS for all public endpoints
  • Restrict SSH gateway access (port 2222) to known IP ranges
  • Never expose PostgreSQL (port 5432) to the public internet
  • Review audit logs weekly for suspicious activity
  • Remove unused API keys and revoke unused sessions
  • Set IP access rules on production instances
  • Neutralize databases when copying production to staging
  • Use separate organizations for separate security domains

OAuth 2.1 Security

oCore's OAuth Authorization Server implements defense-in-depth measures to protect the MCP authorization flow.

PKCE S256 Enforcement

All authorization code flows require Proof Key for Code Exchange (PKCE) with the S256 method. Plain code challenges are rejected. This prevents authorization code interception attacks even for public clients that cannot securely store a client secret.

SSRF Protection for CIMD

When oCore resolves a CIMD (Client ID Metadata Document) -- where the client_id is an HTTPS URL -- it fetches the document with SSRF safeguards:

  • Connections to RFC 1918, loopback, link-local, and IPv6 unique-local addresses are blocked at the dialer level
  • Cloud metadata IPs (169.254.169.254, 100.100.100.200) are explicitly blocked
  • HTTPS only, no redirects, 5-second timeout, 1 MB response limit
  • The client_id field in the fetched document must exactly match the URL that was fetched

The consent flow uses per-request CSRF tokens to prevent cross-site request forgery:

  • A 32-byte random token is generated when the consent page loads and stored server-side keyed by request ID
  • The token is validated with constant-time comparison on consent submission
  • Tokens are single-use (deleted after validation) and expire after 10 minutes

Constant-Time Secret Comparisons

All secret comparisons use crypto/subtle.ConstantTimeCompare to prevent timing attacks:

  • Client secret verification
  • CSRF token validation
  • Authorization code hash comparison

Refresh Token Rotation with Family Tracking

OAuth refresh tokens are rotated on every use with replay detection:

BehaviorDetail
Normal rotationEach refresh returns a new access + refresh token pair. The old refresh token is marked as rotated.
Grace periodIf a rotated token is replayed within 60 seconds (e.g., network retry), a new pair is issued.
Replay detectionIf a rotated token is replayed after the grace period, the entire token family is revoked. This detects token theft.
Family trackingAll tokens in a rotation chain share a family_id. Revoking the family soft-deletes every token in the chain.

Per-Org OAuth Logout Policy

When a user logs out or an administrator triggers session revocation for an organization:

  • All active OAuth tokens for that user in the organization are soft-deleted
  • Auto-created API keys with no remaining active tokens are cleaned up
  • Manually-linked API keys are left untouched

Token Introspection Access

The /oauth/introspect endpoint (RFC 7662) is restricted to confidential clients only. Public clients receive 401 invalid_client. This prevents unauthorized token metadata queries.

Authorization Code Protections

  • Codes are hashed (SHA-256) before storage and consumed atomically (single-use)
  • Codes expire after 10 minutes
  • redirect_uri is verified via exact byte-for-byte string comparison against registered URIs
  • PAR (Pushed Authorization Request) entries expire after 60 seconds

API Key Scoping

oCore API keys support granular scoping to limit what a key can access, both for REST API usage and MCP (Model Context Protocol) sessions.

Instance Scoping

Restrict an API key to specific instances rather than granting access to all instances in the organization:

curl -X POST https://ocore.example.com/api/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Staging MCP Key",
    "instanceIds": ["INSTANCE_UUID_1", "INSTANCE_UUID_2"]
  }'

When instanceIds is set, the key can only operate on the listed instances. When empty, the key has access to all instances in the organization.

MCP Tool-Level Permissions

For API keys used with MCP clients (AI assistants), oCore provides tool-level access control:

SettingDescription
MCPReadOnlyWhen true, only read-only MCP tools are available (no writes, no destructive actions)
MCPToolAllowlistList of tool suffixes that the key can access (e.g., ["list_users", "read_logs"])
MCPAllowlistModenone (no filtering), allow (only listed tools), or deny (all except listed tools)
MCPModelAllowlistRestrict ORM access to specific Odoo models (e.g., ["res.partner", "sale.order"]). Read-only via API — set directly in the database.

Project Scoping

API keys can be scoped to specific projects within the organization:

curl -X POST https://ocore.example.com/api/api-keys \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "name": "Project-scoped Key",
    "projectIds": ["PROJECT_UUID_1"]
  }'

IP Allowlist on API Keys

Each API key can have an IP allowlist. Requests from IPs not in the list are rejected:

{
  "name": "Office-only Key",
  "ipAllowlist": ["203.0.113.0/24", "198.51.100.42"]
}

Rate Limits per Key

MCP sessions enforce per-organization rate limits:

  • SSE transport -- Configurable requests per minute (org setting)
  • Stdio transport -- Separate rate limit for local CLI usage
  • Concurrency -- Maximum concurrent MCP sessions per organization

IP Access Rules

oCore provides a three-tier IP access control system to restrict traffic to your instances by source IP address or CIDR range.

Scope Hierarchy

IP access rules follow a three-tier inheritance model:

ScopeApplies ToInherits From
OrganizationAll instances in the organization--
ProjectAll instances in the projectOrganization rules
EnvironmentInstances in the specific environmentOrganization + Project rules

When evaluating access, oCore merges rules from all applicable scopes. A request is allowed if it matches any active rule at any scope level.

Traffic Scopes

Each rule specifies which types of traffic it covers:

ScopeDescription
httpWeb browser and REST API access
websocketWebSocket connections (live chat, bus)
sshSSH terminal access via the gateway
ideIDE integrations and code editor access

When no traffic scopes are specified, the rule applies to all four types by default.

Creating Rules

# Organization-level rule
curl -X POST https://ocore.example.com/api/ip-access-rules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "organizationId": "ORG_UUID",
    "cidr": "203.0.113.0/24",
    "label": "Office VPN",
    "trafficScopes": ["http", "websocket"]
  }'

# Environment-level rule
curl -X POST https://ocore.example.com/api/ip-access-rules \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "environmentId": "ENV_UUID",
    "cidr": "198.51.100.42",
    "label": "Developer Home IP",
    "trafficScopes": ["http", "ssh"]
  }'

Navigate to Settings > IP Access Rules in your organization, project, or environment settings. Click Add Rule, enter the CIDR range and optional label, select the traffic scopes, and save.

CIDR Validation

oCore validates all IP inputs using Go's net/netip package:

  • Full CIDR ranges are accepted (e.g., 192.168.1.0/24)
  • Bare IP addresses are accepted and stored in canonical form (e.g., 10.0.0.1)
  • Both IPv4 and IPv6 addresses are supported
  • Invalid inputs are rejected with a descriptive error message

Activating and Deactivating Rules

Each rule has an is_active flag. Deactivating a rule temporarily disables it without deleting:

curl -X PATCH https://ocore.example.com/api/ip-access-rules/{ruleId} \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"isActive": false}'

Rule Application

When IP access rules are created or modified, oCore enqueues a background job to apply the rules to the target instances. The job resolves the three-tier inheritance and updates the instance-level firewall configuration via SSH.

Cross-References

Security features are documented in context throughout the documentation:

Troubleshooting

2FA code not accepted

  • Check that your device clock is synchronized (TOTP is time-based)
  • Ensure you are entering the code for the correct account
  • If locked out, use one of your 8 recovery codes
  • Contact your organization Owner to disable 2FA if recovery codes are lost

Passkey not working

  • Ensure your browser supports WebAuthn (all modern browsers do)
  • Try a different authenticator (hardware key vs platform authenticator)
  • Check that the domain matches exactly (no www. prefix mismatch)
  • Re-register the passkey if issues persist

Rate limited on login

  • Wait 60 seconds before retrying
  • Ensure your IP is not shared with many users (NAT/VPN)
  • If persistently rate limited, check for automated scripts hitting the login endpoint

SSH connection refused on port 2222

  • Verify port 2222 is open in the server's firewall
  • Check that your SSH public key is registered in the dashboard
  • Ensure the SSH gateway is enabled in the deployment configuration
  • Verify the instance slug in your SSH command matches an active instance
Was this page helpful?