Secret Fields¶
Overview¶
Secret fields allow you to mark string properties as sensitive data that should be protected from casual access. When a field is marked as a secret:
- Masked on read: Values are automatically replaced with
********in API responses - Protected access: Revealing plaintext values requires explicit permission
- Secure updates: Existing values are preserved when sending the masked placeholder back
Secret fields are ideal for storing: - API keys and tokens - Passwords and credentials - Connection strings - Private configuration values - Any sensitive data that shouldn't be exposed in normal API responses
Creating a Secret Field¶
Add isSecret: true to any string property definition:
{
"name": "api_credentials",
"recordSlug": "api-credentials",
"properties": [
{
"name": "service_name",
"type": "string",
"required": true
},
{
"name": "api_key",
"type": "string",
"isSecret": true,
"required": true
},
{
"name": "api_secret",
"type": "string",
"isSecret": true
}
]
}
Constraints¶
isSecretis only valid forstringtype properties- Secret fields cannot be marked as
isUnique(secrets are not searchable) - Secret fields can still have other string constraints like
minLength,maxLength, andpattern
Reading Secret Fields¶
When you retrieve a record with secret fields, the secret values are automatically masked:
# Create a record with secrets
curl -X POST https://api.centrali.io/records/slug/api-credentials \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"data": {
"service_name": "Payment Gateway",
"api_key": "pk_live_abc123xyz",
"api_secret": "sk_live_secret_key_here"
}
}'
Response:
{
"id": "550e8400-e29b-41d4-a716-446655440000",
"data": {
"service_name": "Payment Gateway",
"api_key": "********",
"api_secret": "********"
}
}
All secret fields show ******** regardless of the actual value length.
Updating Secret Fields¶
Preserving Existing Values¶
When updating a record, you can preserve existing secret values by either:
- Omitting the field - The secret remains unchanged
- Sending the masked value -
"api_key": "********"is detected and ignored
# Update only the service name - secrets are preserved
curl -X PATCH https://api.centrali.io/records/slug/api-credentials/550e8400-... \
-d '{
"data": {
"service_name": "Payment Gateway v2"
}
}'
Updating to a New Value¶
To change a secret value, send the new plaintext value:
curl -X PATCH https://api.centrali.io/records/slug/api-credentials/550e8400-... \
-d '{
"data": {
"api_key": "pk_live_new_key_xyz"
}
}'
Clearing a Secret¶
To remove a secret value, set it to null or an empty string:
curl -X PATCH https://api.centrali.io/records/slug/api-credentials/550e8400-... \
-d '{
"data": {
"api_secret": null
}
}'
Revealing Secrets¶
To access the plaintext value of a secret field, use the reveal endpoint:
curl -X POST https://api.centrali.io/records/slug/api-credentials/550e8400-.../secrets/reveal \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"fields": ["api_key"]
}'
Response:
Reveal All Secrets¶
Omit the fields parameter to reveal all secret fields:
curl -X POST https://api.centrali.io/records/slug/api-credentials/550e8400-.../secrets/reveal \
-H "Authorization: Bearer YOUR_TOKEN"
Response:
Comparing Secrets¶
To verify a value matches a stored secret without revealing it:
curl -X POST https://api.centrali.io/records/slug/api-credentials/550e8400-.../secrets/compare \
-H "Authorization: Bearer YOUR_TOKEN" \
-d '{
"field": "api_key",
"value": "pk_live_abc123xyz"
}'
Response (match):
Response (no match):
This is useful for authentication flows where you need to verify a submitted value matches the stored secret without exposing it.
Permissions¶
Secret operations require specific permissions on the records resource:
| Operation | Required Permission |
|---|---|
| Create/Update secrets | records:update (standard update permission) |
| Reveal secrets | secrets:reveal |
| Compare secrets | secrets:compare |
Workspace administrators automatically have these permissions. For other roles, you'll need to grant them explicitly.
Example: Grant Secret Access to a Role¶
curl -X POST https://api.centrali.io/permissions \
-d '{
"name": "api_integrations_secret_access",
"resourceName": "records",
"policyName": "api_integrations_team",
"actions": ["secrets:reveal", "secrets:compare"]
}'
Audit Logging¶
All secret operations are logged for security auditing:
- Reveal operations: Logs the user, record ID, and which fields were revealed (not the values)
- Compare operations: Logs the user, record ID, field name, and result (not the compared values)
Secret values are never written to audit logs.
SDK Usage¶
Node.js SDK¶
import { Centrali } from '@centrali/sdk';
const client = new Centrali({ apiKey: 'your-api-key' });
// Create a record with secrets
const record = await client.records.create('api-credentials', {
service_name: 'Payment Gateway',
api_key: 'pk_live_abc123xyz',
api_secret: 'sk_live_secret_key_here'
});
// Reading returns masked values
console.log(record.data.api_key); // "********"
// Reveal secrets
const secrets = await client.revealSecrets('api-credentials', record.id, ['api_key']);
console.log(secrets.api_key); // "pk_live_abc123xyz"
// Compare secrets
const matches = await client.compareSecret(
'api-credentials',
record.id,
'api_key',
'pk_live_abc123xyz'
);
console.log(matches); // true
Compute Functions¶
Secret fields are also masked in compute function contexts. Use the SDK's revealSecrets() method when you need the plaintext values:
export default async function handler(ctx) {
const record = await ctx.sdk.records.get('api-credentials', ctx.trigger.recordId);
// record.data.api_key is "********"
// Reveal the secret when needed
const secrets = await ctx.sdk.revealSecrets('api-credentials', record.id);
// Use the plaintext value
await callExternalApi(secrets.api_key);
}
Best Practices¶
- Minimize secret exposure: Only reveal secrets when absolutely necessary
- Use compare for authentication: Instead of revealing and comparing client-side, use the compare endpoint
- Audit secret access: Monitor your audit logs for unexpected reveal operations
- Rotate secrets regularly: Update secret values periodically for security
- Don't log secrets: Never log plaintext secret values in your code
Security¶
Encryption at Rest¶
Secret fields are encrypted using AES-256-GCM before being stored in the database. Each secret value is stored as an encrypted object containing: - The encrypted ciphertext - An encryption flag - Key version information for key rotation support
This means that even if the database is compromised, secret values remain protected.
Key Rotation¶
Encryption keys can be rotated without downtime. The system supports multiple key versions simultaneously:
-
Add new key: Update
SECRET_KEYSenvironment variable with a new key version: -
New secrets: Automatically encrypted with the new key version
-
Existing secrets: Still readable (decryption uses stored key version)
-
Re-encrypt old secrets: Run the rotation job to migrate all secrets to the new key:
# Check rotation status for ALL workspaces
curl -X GET "https://api.centrali.io/admin/secrets/rotation/stats?apiKey=YOUR_ADMIN_API_KEY"
# Check rotation status for a specific workspace
curl -X GET "https://api.centrali.io/admin/secrets/rotation/stats?apiKey=YOUR_ADMIN_API_KEY&workspaceSlug=my-workspace"
# Execute rotation for ALL workspaces (batch mode)
curl -X POST "https://api.centrali.io/admin/secrets/rotation/execute?apiKey=YOUR_ADMIN_API_KEY" \
-H "Content-Type: application/json" \
-d '{"batchSize": 100}'
# Execute rotation for a specific workspace only
curl -X POST "https://api.centrali.io/admin/secrets/rotation/execute?apiKey=YOUR_ADMIN_API_KEY&workspaceSlug=my-workspace" \
-H "Content-Type: application/json" \
-d '{"batchSize": 100}'
# Rotate secrets for a specific structure only
curl -X POST "https://api.centrali.io/admin/secrets/rotation/structure/api-credentials?apiKey=YOUR_ADMIN_API_KEY&workspaceSlug=my-workspace" \
-H "Content-Type: application/json" \
-d '{"batchSize": 100}'
Note: These are app-level admin endpoints that require the ADMIN_API_KEY environment variable. When workspaceSlug is omitted, the endpoints operate on ALL workspaces in batch mode.
- Remove old key: After rotation completes, old key can be removed from config
The rotation job re-encrypts: - All record secret fields - All function trigger encrypted parameters
Limitations¶
- Secrets are not searchable or filterable
- Secret fields cannot be used in computed expressions
- Maximum secret length follows standard string field limits