Credential Security
Credentials — API keys, OAuth tokens, webhook signing secrets, and integration credentials — are the most sensitive data in the integrations system. A compromised credential can grant an attacker access to connected services, financial data, or customer records. Otesse employs multiple layers of protection to ensure credentials are secure at rest, in transit, and throughout their lifecycle.
Encryption at Rest
All integration credentials are encrypted before storage using AES-256-GCM (Advanced Encryption Standard with Galois/Counter Mode).
How It Works
- When a credential is created (API key entry or OAuth token exchange), the plaintext value is passed to the encryption service
- The encryption key is derived from the
INTEGRATIONENCRYPTIONKEYenvironment variable — this key is never stored in the database - AES-256-GCM encryption produces ciphertext plus an authentication tag
- The ciphertext is stored in
IntegrationCredential.credentialValueEncrypted - The plaintext is immediately discarded from memory — it exists only during the encrypt operation
Security Properties
| Property | Guarantee |
|---|---|
| Confidentiality | AES-256 is computationally infeasible to crack with current technology |
| Integrity | GCM mode includes an authentication tag that detects tampering |
| Non-recoverability | Without the environment-variable-based encryption key, ciphertext is useless |
On Disconnection
When an integration is disconnected, credentials receive defense-in-depth treatment:
- The
credentialValueEncryptedfield is overwritten with an empty string - The record is then soft-deleted (
isDeleted = true,isActive = false) - This ensures that even if a soft-deleted record is accessed, the encrypted value is already destroyed
API Key Hashing
Otesse-generated API keys (the ones used by external applications to call the Otesse API) receive different treatment from integration credentials:
- The full API key is generated and displayed to the user once
- The key is immediately hashed using SHA-256
- Only the hash is stored in the database
- When an API request arrives, the provided key is hashed and compared to stored hashes
- The plaintext key never exists in the database at any point after generation
This means that even a complete database compromise cannot reveal API keys — only their hashes.
What Is Never Stored or Logged
The following data never appears in the database, logs, or API responses:
| Data | Protection |
|---|---|
| Plaintext API keys | Only the SHA-256 hash is stored. Last 4 characters stored separately for display |
| Plaintext OAuth tokens | Only AES-256-GCM encrypted ciphertext is stored |
| Plaintext integration credentials | Only encrypted ciphertext is stored |
| SSN values | Encrypted at rest, last 4 digits stored separately for display |
| Webhook signing secrets | Only encrypted ciphertext is stored |
Log Sanitization
All logging is sanitized to exclude credential values:
- API request logs record the endpoint, method, status code, and response time — never the Authorization header value
- Integration connection logs record the provider name and action type — never the credential values
- Audit logs record who performed an action and when — never the credential content
Access Controls
Who Can Access Credentials
| Action | Permission Required |
|---|---|
| View integration connections | settings.view |
| Connect/disconnect integrations | settings.edit |
| Generate API keys | settings.edit |
| Revoke API keys | settings.edit |
| View credential details | Never — credentials are never displayed after creation |
Credential Isolation
- Integration credentials are scoped to a specific connection (one user's authentication)
- API keys are scoped to the organization
- One organization cannot access another organization's credentials
- One user's OAuth tokens cannot be accessed by another user in the same organization
Audit Trail
Every credential-related action is logged in the audit trail:
| Action | Logged Details |
|---|---|
| Credential created | Provider, auth type, user, timestamp, IP address |
| Credential refreshed (OAuth) | Provider, old token expiry, new token issued, timestamp |
| Credential revoked | Provider, revoking user, timestamp, IP address |
| Credential deleted (disconnect) | Provider, user, timestamp, whether OAuth revocation succeeded |
| API key generated | Key name, scope level, scopes granted, user, timestamp |
| API key revoked | Key name, revoking user, timestamp, last usage info |
Security Best Practices for Users
- Use the minimum scope needed — When generating API keys, select only the permissions the application actually requires. A reporting dashboard needs read-only access, not admin.
- Set expiration dates — Avoid "Never" expiration. 90-day rotation is recommended for most use cases. This limits the damage window if a key is compromised.
- Use separate keys per application — Each external application or integration should have its own API key. This enables independent revocation and usage tracking.
- Store credentials in environment variables — Never hard-code API keys in source code, commit them to version control, or share them in plaintext via email or chat.
- Monitor usage logs — Review API usage logs regularly for unexpected endpoints, unfamiliar IP addresses, or unusual request volumes that could indicate a compromised key.
- Revoke immediately if compromised — If a credential is exposed (accidentally committed to a public repository, shared in a chat message, etc.), revoke it immediately and generate a replacement.
- Rotate proactively — Do not wait for keys to expire. Generate replacements before the old key's expiration date to avoid service disruption.
On this page