Skip to main content

Platform Security

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
Not currently enforced

This endpoint is not rate limited today. Kong (previously referenced here) has been fully retired in favor of APISIX, and no rate-limiting plugin for this route exists in the current APISIX config or in application code. Treat any rate-limiting behavior as unconfirmed until it is actually configured.

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