Skip to main content

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

PropertyValue
Code length6 digits
Code expiry5 minutes (configurable)
Max attempts per code3
Max codes per hour5
Lockout threshold5 consecutive failed attempts
Lockout duration30 minutes
Backup codes10 alphanumeric codes (10 characters each)
Phone encryptionFernet (AES-128-CBC)
Code storagebcrypt 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

  1. The ENCRYPTION_SECRET environment variable provides the master secret (minimum 32 characters).
  2. A deterministic encryption key is derived using PBKDF2-HMAC-SHA256 with 100,000 iterations.
  3. Data is encrypted with Fernet, producing a base64-encoded ciphertext safe for database storage.
  4. Decryption reverses the process using the same derived key.

Encrypted Data Types

Data TypeDetection Method
GitHub tokensChecks for ghp_, gho_, ghu_, ghs_, ghr_ prefixes to distinguish plain text from encrypted
API keysGeneric 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"
}
FieldDescription
allowed_replicasMaximum number of inference replicas (1--1000)
support_tierOne 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:

TypeWhat It Checks
settingsClaude Code settings.json -- deny rules, hook commands
mcp_configMCP server config -- unknown servers, hardcoded credentials
skillSkill/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:

SeverityPenalty
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/**, and curl * | bash patterns are blocked
  • Hook commands -- flags hooks that make outbound network calls via curl, wget, nc, or netcat

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 env keys containing secret, key, token, or password (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

VariableServicePurpose
TWO_FA_ENCRYPTION_KEY2FAFernet key for phone number encryption
TWO_FA_CODE_EXPIRY_MINUTES2FACode lifetime (default: 5)
TWO_FA_MAX_ATTEMPTS2FAMax verification attempts per code (default: 3)
TWO_FA_LOCKOUT_MINUTES2FALockout duration after failed attempts (default: 30)
ENCRYPTION_SECRETEncryptionMaster secret for PBKDF2 key derivation (32+ chars)
STRIPE_SECRET_KEYPaymentsStripe API secret key
STRIPE_WEBHOOK_SECRETPaymentsWebhook signature verification