Policy & Safety Hooks
SochDB provides policy enforcement for AI agent safety: pre-write validation, post-read filtering, rate limiting, and audit logging that keep agents inside defined boundaries.
The hook-based policy engine is a real, importable module in the Rust SDK
(sochdb::policy). The Node.js SDK ships a different, real module —
PolicyService — which is a namespace-scoped access-control (ACL/governance)
service rather than a write/read hook engine. In the Python SDK the
hook-based PolicyEngine shown in older docs is not an importable class; it
exists only as an example pattern (validate_user / redact_pii) in the
sochdb-python-examples repo. The Go SDK has no client-side policy engine;
policy on the Go side is reached through the server's gRPC PolicyService.
For server-side authentication, RBAC (Owner / Editor / Viewer), and
per-namespace policy enforced by the gRPC server, see the
Security guide.
Overview (Rust PolicyEngine)
The Rust PolicyEngine<C> wraps a connection and runs configurable hooks
around each operation:
| Trigger | Purpose | Use Case |
|---|---|---|
before_write | Validate before writing | Block system key modifications |
after_write | Post-write actions | Audit logging, notifications |
before_read | Pre-read access control | Permission checks |
after_read | Post-read filtering | Redact sensitive data |
before_delete | Validate before delete | Protect critical data |
after_delete | Post-delete actions | Cleanup, audit |
A handler receives a single &PolicyContext and returns a PolicyAction:
Allow, Deny, Modify(Vec<u8>), or Log. The engine applies the highest-
precedence outcome it sees, where Deny outranks Modify, which outranks
Allow; Log is a side effect.
Quick Start
- Rust
- TypeScript / Node.js
- Python (example pattern)
- Go
The Rust SDK (crate sochdb, v2.0.3) exposes the hook engine in sochdb::policy.
use sochdb::{Connection, prelude::*};
use sochdb::policy::{PolicyEngine, PolicyAction, PolicyContext};
let conn = Connection::open("./agent_data")?;
let policy = PolicyEngine::new(conn);
// Block writes to system keys when an agent_id is present
policy.before_write("system/*", |ctx: &PolicyContext| {
if ctx.agent_id.is_some() {
PolicyAction::Deny
} else {
PolicyAction::Allow
}
});
// Redact a value on read
policy.after_read("users/*/email", |_ctx: &PolicyContext| {
PolicyAction::Modify(b"[REDACTED]".to_vec())
});
// Use policy-wrapped operations (put/get/delete enforce the hooks)
let ctx = PolicyContext::new("write", b"users/alice")
.with_agent_id("agent_001");
policy.put(b"users/alice", b"data", Some(&ctx))?;
Each put / get / delete returns
Result<_, sochdb::policy::PolicyViolationError> and runs the registered hooks
before (and after) touching storage.
The Node.js SDK (@sochdb/sochdb, v0.5.3) ships PolicyService — a
namespace-scoped access-control service. It is rule-based (allow/deny by
principal, resource, and action), not a write/read callback engine.
import { EmbeddedDatabase, PolicyService } from '@sochdb/sochdb';
// EmbeddedDatabase.open() is synchronous in the Node SDK.
const db = EmbeddedDatabase.open('./agent_data');
const policy = new PolicyService(db, { enableAudit: true });
// Define a default-deny policy for a tenant namespace
await policy.createNamespacePolicy({
namespace: 'tenant_123',
defaultEffect: 'deny',
rules: [
{
id: 'read_only',
name: 'Read Only Access',
effect: 'allow',
principals: ['user:*'],
resources: ['collection:*'],
actions: ['read', 'search'],
},
],
});
// Evaluate a request
const decision = await policy.evaluate({
principal: 'user:alice',
action: 'read',
resource: 'collection:documents',
});
if (!decision.allowed) {
throw new Error(`Denied: ${decision.reason}`);
}
The pure-Python SDK (sochdb, v0.5.9) does not export a PolicyEngine.
The snippet below mirrors the demonstration in
sochdb-python-examples/graph_overlay_examples/policy_hooks_example.py
(validate_user / redact_pii). Copy the pattern into your own application
code; do not import it from the SDK.
# Example pattern (from sochdb-python-examples) — NOT a SochDB import.
from sochdb import Database
db = Database.open("./agent_data")
def validate_user(key: bytes, value: bytes, context: dict) -> bool:
"""Reject writes to system/* coming from an agent."""
if key.startswith(b"system/") and context.get("agent_id"):
return False
return True
def redact_pii(key: bytes, value: bytes, context: dict) -> bytes:
"""Replace sensitive values on read."""
if key.endswith(b"/email") and context.get("redact_pii"):
return b"[REDACTED]"
return value
# You wrap db.put / db.get yourself and call these helpers.
context = {"agent_id": "agent_001"}
if validate_user(b"users/alice", b"data", context):
db.put(b"users/alice", b"data")
For server-enforced policy from Python, use the gRPC server's PolicyService
via SochDBClient and the Security guide.
The Go SDK (github.com/sochdb/sochdb-go, v0.4.5) has no embedded
PolicyEngine. Policy is enforced server-side through the gRPC
PolicyService (RegisterPolicy, Evaluate, ListPolicies,
DeletePolicy), reachable via the generated gRPC client. Run the
sochdb-grpc-server with --auth and configure RBAC as described in the
Security guide.
Pattern Matching (Rust engine)
The Rust PolicyEngine compiles each pattern to an anchored regex: . is
escaped, * matches within a single path segment ([^/]*), and ** matches
across segments (.*).
| Pattern | Matches |
|---|---|
system/* | system/config, system/users |
users/*/email | users/alice/email, users/bob/email |
users/** | users/alice, users/alice/profile/photo |
*.json | config.json, data.json |
Rate Limiting (Rust engine)
Prevent runaway agents with token-bucket rate limiting. The signature is
add_rate_limit(operation: &str, max_per_minute: u32, scope: &str):
// Global limit: 1000 writes per minute
policy.add_rate_limit("write", 1000, "global");
// Per-agent limit: 100 writes per minute per agent
policy.add_rate_limit("write", 100, "agent_id");
// Per-session limit
policy.add_rate_limit("read", 500, "session_id");
Scope options:
global- shared limit across all operationsagent_id- separate limit per agentsession_id- separate limit per session- any custom key present in the
PolicyContext
Audit Logging (Rust engine)
Track agent operations for compliance and debugging. enable_audit() takes no
arguments; the ring buffer retains the most recent 10,000 entries:
// Enable audit logging
policy.enable_audit();
// Perform operations...
let ctx = PolicyContext::new("write", b"key").with_agent_id("agent_001");
policy.put(b"key", b"value", Some(&ctx))?;
// Get the most recent audit entries (newest last)
for entry in policy.get_audit_log(100) {
println!("{:?}", entry);
}
get_audit_log(limit) returns up to limit of the most recent AuditEntry
records. There is no built-in per-agent filter argument; filter the returned
slice yourself.
Built-in Policy Helpers (Rust engine)
sochdb::policy provides factory functions that return ready-made handler
closures. Call them (note the parentheses) and pass the result to a hook:
use sochdb::policy::{deny_all, allow_all, require_agent_id, redact_value};
// Deny all operations matching the pattern
policy.before_write("readonly/*", deny_all());
// Require an agent_id in the context
policy.before_write("agents/*", require_agent_id());
// Redact values on read
policy.after_read("secrets/*", redact_value(b"[REDACTED]".to_vec()));
Error Handling (Rust engine)
When a policy blocks an operation, the call returns
Err(PolicyViolationError):
use sochdb::policy::PolicyContext;
let ctx = PolicyContext::new("write", b"system/config").with_agent_id("rogue");
match policy.put(b"system/config", b"malicious", Some(&ctx)) {
Ok(()) => {}
Err(e) => {
eprintln!("Operation blocked: {e}");
// Log the security event
}
}
PolicyViolationError implements std::error::Error and Display
(it prints as PolicyViolation: <message>).
Node.js PolicyService
The Node SDK's PolicyService is rule-based access control plus grant
management, scoped per namespace. Rules carry an effect ('allow' or
'deny'), principals, resources, actions, optional conditions, and a
priority; the namespace's defaultEffect applies when no rule matches.
Rules and evaluation
import { EmbeddedDatabase, PolicyService } from '@sochdb/sochdb';
const db = EmbeddedDatabase.open('./agent_data');
const policy = new PolicyService(db, { enableAudit: true });
await policy.createNamespacePolicy({
namespace: 'tenant_123',
defaultEffect: 'deny',
rules: [],
});
// Add a rule after the fact
await policy.addRule('tenant_123', {
id: 'writers',
name: 'Writers may write documents',
effect: 'allow',
principals: ['user:alice', 'service:ingest'],
resources: ['collection:documents'],
actions: ['write', 'create'],
priority: 10,
});
const decision = await policy.evaluate({
principal: 'user:alice',
action: 'write',
resource: 'collection:documents',
});
// decision: { allowed, matchedRule?, reason?, evaluationTime }
Grants and permissions
For coarse-grained, principal-to-namespace access, use grants instead of (or
alongside) rules. Permissions are drawn from NamespacePermission: 'read',
'write', 'delete', 'admin', 'create_collection', 'delete_collection',
'search', 'manage_policy'.
await policy.grantAccess({
namespace: 'tenant_123',
principal: 'user:bob',
permissions: ['read', 'search'],
expiresAt: Date.now() + 86_400_000, // optional, 24h
});
const canSearch = await policy.hasPermission('tenant_123', 'user:bob', 'search');
// Inspect decisions (when enableAudit is on)
const log = await policy.getAuditLog();
PolicyService is an application-level ACL layer stored in the embedded
database (keys prefixed _policy:). It is independent from the gRPC server's
RBAC. To enforce auth and roles at the wire level, run sochdb-grpc-server
with --auth — see the Security guide.
Best Practices
- Defense in Depth: Combine multiple policies for layered security
- Audit Critical Operations: Enable audit logging for sensitive namespaces
- Rate Limit by Agent: Prevent any single agent from overwhelming the system
- Redact by Default: Apply redaction policies to PII fields
- Test Policies: Write unit tests for policy handlers
- Monitor Denials: Alert on unusual denial patterns
See Also
- Security Guide - Server-side auth, RBAC (
Owner/Editor/Viewer), and per-namespace policy - Tool Routing Guide - Route tools to specialized agents
- Python SDK Guide - Namespace isolation
- Deployment Guide - Production setup