Platform Security
AINative provides multiple layers of security: two-factor authentication for user accounts, encryption at rest for sensitive data, VPC-isolated inference for regulated industries, and automated security scanning for agent configurations.
Two-Factor Authentication
SMS-based 2FA with backup codes, rate limiting, and account lockout protection. Codes are delivered via Twilio and stored as bcrypt hashes -- never in plain text.
Security Properties
| Property | Value |
|---|---|
| Code length | 6 digits |
| Code expiry | 5 minutes (configurable) |
| Max attempts per code | 3 |
| Max codes per hour | 5 |
| Lockout threshold | 5 consecutive failed attempts |
| Lockout duration | 30 minutes |
| Backup codes | 10 alphanumeric codes (10 characters each) |
| Phone encryption | Fernet (AES-128-CBC) |
| Code storage | bcrypt hash |
Weak patterns such as 123456, 111111, and sequential digits are rejected during code generation.
Enable 2FA
Validates the phone number via Twilio, encrypts it at rest, generates 10 backup codes, and activates 2FA on the account.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/enable \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone_number": "+14155551234"}'
Response (200):
{
"success": true,
"message": "Two-factor authentication enabled successfully",
"backup_codes": [
"A3K9M2X7P1",
"B8N4R6Y2Q5",
"C1L7T3W9S4",
"D5J2V8F6E3",
"G9H1Z4U7K8",
"H6P3X5M2R1",
"J4Q8S1N7L6",
"K2W5Y9D3T8",
"L7E4F6A1V2",
"M3U9G5B8C7"
]
}
Save the backup codes immediately. They are shown only once and stored as bcrypt hashes after this response.
Generate and Send Code
Generates a 6-digit code and sends it to the user's registered phone via SMS.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/send \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"phone_number": "+14155551234"}'
Response (200):
{
"success": true,
"message": "Verification code sent successfully",
"expires_at": "2026-06-01T12:05:00",
"message_sid": "SM1234567890abcdef"
}
Rate limited to 5 codes per hour per user. Previous unverified codes are invalidated when a new code is generated.
Verify Code
Validates the 6-digit code against the stored bcrypt hash.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/verify \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"code": "482917"}'
Response (200) -- success:
{
"success": true,
"message": "Verification code is valid",
"locked": false
}
Response (200) -- failure:
{
"success": false,
"error": "Invalid verification code. 2 attempts remaining.",
"locked": false
}
Response (200) -- locked:
{
"success": false,
"error": "Account locked for 30 minutes due to too many failed attempts",
"locked": true
}
Verify Backup Code
Use a backup code when SMS is unavailable. Each code can only be used once.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/backup \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{"code": "A3K9M2X7P1"}'
A successful verification consumes the backup code and resets the failed attempt counter.
Regenerate Backup Codes
Generates a fresh set of 10 backup codes, replacing any existing codes.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/backup/regenerate \
-H "Authorization: Bearer $TOKEN"
Get 2FA Status
Returns whether 2FA is enabled, a masked phone number, remaining backup codes, and lockout status.
curl https://api.ainative.studio/api/v1/auth/2fa/status \
-H "Authorization: Bearer $TOKEN"
Response (200):
{
"enabled": true,
"phone_number": "****1234",
"verified_at": "2026-05-15T10:30:00",
"backup_codes_remaining": 8,
"is_locked": false,
"lockout_until": null
}
Disable 2FA
Disables 2FA, clears backup codes, and invalidates all pending verification codes. The caller must verify the user's current password before calling this endpoint.
curl -X POST https://api.ainative.studio/api/v1/auth/2fa/disable \
-H "Authorization: Bearer $TOKEN"
Encryption Service
Sensitive data such as API tokens and credentials are encrypted at rest using Fernet symmetric encryption (AES-256) with PBKDF2 key derivation.
How It Works
- The
ENCRYPTION_SECRETenvironment variable provides the master secret (minimum 32 characters). - A deterministic encryption key is derived using PBKDF2-HMAC-SHA256 with 100,000 iterations.
- Data is encrypted with Fernet, producing a base64-encoded ciphertext safe for database storage.
- Decryption reverses the process using the same derived key.
Encrypted Data Types
| Data Type | Detection Method |
|---|---|
| GitHub tokens | Checks for ghp_, gho_, ghu_, ghs_, ghr_ prefixes to distinguish plain text from encrypted |
| API keys | Generic encryption via encrypt_api_key() / decrypt_api_key() |
Usage
from app.services.encryption_service import encrypt_github_token, decrypt_github_token
# Encrypt before storing
encrypted = encrypt_github_token("ghp_abc123...")
# Decrypt when needed
plain = decrypt_github_token(encrypted)
The is_github_token_encrypted() helper detects whether a stored value is already encrypted, preventing double-encryption.
VPC Inference
For regulated industries (finance, healthcare, government), AINative supports deploying the inference stack inside a customer's own cloud account via Helm chart. The platform manages licensing and usage metering.
Base path: /api/v1/public/vpc
License Key Format
License keys follow the format vpc_<base62(32 chars)>. The raw key is shown once at issuance. Only a SHA-256 hash is stored in the database.
POST /vpc/license/issue (Admin Only)
Issue a new VPC license key for a customer organization.
curl -X POST https://api.ainative.studio/api/v1/public/vpc/license/issue \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"org_id": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2027-06-01T00:00:00Z",
"allowed_replicas": 5,
"support_tier": "enterprise",
"notes": "ACME Corp annual license"
}'
Response (200):
{
"license_key": "vpc_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6",
"license_id": "uuid",
"org_id": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2027-06-01T00:00:00",
"allowed_replicas": 5,
"support_tier": "enterprise"
}
| Field | Description |
|---|---|
allowed_replicas | Maximum number of inference replicas (1--1000) |
support_tier | One of standard, enterprise, platinum |
POST /vpc/license/validate
Called by the customer-deployed Helm chart on startup and every 24 hours. No bearer token required -- the license key itself is the credential.
curl -X POST https://api.ainative.studio/api/v1/public/vpc/license/validate \
-H "Content-Type: application/json" \
-d '{
"license_key": "vpc_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6",
"deployment_id": "prod-us-east-1",
"hostname": "inference-0.acme.internal",
"version": "1.2.0"
}'
Response (200) -- valid:
{
"valid": true,
"org_id": "550e8400-e29b-41d4-a716-446655440000",
"expires_at": "2027-06-01T00:00:00",
"allowed_replicas": 5,
"support_tier": "enterprise",
"message": "OK"
}
Response (200) -- invalid:
{
"valid": false,
"message": "License has expired"
}
Possible rejection reasons: License key not found, License has been revoked, License is suspended -- contact support, License has expired.
POST /vpc/telemetry
Phone-home telemetry from customer VPC deployments, called hourly. Used for metered billing. No bearer token -- license key is the credential.
curl -X POST https://api.ainative.studio/api/v1/public/vpc/telemetry \
-H "Content-Type: application/json" \
-d '{
"license_key": "vpc_a1B2c3D4e5F6g7H8i9J0k1L2m3N4o5P6",
"deployment_id": "prod-us-east-1",
"period_start": "2026-06-01T11:00:00Z",
"period_end": "2026-06-01T12:00:00Z",
"input_tokens": 150000,
"output_tokens": 45000,
"request_count": 1200,
"model": "llama-3.1-70b",
"hostname": "inference-0.acme.internal"
}'
Response (200):
{
"received": true,
"event_id": "uuid"
}
Returns 403 if the license key is invalid or expired.
GET /vpc/license/{license_id} (Admin Only)
Fetch license details and aggregated telemetry event count.
curl https://api.ainative.studio/api/v1/public/vpc/license/LICENSE_UUID \
-H "Authorization: Bearer $ADMIN_TOKEN"
Response (200):
{
"license_id": "uuid",
"org_id": "550e8400-e29b-41d4-a716-446655440000",
"status": "active",
"expires_at": "2027-06-01T00:00:00",
"allowed_replicas": 5,
"support_tier": "enterprise",
"issued_at": "2026-06-01T00:00:00",
"notes": "ACME Corp annual license",
"last_validated_at": "2026-06-01T12:00:00",
"total_events": 8472
}
Security Scanning
Automated scanning of Claude Code settings, MCP server configurations, and skill files for security vulnerabilities. Inspired by the AgentShield pattern.
Base path: /api/v1/public/security/scan
POST /scan
Submit a file for security analysis. Returns structured findings with severity levels and a 0--100 security score.
curl -X POST https://api.ainative.studio/api/v1/public/security/scan \
-H "Authorization: Bearer $TOKEN" \
-H "Content-Type: application/json" \
-d '{
"type": "settings",
"content": "{\"permissions\": {\"deny\": []}}"
}'
Scan Types:
| Type | What It Checks |
|---|---|
settings | Claude Code settings.json -- deny rules, hook commands |
mcp_config | MCP server config -- unknown servers, hardcoded credentials |
skill | Skill/prompt files -- injection patterns, competitor references |
Response (200):
{
"ok": true,
"scan_id": "uuid",
"type": "settings",
"threats": [
{
"severity": "critical",
"category": "missing_deny_rule",
"description": "SSH key access not blocked in deny rules",
"recommendation": "Add Read(~/.ssh/**) to deny rules",
"line": null
},
{
"severity": "critical",
"category": "missing_deny_rule",
"description": "AWS credential access not blocked",
"recommendation": "Add Read(~/.aws/**) to deny rules",
"line": null
}
],
"score": 35,
"passed": false,
"summary": {
"critical": 2,
"high": 1,
"medium": 0,
"low": 0
},
"scanned_at": "2026-06-01T12:00:00Z"
}
Scoring
The security score starts at 100 and is reduced by severity-weighted penalties:
| Severity | Penalty |
|---|---|
| Critical | -25 points |
| High | -15 points |
| Medium | -5 points |
| Low | -2 points |
A score of 70 or above is considered passed. Below 70 is failed.
Settings Scan Checks
The settings scanner validates:
- Deny rules -- checks that
~/.ssh/**,~/.aws/**, andcurl * | bashpatterns are blocked - Hook commands -- flags hooks that make outbound network calls via
curl,wget,nc, ornetcat
Deny rules are normalized (whitespace-collapsed) and support both string and object formats.
MCP Config Scan Checks
The MCP config scanner validates:
- Unknown servers -- flags any MCP server not in the vetted allowlist (
ainative-zerodb-memory,sequential-thinking,context7,filesystem,google-analytics,google-ads,railway,zerodb-memory,ainative-gtm) - Credential exposure -- detects hardcoded values in
envkeys containingsecret,key,token, orpassword(values starting with$are treated as safe env var references)
Skill Scan Checks
The skill scanner detects:
- Injection patterns -- instruction overrides, unicode homoglyph attacks, XML role injection, Llama-style injection markers
- Competitor references -- mentions of Supabase, NeonDB, PlanetScale, or DevFleet
Rate limited to 100 scans per hour per API key (enforced at the Kong gateway).
Environment Variables
| Variable | Service | Purpose |
|---|---|---|
TWO_FA_ENCRYPTION_KEY | 2FA | Fernet key for phone number encryption |
TWO_FA_CODE_EXPIRY_MINUTES | 2FA | Code lifetime (default: 5) |
TWO_FA_MAX_ATTEMPTS | 2FA | Max verification attempts per code (default: 3) |
TWO_FA_LOCKOUT_MINUTES | 2FA | Lockout duration after failed attempts (default: 30) |
ENCRYPTION_SECRET | Encryption | Master secret for PBKDF2 key derivation (32+ chars) |
STRIPE_SECRET_KEY | Payments | Stripe API secret key |
STRIPE_WEBHOOK_SECRET | Payments | Webhook signature verification |