Skip to main content

Building on AINative Securely

This guide covers the security primitives shipped in the Agent Security Sprint. Every section maps to a real API — copy the examples directly.


The Threat Model

AI agents sit at the intersection of three dangers — the lethal trifecta:

  1. Private data — agents read memories, user records, and org secrets.
  2. Untrusted content — agents process emails, web scrapes, uploaded files, and tool responses that may contain adversarial payloads.
  3. External communications — agents call APIs, send emails, and write to databases.

When these converge without controls, a single malicious document can exfiltrate private data over external channels.

ThreatDefense
Over-privileged credentialsAgent-scoped API keys with TTL
Cross-session memory contaminationNamespace isolation
Prompt injection via untrusted contentQuarantine API
Runaway long-running agentsHeartbeat + dead-man switch
Unauthorized high-risk actionsApproval gates
No visibility into what agents didAudit log

1. Credential Hygiene — Scoped API Keys

Your root ZERODB_API_KEY grants full access. Agent-scoped keys restrict each agent to only the permissions it needs, with an automatic expiry.

Create a key

POST /api/v1/auth/keys

FieldTypeRequiredDescription
namestringyesHuman-readable label (1–128 chars)
ttl_secondsintegernoSeconds until expiry. Omit for non-expiring.
scopesarrayyesPermissions: <service>:<permission>[:<namespace>]

Valid services: zerodb, inference, memory, file, agent, mcp

curl -X POST https://api.ainative.studio/api/v1/auth/keys \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"name": "scraper-agent-key",
"ttl_seconds": 3600,
"scopes": [
"zerodb:read:project/my-project",
"memory:write:session/run-42"
]
}'

The key field is returned once only. Store it in a secret manager — it cannot be recovered.

Revoke a key

curl -X DELETE https://api.ainative.studio/api/v1/auth/keys/{key_id} \
-H "Authorization: Bearer $ZERODB_API_KEY"

Key rotation workflow

  1. POST /api/v1/auth/keys → capture new key
  2. Update agent config to use new key
  3. Verify agent is healthy
  4. DELETE /api/v1/auth/keys/{old_key_id}

2. Memory Safety — Namespace Isolation

Why the default namespace is dangerous

Without isolation, every memory write lands in the global namespace — shared across all workflows. A prompt injection in an email could poison future agent behavior.

Namespace format

NamespaceWhen to use
globalTrusted, long-lived facts about the user or org
project:<project_id>Project-scoped isolation
session:<session_id>Ephemeral — disposable after the agent run

Disposable memory pattern

import uuid
from app.services.memory.zeromemory import ZeroMemory

session_ns = f"session:{uuid.uuid4()}"
memory = ZeroMemory(db)

try:
# Write to isolated session namespace
await memory.remember(
content=sanitized_document,
entity_id="scrape-job-88",
namespace=session_ns,
)
results = await agent.run(namespace=session_ns)

# Promote verified facts to global
for fact in results.verified_facts:
await memory.remember(content=fact, namespace="global")
finally:
# Always clean up — even if agent crashed
await memory.forget(namespace=session_ns)

3. Content Sanitization — The Quarantine API

Quarantine any content arriving from outside your control boundary before passing it to a privileged agent.

What gets stripped:

  • Zero-width and bidi Unicode (U+200B, U+202A–U+202E, etc.)
  • HTML comment injection (<!-- ... -->)
  • Script tags (<script>...</script>)
  • Base64 blobs (60+ char heuristic)
  • External links (when strip_links: true)

POST /api/v1/public/security/quarantine

FieldTypeRequiredDescription
contentstringyesRaw text to sanitize (max 500,000 chars)
content_typestringyespdf_extract, html, ocr, or markdown
strip_linksbooleannoStrip external links (default true)
curl -X POST https://api.ainative.studio/api/v1/public/security/quarantine \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"content": "<raw email or scraped HTML>",
"content_type": "html",
"strip_links": true
}'

Response fields:

FieldDescription
sanitizedCleaned text — pass this to your agent
threats_detectedArray: hidden_unicode, html_comment_injection, script_injection, base64_blob, external_links
stripped_countTotal items removed
safe_to_usefalse when script_injection or base64_blob found — human review recommended
warning

Never pass content with safe_to_use: false to a privileged agent context. Flag it for human review.

Integration flow

Untrusted content


POST /security/quarantine

├─ safe_to_use=false ──► Reject / escalate to human

└─ safe_to_use=true ──► Pass sanitized to agent
(use session namespace, not global)

4. Agent Oversight — Heartbeat and Approval Gates

Heartbeat: the dead-man switch

If an agent stops heartbeating within heartbeat_interval_seconds, the session transitions to stalled and supervisors are alerted.

1. Create a session

curl -X POST https://api.ainative.studio/api/v1/agent/sessions \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"user_id": "u_xyz",
"session_id": "run-88",
"heartbeat_interval_seconds": 30
}'

2. Send heartbeats (call at least once per heartbeat_interval_seconds)

curl -X POST https://api.ainative.studio/api/v1/agent/sessions/run-88/heartbeat \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id": "u_xyz"}'

Check the status field in the response — exit cleanly on terminated or quarantined.

3. Terminate

curl -X POST https://api.ainative.studio/api/v1/agent/sessions/run-88/terminate \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"user_id": "u_xyz", "quarantine": false}'

Set quarantine: true to preserve state for forensics.

Python heartbeat loop:

import threading, time, requests, os

def heartbeat_loop(session_id: str, user_id: str, interval: int = 25):
while True:
time.sleep(interval)
resp = requests.post(
f"https://api.ainative.studio/api/v1/agent/sessions/{session_id}/heartbeat",
headers={"Authorization": f"Bearer {os.environ['ZERODB_API_KEY']}"},
json={"user_id": user_id},
)
resp.raise_for_status()
if resp.json().get("status") in ("terminated", "quarantined"):
raise SystemExit(f"Session {session_id} killed by supervisor")

t = threading.Thread(target=heartbeat_loop, args=("run-88", "u_xyz"), daemon=True)
t.start()

Approval gates for high-risk actions

Before any destructive or irreversible action, pause and request human approval.

Request approval: POST /api/v1/sessions/{session_id}/approval-requests

FieldTypeDescription
actionstringMachine-readable ID, e.g. bash_egress
descriptionstringHuman-readable: what will happen
risk_levelstringlow, medium, high, or critical
curl -X POST https://api.ainative.studio/api/v1/sessions/run-88/approval-requests \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "send_email",
"description": "Send summary report to user@example.com",
"risk_level": "high"
}'

Requests start in pending status and auto-deny after 5 minutes.

List pending approvals: GET /api/v1/approvals

curl "https://api.ainative.studio/api/v1/approvals" \
-H "Authorization: Bearer $ZERODB_API_KEY"

Submit a decision: POST /api/v1/approvals/{approval_id}

curl -X POST https://api.ainative.studio/api/v1/approvals/apr_abc123 \
-H "Authorization: Bearer $ZERODB_API_KEY" \
-H "Content-Type: application/json" \
-d '{"decision": "approve"}'

decision must be "approve" or "deny".


5. Observability — Audit Logs

Every MCP tool call is recorded in mcp_request_logs with session_id, tool_name, risk_score, approval_decision, and input_hash. Data is retained for 90 days.

GET /api/v1/audit/tool-calls

ParameterDescription
session_idFilter by session
fromISO-8601 start (inclusive)
toISO-8601 end (inclusive)
min_risk_score0.0–1.0 threshold
tool_nameExact tool name
limit1–1000, default 100
offsetPagination
# High-risk calls in the last hour
curl "https://api.ainative.studio/api/v1/audit/tool-calls\
?from=2026-05-17T13:00:00Z&min_risk_score=0.7" \
-H "Authorization: Bearer $ZERODB_API_KEY"

Response record fields:

FieldDescription
timestampISO-8601 UTC
session_idMCP session identifier
toolTool name invoked
input_summaryEndpoint path used
outcomesuccess, client_error, or error
risk_score0.0–1.0 (null if unscored)
approval_decisionDecision if an approval gate was used

6. Defense in Depth Checklist

Before deploying any agent to production:

CheckDone?
Use a scoped API key (not root key) — POST /api/v1/auth/keys[ ]
Set TTL on all agent keys (ttl_seconds)[ ]
Use session:<id> namespace for untrusted workflows[ ]
Quarantine all external content before agent ingestion[ ]
Never pass content with safe_to_use: false to a privileged agent[ ]
Enable heartbeat for any long-running agent (>5 min)[ ]
Set approval gates for destructive or external actions[ ]
Monitor audit log for risk_score anomalies[ ]
Rotate agent keys on a schedule (weekly CI, daily high-risk)[ ]
Clean up session namespaces after agent runs[ ]