Security Operations
This reference covers the complete security operations lifecycle for Arbitex administrators: monitoring authentication events, verifying audit log integrity, executing incident response playbooks, hardening your deployment, and collecting SOC 2 evidence. It consolidates operational procedures that span multiple subsystems into a single authoritative reference.
For user-facing event history and session self-service, see Account Security Monitor. For OCSF export format details and framework-specific evidence packs, see Compliance Audit Evidence Guide. For CredInt architecture and corpus details, see Credential Intelligence.
Account Security Monitoring
Section titled “Account Security Monitoring”Authentication Event Types
Section titled “Authentication Event Types”Every authentication action against an Arbitex account produces an audit event with the auth. prefix. The table below lists all 12 event types, the action string used in API responses and audit exports, the source service, and the trigger condition.
| Action String | Source | Trigger |
|---|---|---|
auth.login_success |
auth.py |
Successful interactive login via password or passkey |
auth.login_failed |
auth.py |
Failed login — wrong password, locked account, or unrecognised user |
auth.logout |
auth.py |
Explicit logout initiated by the user |
auth.mfa_verify_success |
auth.py |
MFA challenge completed successfully during the login flow |
auth.mfa_verify_failed |
auth.py |
MFA challenge failed — wrong code or expired TOTP window |
auth.mfa_enabled |
auth.py |
TOTP or WebAuthn MFA factor added to the account |
auth.mfa_disabled |
auth.py |
MFA factor removed by user or admin |
auth.webauthn_registered |
auth.py |
New WebAuthn passkey registered on the account |
auth.webauthn_login |
auth.py |
Successful login using a WebAuthn/FIDO2 passkey |
auth.webauthn_revoked |
auth.py |
WebAuthn passkey removed from the account |
auth.session_evicted |
sessions.py |
Session terminated automatically — concurrent limit exceeded, oldest session removed (FIFO) |
auth.session_force_logout |
sessions.py |
Session terminated by an admin force-logout action |
All events include user_id (UUID of the authenticating user) and source_ip (origin of the request). Events related to admin actions additionally include metadata.admin_id identifying the acting administrator.
Self-Service View (Cloud Portal)
Section titled “Self-Service View (Cloud Portal)”Every user can view their own authentication history and active sessions without administrator involvement.
Path: /portal/my-security or Account → Security
The self-service view provides:
- Event timeline — last 90 days of auth events, most recent first; relative timestamps with absolute on hover
- Active Sessions panel — all currently valid sessions with IP, location, device, and expiry
- Filter controls — event type (one of the 12 types or “All”), date range
- Auto-refresh — timeline refreshes every 60 seconds
Each event row shows: event type icon (green = success, red = failure, amber = warning), IP address, GeoIP city and country, device parsed from User-Agent, and anonymising network badges (VPN / Proxy / Tor / Hosting) when detected.
API (self-service):
# Query own auth eventscurl -H "Authorization: Bearer $TOKEN" \ "https://api.arbitex.ai/v1/audit/events?user_id=me&limit=50"
# Filter to failed logins onlycurl -H "Authorization: Bearer $TOKEN" \ "https://api.arbitex.ai/v1/audit/events?user_id=me&action=auth.login_failed&limit=50"The user_id=me alias resolves to the authenticated caller’s UUID at query time. Non-admin users can only use "me" — passing any other UUID returns 403.
Active Sessions: API Reference
Section titled “Active Sessions: API Reference”Admin users can list and terminate sessions across the entire organisation.
GET /api/v1/admin/sessions # list all org sessionsGET /api/v1/admin/sessions?user_id={uuid} # filter to one userDELETE /api/v1/admin/sessions/{session_id} # revoke a single sessionDELETE /api/v1/admin/users/{user_id}/sessions # revoke all sessions for a userList sessions response:
{ "items": [ { "id": "3fa85f64-5717-4562-b3fc-2c963f66afa6", "user_id": "a1b2c3d4-0000-0000-0000-000000000001", "token_jti": "d4f8e2c1-1234-5678-abcd-ef0123456789", "created_at": "2026-03-14T08:00:00Z", "last_activity": "2026-03-14T10:15:00Z", "expires_at": "2026-03-21T08:00:00Z", "ip_address": "203.0.113.42", "user_agent": "Mozilla/5.0 ...", "is_active": true } ], "total": 1}Concurrent session limit: Default is 5 sessions per user. When a new login would exceed the limit, sessions.py runs enforce_session_limit() immediately: the oldest sessions are evicted in FIFO order, their token_jti values added to the JWT blacklist, and an auth.session_evicted event is written for each. Session rows are soft-deleted (is_active = false) — they are never purged, preserving the audit trail.
Revoke all sessions for a user:
curl -X DELETE \ -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/users/a1b2c3d4-0000-0000-0000-000000000001/sessions"Returns the list of revoked session objects. Returns 404 if the user has no active sessions. Each revocation writes an auth.session_force_logout event with metadata.admin_id, metadata.session_id, and metadata.reason: "admin_force_logout".
GeoIP Enrichment
Section titled “GeoIP Enrichment”Every authentication event is enriched at write time using a layered GeoIP pipeline. The enrichment data is stored with the event record and cannot be retroactively altered.
| Data Source | Purpose | Fields |
|---|---|---|
| MaxMind GeoIP2 City MMDB | Primary city/country/ISP resolution | country_code, country_name, region, city, isp |
| IP2Location DB25 | Fallback city resolution | proxy_type, threat |
| iptoasn TSV | ASN resolution | asn, asn_org |
| MaxMind Anonymous IP MMDB | Anonymising network detection | is_vpn, is_proxy, is_tor, is_hosting |
The enrichment layer maintains a 128,000-entry TTL cache with a 4-hour TTL per entry. The cache is hot-reloaded on SIGHUP — send this signal to the platform process to reload updated MMDB files without a full restart.
Fields on each auth event:
| Field | Description |
|---|---|
src_country_code |
ISO 3166-1 alpha-2 country code |
src_country_name |
Full country name |
src_region |
Region/state/province |
src_city |
City name (best-effort; absent for some IP ranges) |
src_isp |
Internet service provider |
src_proxy_type |
Proxy classification if is_proxy is true |
src_threat |
Threat label from IP2Location (e.g., spam, attacker) |
src_asn |
Autonomous System Number |
src_asn_org |
ASN organisation name |
is_vpn |
True if source IP is a known VPN exit node |
is_proxy |
True if source IP is a known open proxy |
is_tor |
True if source IP is a Tor exit node |
is_hosting |
True if source IP is a cloud hosting/datacenter range |
Private (RFC 1918), CGNAT (100.64.0.0/10), and loopback addresses resolve to empty location fields. GeoIP enrichment is best-effort and should not be the sole basis for access control decisions.
Admin View
Section titled “Admin View”Users with the ADMIN role have access to expanded controls that are not available to standard USER accounts. Arbitex has two roles: USER and ADMIN. There is no dedicated security_auditor role — security auditing is performed by ADMIN users.
Portal path: Admin → Users → [User] → Security Events
Admin capabilities beyond self-service:
- View the full event timeline for any user in the organisation
- Export — download events as CSV for audit evidence
- Force Logout — terminate all active sessions for a selected user in a single action
- Search across users by IP address, event type, or date range
Hot retention: All auth events are queryable via the API for 90 days. Events older than 90 days are available via the compliance export API. See Compliance Audit Evidence Guide for export procedures.
Audit Log Integrity
Section titled “Audit Log Integrity”HMAC-SHA256 Chain
Section titled “HMAC-SHA256 Chain”Every audit log record is cryptographically bound to its predecessor using HMAC-SHA256. This makes it detectable if any record is modified, deleted, or reordered after the fact — even by a database administrator with direct write access.
Each record includes three integrity fields:
| Field | Description |
|---|---|
hmac |
HMAC-SHA256 of this record’s canonical content + previous_hmac |
previous_hmac |
HMAC of the immediately preceding record (acts as a digest pointer) |
hmac_key_id |
Identifier of the signing key used for this record |
These three fields are excluded from the HMAC digest computation. This makes re-verification idempotent — you can verify the chain without needing to strip integrity fields before hashing.
Genesis Sentinel
Section titled “Genesis Sentinel”The first audit record in the chain uses a fixed genesis sentinel as its previous_hmac:
previous_hmac = "0000000000000000000000000000000000000000000000000000000000000000"That is 64 zero characters — a 256-bit zero string. verify_chain() checks that the first record’s previous_hmac equals exactly this value. Any deviation indicates the chain has been prefixed with fabricated records.
HMAC Message Format
Section titled “HMAC Message Format”The canonical message that is HMAC-signed for each record is:
key_id + ":" + json.dumps(content, sort_keys=True) + previous_hmacWhere:
key_idis the value ofhmac_key_idfor the record (defaults to"default"for legacy events created before key versioning was introduced)contentis the record’s data as a Python dict with keys sorted (sort_keys=True) — this ensures deterministic serialisation across platform versionsprevious_hmacis the raw 64-character hex string from the preceding record
The sort_keys=True requirement is critical: if keys are serialised in a different order, the HMAC will not match even though the data is identical.
Key Versioning
Section titled “Key Versioning”The hmac_key_id field enables key rotation without breaking backward-compatible verification. When a new signing key is introduced:
- New records use the new
key_idin the message prefix and store the newhmac_key_id. - Verification reads
hmac_key_idfrom each record and selects the corresponding key for that record’s HMAC computation. - Records signed with the previous key (stored with the old
hmac_key_id) continue to verify correctly using the old key.
Legacy records that predate key versioning have hmac_key_id = "default".
Chain Verification
Section titled “Chain Verification”The verify_chain() function performs three checks on a sequence of records ordered by creation time:
- Genesis check — the first record’s
previous_hmacmust equal the 64-character zero sentinel. - HMAC recompute — for each record, recompute the HMAC using the stored
hmac_key_idand compare against the storedhmac. Mismatch means the record was altered. - Link consistency — for each record after the first, the stored
previous_hmacmust equal thehmacof the immediately preceding record. Mismatch means a record was inserted, deleted, or reordered.
The function returns (is_valid: bool, errors: list[str]). A valid chain returns (True, []). Any failure returns (False, [<description of each broken link>]).
API verification:
curl -s -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"start": "2026-01-01T00:00:00Z", "end": "2026-12-31T23:59:59Z"}' \ "https://api.arbitex.ai/api/v1/admin/audit/verify" \ | jq '.'Clean chain response:
{ "valid": true, "events_checked": 48215, "errors": []}Broken chain response:
{ "valid": false, "events_checked": 48215, "errors": [ "Event 1423: HMAC mismatch (expected 'a3f...', got 'c7d...')", "Event 1424: previous_hmac mismatch" ]}Disabling HMAC (Not Recommended)
Section titled “Disabling HMAC (Not Recommended)”The HMAC chain is silently disabled when the AUDIT_HMAC_KEY environment variable is empty or unset. In this state, hmac, previous_hmac, and hmac_key_id fields are omitted from records, and verify_chain() returns (True, []) trivially (no records to verify). Production deployments must always have AUDIT_HMAC_KEY set to a strong secret.
OCSF Compliance Export Format
Section titled “OCSF Compliance Export Format”Arbitex exports audit events in OCSF v1.1.0 (Open Cybersecurity Schema Framework) for SIEM integration. The product metadata embedded in every export is:
{ "metadata": { "product": { "vendor_name": "Arbitex", "uid": "arbitex-platform", "version": "..." } }}OCSF class mappings:
| OCSF Class | Class UID | Arbitex Events |
|---|---|---|
| API Activity | 6003 | API requests, model routing, admin API calls |
| Security Finding | 2001 | DLP blocks, policy enforcement, CredInt hits |
| Authentication Activity | 3002 | Auth events (auth.*) |
| Account Change | 3004 | Admin config changes, user provisioning, SCIM events |
OCSF formatter action mappings (shorter form used internally by the formatter, which maps to the full auth.* action strings above):
| Formatter Form | Maps From | OCSF Activity |
|---|---|---|
login_success |
auth.login_success |
Authentication success |
mfa_success |
auth.mfa_verify_success |
MFA challenge success |
token_issued |
JWT issuance events | Token lifecycle |
token_revoked |
Session eviction/force-logout | Token revocation |
password_changed |
Password reset events | Account change |
saml_login |
SAML SSO login | Federation login |
webauthn_login |
auth.webauthn_login |
Passkey authentication |
For full SIEM connector configuration and OCSF field mappings per connector, see Compliance Audit Evidence Guide.
Incident Response Playbooks
Section titled “Incident Response Playbooks”Playbook 1: Suspicious Login Investigation
Section titled “Playbook 1: Suspicious Login Investigation”Use this playbook when you observe unexplained auth.login_failed spikes, logins from unusual geographic locations, or reports of account compromise.
Step 1 — Identify the pattern
Query the audit log for failed login events in the investigation window:
# Failed logins in last 24 hourscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/v1/audit/events?action=auth.login_failed&created_after=$(date -u -d '24 hours ago' +%Y-%m-%dT%H:%M:%SZ)&limit=500" \ | jq '.events | group_by(.metadata.src_ip) | map({ip: .[0].metadata.src_ip, count: length}) | sort_by(-.count)'Look for: a single IP with high failed-login count (brute force), or a single user_id appearing across many failures (targeted account attack).
Step 2 — Check GeoIP and anonymising network flags
For each suspicious IP, examine the enrichment fields on the events:
curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/v1/audit/events?action=auth.login_failed&created_after=2026-03-14T00:00:00Z&limit=50" \ | jq '.events[] | {ip: .metadata.src_ip, country: .src_country_code, city: .src_city, is_vpn: .is_vpn, is_tor: .is_tor, is_hosting: .is_hosting}'Impossible travel: If the same user_id has a auth.login_success in city A and then a login attempt (success or failure) from city B within a timeframe too short to travel, this is a strong indicator of credential compromise.
VPN/Tor/hosting flags: is_vpn: true, is_tor: true, or is_hosting: true on a login event does not by itself indicate compromise, but combined with auth.login_failed spikes or unusual access times, it warrants investigation.
Step 3 — Force-revoke sessions
If you determine the account is compromised or being actively attacked, revoke all active sessions immediately:
curl -X DELETE \ -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/users/{user_id}/sessions"This immediately blacklists all token_jti values for the user’s sessions. Any in-flight requests using those tokens will receive 401 Unauthorized. The action is logged as auth.session_force_logout events.
Step 4 — Export audit trail for evidence
Export the investigation period’s events with the HMAC chain intact:
# Export events for the affected user, full investigation windowcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/v1/audit/events?user_id={user_id}&created_after=2026-03-01T00:00:00Z&limit=1000" \ > investigation-$(date +%Y%m%d)-user-{user_id}.json
# Verify chain integrity for the evidence periodcurl -s -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"start": "2026-03-01T00:00:00Z", "end": "2026-03-15T00:00:00Z"}' \ "https://api.arbitex.ai/api/v1/admin/audit/verify" \ > chain-verification-$(date +%Y%m%d).jsonStep 5 — Notify and harden
- Notify the affected user and their org admin of the investigation.
- Initiate a forced password reset:
POST /api/v1/admin/users/{user_id}/reset-password - If the attack originated from a specific IP range, consider adding an IP allowlist entry via Admin → Security → IP Allowlist.
- If attacks are ongoing from a class of IPs (e.g., a specific ASN), route the events to your SIEM and set a detection rule on
auth.login_failedvolume from thatsrc_asn.
Playbook 2: DLP Policy Bypass Investigation
Section titled “Playbook 2: DLP Policy Bypass Investigation”Use this playbook when you observe ALLOW_WITH_OVERRIDE events that appear unexpected — overrides by users who should not have override permissions, override volume spikes, or overrides on high-sensitivity policies.
Step 1 — Query override events
# All overrides in the last 7 dayscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?action=allow_with_override&start=2026-03-08T00:00:00Z&end=2026-03-15T00:00:00Z&limit=1000" \ | jq '.events[] | {event_id: .id, user_id: .user_id, org_id: .org_id, rule_id: .metadata.rule_id, entity_type: .metadata.entity_type, override_reason: .metadata.override_reason, created_at: .created_at}'Look for: repeated overrides from the same user_id on the same rule_id (systematic bypass), overrides on BLOCK-level policies (which should be impossible — BLOCK is non-overridable), or overrides outside business hours.
Step 2 — Examine the override token details
ALLOW_WITH_OVERRIDE generates a short-lived override token issued at the time the user provides their justification. This token is a signed HS256 JWT with:
- Subject:
policy_override - TTL: 5 minutes from issuance
- Claims:
rule_id,user_id,org_id,entity_type
The token TTL means an override is scoped to a single 5-minute window. If you see many override events from the same user on the same rule within a short window, this indicates the user is repeatedly triggering overrides rather than a persistent bypass.
Step 3 — Check user group membership
Examine whether the overriding user belongs to a group that legitimately has override permissions for that rule:
# Get the user's group membershipscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/users/{user_id}/groups"
# Check which groups have override permission on the affected rulecurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/policy/rules/{rule_id}/group-permissions"If the user’s group is not expected to have override capability for that rule, this is a policy misconfiguration — not a technical bypass. Update the rule’s override permission matrix to remove the group.
Step 4 — Run policy simulation
Use the policy simulator to validate current rule behaviour before making changes:
# Simulate against a test payload to confirm current enforcementcurl -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{ "user_id": "{user_id}", "payload": "Sample content containing sensitive data", "policy_chain_id": "{chain_id}" }' \ "https://api.arbitex.ai/api/v1/admin/policy/simulate"See Outpost Policy Simulator for full simulation options.
Step 5 — Remediation
| Finding | Action |
|---|---|
| User overriding a policy they should not have override rights on | Remove group override permission for that rule |
| Override volume is expected but undocumented | Add override event routing to SIEM; create audit evidence package |
Policy is set to ALLOW_WITH_OVERRIDE when it should be BLOCK |
Change rule action to BLOCK — this removes the override path entirely |
| Override reason text is absent or trivial | Review override reason requirements in policy configuration |
Playbook 3: Credential Exposure Response
Section titled “Playbook 3: Credential Exposure Response”Use this playbook when CredInt detects a known-compromised credential in an AI request, indicated by a credint_hit: true audit event with frequency_bucket: "critical" or "high".
Step 1 — Identify the exposure event
# Find credint hits in the last 7 dayscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?credint_hit=true&start=2026-03-08T00:00:00Z&limit=100" \ | jq '.events[] | {id: .id, user_id: .user_id, frequency_bucket: .frequency_bucket, sha1_prefix: .sha1_prefix, created_at: .created_at, action: .action}'The sha1_prefix field contains the first 8 hex characters of the SHA-1 hash of the detected credential. This allows cross-referencing against breach databases without exposing the plaintext credential. The cleartext credential is never written to the audit log.
Step 2 — Assess CredInt service state
CredInt uses a circuit breaker to prevent a degraded CredInt microservice from blocking AI requests. Check the circuit breaker state:
- Circuit breaker threshold: 3 consecutive failures triggers the circuit open
- Reset window: 60 seconds after opening, the circuit transitions to half-open
- Fail-open design: When the circuit is open, requests are permitted through without CredInt checking — they are not blocked
Check current CredInt availability:
# CredInt service health (from outpost or platform host)curl -s http://credint:8202/health | jq '.'
# Check circuit breaker state in platform logsgrep "credint circuit" /var/log/arbitex/platform.log | tail -20Step 3 — Block the user’s sessions
Immediately terminate all active sessions for the user whose request contained the compromised credential:
curl -X DELETE \ -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/users/{user_id}/sessions"This revokes all active JWTs immediately via the blacklist. The user cannot authenticate with the compromised credential even if they attempt another login before the password is reset.
Step 4 — Coordinate credential rotation
Arbitex cannot rotate the exposed credential — it is external to the platform (a database password, API key, bearer token, etc.). The sha1_prefix field in the audit event identifies which credential was flagged without revealing the plaintext.
Actions required outside Arbitex:
- Determine which system or service the credential belongs to (coordinate with the user).
- Immediately rotate the credential in the owning system.
- Revoke or invalidate the exposed credential in all systems where it was used.
- Audit the owning system’s access logs for any use of the credential between the estimated exposure time and revocation.
Step 5 — Assemble compliance record
Export the CredInt event and related audit trail:
# Export the specific credint eventcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/v1/audit/events/{event_id}" \ > credint-event-$(date +%Y%m%d).json
# Export full user audit trail for investigation windowcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/v1/audit/events?user_id={user_id}&created_after=2026-03-01T00:00:00Z&limit=1000" \ > user-trail-$(date +%Y%m%d).json
# Verify chain integritycurl -s -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"start": "2026-03-01T00:00:00Z", "end": "2026-03-15T23:59:59Z"}' \ "https://api.arbitex.ai/api/v1/admin/audit/verify" \ > chain-verify-$(date +%Y%m%d).jsonThis package constitutes the incident record for compliance reporting. The credint_hit: true flag, frequency_bucket, and sha1_prefix fields in the audit export are sufficient to document the detection event without retaining the plaintext credential.
Security Hardening Checklist
Section titled “Security Hardening Checklist”Secrets Backend — Azure Key Vault
Section titled “Secrets Backend — Azure Key Vault”Arbitex supports Azure Key Vault as a secrets backend. When configured, all sensitive environment variables are resolved from Key Vault at startup rather than from process environment variables or .env files.
Configuration:
SECRETS_BACKEND=vaultAZURE_VAULT_URL=https://your-vault.vault.azure.net/Authentication uses DefaultAzureCredential — this resolves credentials in order: environment variables, workload identity, managed identity, Azure CLI. In production, configure a managed identity on the compute hosting the platform.
Name translation: Environment variable names use underscores (AUDIT_HMAC_KEY) but Key Vault secret names use hyphens. The secrets backend automatically translates _ to - when resolving names: AUDIT_HMAC_KEY resolves from Key Vault as AUDIT-HMAC-KEY.
Fail-fast startup: If SECRETS_BACKEND=vault and any required secret cannot be resolved from Key Vault, the platform refuses to start. This prevents silent fallback to insecure defaults or unset secrets in a production environment.
mTLS for Internal Service Communication
Section titled “mTLS for Internal Service Communication”Internal API routes (/v1/internal/) are protected by mutual TLS. The platform validates the client certificate against a CA bundle before processing any internal request.
Configuration:
| Variable | Description |
|---|---|
MTLS_CA_BUNDLE |
Path to a PEM bundle containing CA certificates. Takes precedence over individual cert paths. |
CLOUD_CA_CERT_PATH |
Path to the primary CA certificate (used if MTLS_CA_BUNDLE is not set). |
CLOUD_CA_INTERMEDIATE_PATH |
Path to the intermediate CA certificate (used if MTLS_CA_BUNDLE is not set). |
When MTLS_CA_BUNDLE is set, it overrides CLOUD_CA_CERT_PATH and CLOUD_CA_INTERMEDIATE_PATH. Use the bundle path for simplicity; use individual cert paths only for legacy compatibility.
What mTLS protects: The /v1/internal/ route prefix. All requests to these endpoints must present a valid client certificate signed by the configured CA. External requests without a certificate are rejected before any handler logic runs.
Support mTLS Bypass — Emergency Use Only
Section titled “Support mTLS Bypass — Emergency Use Only”The _SUPPORT_MTLS_BYPASS environment variable enables an emergency bypass that allows support access without a valid client certificate. This is intended exclusively for recovery scenarios where certificate infrastructure has failed and the system is inaccessible.
Confirm bypass is not active:
# Confirm the variable is not set in the running environmentprintenv _SUPPORT_MTLS_BYPASS
# Search application logs for bypass usagegrep "support-bypass" /var/log/arbitex/platform.logSCIM Token Security
Section titled “SCIM Token Security”SCIM provisioning tokens are generated using Python’s secrets.token_urlsafe(32), providing 256 bits of cryptographic entropy. When a SCIM token is created:
- The full token value is displayed once — at creation time only.
- The platform stores only the SHA-256 hash of the token (not bcrypt, not the plaintext).
- Subsequent requests authenticate by hashing the presented token and comparing to the stored hash.
Multi-organisation isolation is enforced at the token level: each SCIM token is scoped to a single org_id. A token issued for organisation A cannot authenticate SCIM requests targeting organisation B.
Token rotation procedure:
- Generate a new token:
POST /api/v1/admin/scim/tokens - Copy the returned token value — this is the only opportunity to do so.
- Update the token value in your IdP SCIM connector.
- Verify provisioning is working with the new token.
- Delete the old token:
DELETE /api/v1/admin/scim/tokens/{token_id}
MFA Configuration
Section titled “MFA Configuration”MFA events produce auditable records that demonstrate the MFA control is operating:
| Event | Trigger |
|---|---|
auth.mfa_enabled |
User adds TOTP or WebAuthn factor |
auth.mfa_disabled |
User or admin removes an MFA factor |
auth.mfa_verify_success |
MFA challenge passed during login |
auth.mfa_verify_failed |
MFA challenge failed |
MFA backup codes are stored using bcrypt (not SCIM token SHA-256). Backup codes are one-time use; each use invalidates the code.
Enforce MFA org-wide:
curl -X PUT \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d '{"enforcement_level": "required"}' \ "https://api.arbitex.ai/api/v1/admin/org/mfa-policy"With enforcement_level: "required", all requests to sensitive endpoints must carry a JWT with mfa_verified: true. Requests without this claim receive 403. The policy takes effect immediately — the in-memory policy cache is cleared synchronously on the API response.
See MFA Enforcement for full policy configuration options.
DLP Inference Fail Mode
Section titled “DLP Inference Fail Mode”When the DLP inference service is unavailable, the platform’s behaviour is controlled by DLP_INFERENCE_FAIL_MODE:
| Value | Behaviour |
|---|---|
closed (default) |
Requests are blocked when inference is unavailable — no content passes without a policy evaluation |
open |
Legacy mode — requests pass through without DLP inspection when inference is unavailable |
Production deployments should always use the default closed mode. Monitor inference service availability via the OTel metrics pipeline and alert on sustained unavailability so that fail-closed blocks can be distinguished from an actual infrastructure issue.
Hardening Checklist Summary
Section titled “Hardening Checklist Summary”| Control | Configuration | Verification |
|---|---|---|
| Azure Key Vault secrets | SECRETS_BACKEND=vault, AZURE_VAULT_URL set |
Platform starts without env var secrets |
| mTLS on internal routes | MTLS_CA_BUNDLE or cert paths set |
curl /v1/internal/ without cert returns 403 |
| Support bypass disabled | _SUPPORT_MTLS_BYPASS unset |
printenv _SUPPORT_MTLS_BYPASS returns empty |
| AUDIT_HMAC_KEY set | Non-empty value in secrets backend | verify_chain() returns field-present responses |
| MFA enforced | enforcement_level: "required" |
Test request without mfa_verified claim returns 403 |
| DLP fail mode closed | DLP_INFERENCE_FAIL_MODE=closed or not set |
Default confirmed; do not set open |
| SCIM tokens rotated | Tokens < 90 days old | Audit: GET /api/v1/admin/scim/tokens — check created_at |
| Session limit configured | max_concurrent_sessions reviewed |
Default is 5; adjust if org policy requires lower |
SOC 2 Audit Evidence Gathering
Section titled “SOC 2 Audit Evidence Gathering”SOC 2 Report Endpoint
Section titled “SOC 2 Report Endpoint”Arbitex provides a pre-built SOC 2 summary report for administrators:
GET /api/v1/admin/compliance/soc2-report?days=30This endpoint requires the ADMIN role. It returns a structured summary covering the five TSC domains mapped to actual platform state — not a static template.
Example request:
# 30-day report (default)curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/compliance/soc2-report?days=30" \ | jq '.' > soc2-report-$(date +%Y%m%d).json
# 90-day report for quarterly reviewcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/compliance/soc2-report?days=90" \ | jq '.' > soc2-report-90day-$(date +%Y%m%d).json
# Annual report for SOC 2 Type II examination periodcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/compliance/soc2-report?days=365" \ | jq '.' > soc2-report-annual-$(date +%Y%m%d).jsonReport Sections and TSC Mapping
Section titled “Report Sections and TSC Mapping”The report response contains five top-level sections. Each maps to a SOC 2 Trust Services Criteria domain.
Section: access_controls (TSC: CC6)
Logical and physical access controls. Covers user account state, privilege distribution, MFA adoption, session management, and IP access controls.
{ "access_controls": { "tsc": "CC6", "total_users": 142, "active_users": 138, "role_distribution": { "ADMIN": 8, "USER": 134 }, "mfa_adoption_pct": 94.4, "active_sessions": 23, "ip_allowlist_entries": 5 }}| Field | SOC 2 Use |
|---|---|
role_distribution |
Demonstrates least-privilege — only necessary users hold ADMIN role |
mfa_adoption_pct |
Quantifies MFA adoption against org policy requirement |
ip_allowlist_entries |
Documents network-level access restrictions in place |
Section: audit_trail (TSC: CC7)
Detection and monitoring controls. Covers audit log volume, HMAC chain integrity status, any detected gaps, and the date range of coverage.
{ "audit_trail": { "tsc": "CC7", "total_records": 482150, "hmac_chain_status": "verified", "chain_errors": 0, "gap_count": 0, "date_range": { "start": "2026-02-13T00:00:00Z", "end": "2026-03-15T00:00:00Z" } }}hmac_chain_status will be one of:
"verified"— chain ran and all records checked cleanly"failed"— chain verification detected one or more integrity errors;chain_errors> 0"disabled"—AUDIT_HMAC_KEYis not set; chain integrity is not being maintained
Section: data_protection (TSC: CC6/CC7)
Data classification and enforcement controls. Covers DLP rule coverage, event volume by severity, and compliance bundle activation.
{ "data_protection": { "tsc": "CC6/CC7", "dlp_rules_active": 47, "dlp_rules_total": 52, "events_by_severity": { "critical": 12, "high": 89, "medium": 1204, "low": 8871 }, "compliance_bundles_enabled": 3, "compliance_bundles_total": 6 }}Section: change_management (TSC: CC8)
Change and configuration management controls. Covers the count and types of admin configuration changes during the report period.
{ "change_management": { "tsc": "CC8", "admin_config_changes": 34, "tracked_actions": [ "policy_rule_created", "policy_rule_updated", "policy_rule_deleted", "mfa_policy_updated", "scim_token_created", "scim_token_revoked", "compliance_bundle_enabled", "compliance_bundle_disabled", "ip_allowlist_updated", "user_role_changed" ] }}Section: availability (TSC: A1)
Availability and reliability controls. Covers circuit breaker states for external service dependencies and failover configuration.
{ "availability": { "tsc": "A1", "circuit_breakers": { "credint": "closed", "dlp_inference": "closed", "geoip": "closed" }, "failover_configured": true }}Circuit breaker states:
"closed"— service operating normally, all requests passing through"open"— circuit tripped; service is bypassed (fail-open behaviour active)"half_open"— recovering; test requests are being sent
Evidence Collection Commands
Section titled “Evidence Collection Commands”Full SOC 2 evidence pack for a 12-month examination period:
EXAM_START="2025-01-01T00:00:00Z"EXAM_END="2025-12-31T23:59:59Z"EVIDENCE_DIR="soc2-evidence-$(date +%Y%m%d)"mkdir -p "$EVIDENCE_DIR"
# SOC 2 report summarycurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/compliance/soc2-report?days=365" \ > "$EVIDENCE_DIR/soc2-report.json"
# CC6 — Access eventscurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?category=authentication&start=${EXAM_START}&end=${EXAM_END}&limit=10000" \ > "$EVIDENCE_DIR/cc6-access-events.json"
# CC6 — DLP enforcementcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?action=block&start=${EXAM_START}&end=${EXAM_END}&limit=10000" \ > "$EVIDENCE_DIR/cc6-dlp-enforcement.json"
# CC7 — HMAC chain verificationcurl -s -X POST \ -H "Authorization: Bearer $ADMIN_TOKEN" \ -H "Content-Type: application/json" \ -d "{\"start\": \"${EXAM_START}\", \"end\": \"${EXAM_END}\"}" \ "https://api.arbitex.ai/api/v1/admin/audit/verify" \ > "$EVIDENCE_DIR/cc7-chain-verification.json"
# CC8 — Admin config changescurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?category=admin&start=${EXAM_START}&end=${EXAM_END}&limit=5000" \ > "$EVIDENCE_DIR/cc8-config-changes.json"
# CC6/CC7 — Compliance bundle exportcurl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/compliance-bundles-export/" \ > "$EVIDENCE_DIR/compliance-bundles.json"
# Override audit (policy exceptions)curl -H "Authorization: Bearer $ADMIN_TOKEN" \ "https://api.arbitex.ai/api/v1/admin/audit-logs?action=allow_with_override&start=${EXAM_START}&end=${EXAM_END}&limit=5000" \ > "$EVIDENCE_DIR/policy-overrides.json"
echo "Evidence pack assembled in ${EVIDENCE_DIR}/"ls -la "$EVIDENCE_DIR/"TSC Cross-Reference
Section titled “TSC Cross-Reference”| TSC Domain | Label | Arbitex Evidence |
|---|---|---|
| CC6 | Logical and Physical Access Controls | access_controls section; auth event exports; DLP enforcement exports; compliance bundle export |
| CC7 | System Operations and Detection | audit_trail section; HMAC chain verification result; SIEM OCSF exports |
| CC8 | Change Management | change_management section; admin audit log for config change events |
| A1 | Availability | availability section; circuit breaker states; outpost heartbeat logs |
For sub-domain mapping and OCSF field references used in SIEM-based evidence, see Compliance Audit Evidence Guide. That guide covers framework-specific evidence filters for PCI-DSS, HIPAA, SOX, and the OCSF event schema in full detail.
Pre-Examination Checklist
Section titled “Pre-Examination Checklist”Before presenting evidence to SOC 2 examiners:
- Run
GET /api/v1/admin/compliance/soc2-report?days=365and confirmhmac_chain_status: "verified"andgap_count: 0 - Run
POST /api/v1/admin/audit/verifyfor the full examination period and confirmvalid: true - Export compliance bundle definitions and confirm required bundles show
"enabled": true - Confirm audit log retention covers the full examination period (90-day hot; archive via SIEM for longer periods)
- Review
role_distributionin the report — document rationale for each ADMIN-role user - Review
mfa_adoption_pct— if below 100%, document exemptions and compensating controls - Check
circuit_breakers— any"open"state at the time of the report indicates a live service degradation - Confirm
_SUPPORT_MTLS_BYPASSis not set in the production environment - Confirm
AUDIT_HMAC_KEYis set (chain status will show"disabled"if it is not)
See Also
Section titled “See Also”- Account Security Monitor — user self-service event history, active sessions, and admin session management
- Compliance Audit Evidence Guide — OCSF format, framework-specific evidence packs, and examiner talking points
- Credential Intelligence — CredInt architecture, breach corpus, frequency buckets, and routing logic
- MFA Enforcement — MFA policy configuration and enforcement levels
- SIEM Integration — OCSF connector configuration for Splunk, Sentinel, Elastic, and others
- Audit Log Management — full audit log search, pagination, and export reference