BUILT TO SURVIVE AN AUDIT.
Not a compliance checklist bolted on after the fact. The design constraint from day one was: prove it to an auditor. Every call traced, every secret isolated, every action enforced.
Air-Gapped Deployment
systemprompt.io ships as a single Rust binary that is its own token issuer and validator. It runs on your infrastructure against a local Postgres database. The binary includes the web server, API layer, job scheduler, AI orchestration, and admin dashboard in one process.
This is not a SaaS product with an "on-prem option." The runtime depends on Postgres and nothing else. The health endpoint validates connectivity by running SELECT 1 against the local database, with no callbacks to a vendor service. Outbound traffic only leaves the process when an operator explicitly configures an AI provider, and the route is whatever proxy you point it at.
Plug your existing IdP in and the binary handles the rest. JWTs are minted by JwtService using HS256 and validated locally by AuthValidationService, so every action is tied back to the authenticated subject without a round trip to an external auth server.
- Single Binary, Own Token Issuer — JwtService generates HS256 tokens with SessionGenerator managing per-user sessions. No external auth service required. Plug your IdP in and go.
- No Implicit Outbound Calls — Health, discovery, and auth code paths talk only to the local Postgres. AI provider connections are opt-in through profile config and route through whatever proxy you specify.
- Cryptographic User Tracing — Tokens are embedded in data ingestion. Every action is cryptographically tied to the authenticated user across the entire system.
- health.rs Health check with local Postgres: handle_health validates DB connectivity, no external calls
- discovery.rs API discovery endpoints: self-contained routing for OAuth, MCP, agents, and core services
- jwt/mod.rs JwtService: own token issuer using HS256 with jsonwebtoken crate, no external auth service
- security.rs SecurityConfig: jwt_issuer, access/refresh token expiration, audiences configured locally
- session/generator.rs SessionGenerator: issues JWT tokens with permissions, roles, and rate_limit_tier embedded
- auth/validation.rs AuthValidationService: validates tokens locally with HS256 decoding, no external IdP call
Secrets Never Touch Inference
With agents calling tools, secrets end up flowing through inference endpoints. API keys, database credentials, and tokens sitting in plain text in prompts. Every credential that enters a prompt is one prompt-injection away from exfiltration.
systemprompt.io abstracts secrets through MCP services. The agent calls the tool, McpToolHandler injects the credential server-side from the request context, and the model never sees it. PluginVariableDef marks each variable with a secret: bool flag so secret-flagged values are filtered out of anything the model can read.
Secret material is held in Secrets with provider isolation and a minimum 32-byte JWT secret enforced by Secrets::validate(). Source resolution is configurable via SecretsConfig (file or environment) and SecretsValidationMode (Strict, Warn, Skip). Master values stay in environment variables, never in the database.
- Server-Side Credential Injection — McpToolHandler executes with RequestContext server-side. PluginVariableDef marks secrets with
secret: bool. The LLM never sees API keys, database passwords, or tokens. - Provider-Isolated Secret Material — Secrets holds per-provider credentials in distinct fields with custom_env_vars() resolution. A leak in one provider context cannot exfiltrate another.
- Validated at Load — Secrets::validate() enforces JWT_SECRET_MIN_LENGTH of 32 bytes. SecretsConfig selects file or env source, SecretsValidationMode picks Strict, Warn, or Skip behavior at boot.
- plugin.rs PluginVariableDef with secret: bool flag, marking which variables require server-side injection
- secrets.rs Secrets struct: JWT_SECRET_MIN_LENGTH of 32 bytes, per-provider key isolation, custom_env_vars()
- profile/secrets.rs SecretsConfig: file or env source, SecretsValidationMode (Strict, Warn, Skip)
- tool.rs McpToolHandler: server-side tool execution with RequestContext, secrets never reach inference
- rbac.rs enforce_rbac_from_registry: validates JWT before tool execution, secrets stay server-side
- scanner.rs ScannerDetector: blocks scanner paths (.env, .git, .sql) and known attack user-agents
Identity-Bound, Replayable Audit Trails
Every tool call in the audit trail is tied to the authenticated user, timestamped, and replayable. This is non-negotiable. When an auditor asks "who accessed what, and when," the answer is a database query, not a guess.
Tokens issued by the binary are embedded in the data ingestion layer. This means every action, every tool invocation, every secret access is cryptographically traceable to the authenticated user throughout the entire system.
Records are written to the logs table defined in crates/infra/logging/schema/log.sql. TraceQueryService exposes 25+ query methods for the admin dashboard and SIEM export. Retention is policy-driven through RetentionConfig, with tiered cleanup from one day at debug to ninety days at error.
- Identity-Bound Tool Calls — LogEntry carries user_id, session_id, trace_id, and context_id on every record. JwtClaims embed sub, jti, and scope. No anonymous actions, no shared service accounts hiding who did what.
- Replayable Events — TraceQueryService.get_all_trace_data() returns log events, AI requests, MCP executions, and execution steps in a single query. Full replay context for every action.
- Queryable and Exportable — 25+ query methods on TraceQueryService: search_logs, list_tool_executions, count_logs_by_level, find_ai_request_for_audit. Export to CSV or JSON for your SIEM.
- log_entry.rs LogEntry: user_id, session_id, trace_id, context_id, client_id on every audit record
- audit_queries.rs find_ai_request_for_audit: lookup by request_id, task_id, or trace_id with linked MCP calls
- trace/service.rs TraceQueryService: 25+ query methods including search_logs, list_tool_executions, count_logs_by_level
- claims.rs JwtClaims: sub, session_id, jti, scope, roles embedded in every token for identity binding
- retention/policies.rs RetentionConfig: debug 1 day, info 7 days, warnings 30 days, errors 90 days with vacuum
- log.sql logs table: base schema for the identity-bound audit records written by the logging crate
Policy-as-Code on PreToolUseHooks
Every tool invocation is a policy decision point, not an afterthought. HookEvent::PreToolUse fires before McpToolHandler::handle ever reaches inference. A denying hook short-circuits the call. Security teams ship rules without coordinating a product release.
Hooks are loaded from DiskHookConfig as YAML, with matcher patterns, category (System or Custom), and visible_to scoping. HookAction dispatches to a Command, a Prompt, or a sub-Agent with a configurable timeout. Block unauthorized data access, enforce data classification boundaries, require approval for sensitive operations.
Policies are code, not configuration toggles. Version them, review them, test them, deploy them through your existing CI/CD pipeline. When the CISO asks "how do we prevent agents from accessing production databases," the answer is a PreToolUse hook that runs before every tool call.
- Pre-Execution Enforcement — HookEvent::PreToolUse fires before tool execution. HookAction supports Command, Prompt, and Agent types with configurable timeout. Abort before anything hits inference.
- Independent Security Rules — DiskHookConfig loads from YAML with matcher patterns, category (System/Custom), and visible_to scoping. Security teams ship policy rules independently of product releases.
- Hooks Wired Per Plugin — PluginConfig.hooks embeds a HookEventsConfig per plugin and runs validate() at load. Policy attaches to the plugins it is meant to govern, not as a global afterthought.
- hooks.rs HookEvent enum: 10 variants including PreToolUse, PostToolUse, SubagentStart, SubagentStop
- hooks.rs (HookEventsConfig) HookEventsConfig: per-event matcher arrays with HookAction (Command, Prompt, Agent types)
- hooks.rs (DiskHookConfig) DiskHookConfig: matcher patterns, async flag, category (System/Custom), visible_to scoping
- plugin.rs PluginConfig.hooks: HookEventsConfig embedded per plugin with validate() enforcement
- rbac.rs enforce_rbac_from_registry: hooks run after RBAC validation, before tool execution
- tool.rs McpToolHandler trait: handle() receives RequestContext with authenticated user for hook evaluation
Data-Domain Scoping
Skills, plugins, and MCP servers are scoped by role and department. Finance sees finance tools. Engineering sees engineering tools. Down to which MCP servers are even visible to a given user.
This is not just RBAC at the API level. The entire tool surface changes based on who is authenticated. A finance-analyst sees a completely different set of capabilities than an engineering-lead. The tools that do not apply to your role do not exist in your session.
Access control is enforced in middleware on every request. A user cannot access another user's secrets, modify another user's skills, or view another user's analytics, regardless of how the request is constructed. Entity ownership is enforced at the database query level.
- Role + Department Scoping — Permission.implies() enforces hierarchy: Admin(100), User(50), Service(40), A2a(30), Mcp(20), Anonymous(10). PluginComponentRef scopes skills with include/exclude lists per role.
- Invisible by Default — enforce_rbac_from_registry checks OAuth scopes per MCP server before tool listing. Tools outside your scope do not exist in your session. Reduced attack surface by design.
- Middleware Enforcement — validate_scopes_for_permissions runs in RBAC middleware before any application code. BaseRoles.anonymous() grants only users.read. No bypass, no exceptions.
- rbac.rs enforce_rbac_from_registry: validates OAuth scopes per MCP server, invisible tools by default
- permission.rs Permission::implies() with hierarchy_level enforcement: Admin(100), User(50), Mcp(20), Anonymous(10)
- enums.rs UserType with rate_tier() mapping: Admin, User, A2a, Mcp, Service, Anon each get distinct access
- plugin.rs PluginComponentRef: source, filter, include/exclude lists for per-role skill and agent scoping
- roles.rs BaseRoles: admin wildcard permissions, anonymous limited to users.read only
- claims.rs JwtClaims: scope, roles, user_type embedded in token; has_role() and has_permission() checks
Full Data-Plane Control
Every call enters through the API layer and is evaluated before reaching application code. RateLimitsConfig defines 11 per-endpoint base rates (oauth 10/s, contexts 100/s, MCP 200/s) with six TierMultipliers keyed off the authenticated user's role. AuthValidationService decodes the token, checks issuer and audience, and rejects anything that does not match before a handler runs.
The codebase is Rust. The compiler rejects buffer overflows, use-after-free, double-free, data races, and null pointer dereferences before the binary links. These are not classes you mitigate at runtime with sanitizers; they fail to build.
The build artifact is a single statically linked binary pinned by Cargo.lock. The same source produces the same binary. ScannerDetector is compiled in, blocking known scanner paths and 20+ malicious user-agents at the edge of the request lifecycle. What you audit is what you deploy.
- Policy Engine on Every Call — RateLimitsConfig defines 11 per-endpoint base rates (oauth 10/s, contexts 100/s, MCP 200/s) with six TierMultipliers. Every call evaluated structurally, not retroactively.
- Rust Memory Safety — The compiler prevents buffer overflows, use-after-free, data races, and null dereferences at compile time. Not with sanitizers. At compile time.
- Cargo Audit and Reproducible Builds — ScannerDetector blocks known attack paths (.env, .git, .sql, wp-admin) and 20+ scanner user-agents. Cargo.lock pins every version. The same source always produces the same binary.
- profile/mod.rs Profile struct: security, rate_limits, secrets, database, server all configured as typed YAML
- rate_limits.rs RateLimitsConfig: 11 per-endpoint base rates with TierMultipliers (admin 10x, anon 0.5x)
- scanner.rs ScannerDetector: blocks .env/.git/.sql paths, known attack tools, outdated browsers, high velocity
- permission.rs Permission hierarchy: Admin(100) implies all lower tiers, compile-time enforcement via const fn
- secrets.rs Secrets::validate(): enforces JWT_SECRET_MIN_LENGTH of 32 bytes, provider isolation
- auth/validation.rs AuthValidationService: HS256 token validation with issuer and audience checks, three AuthModes
Compliance Framework Compatibility
systemprompt.io's architecture provides technical controls that map to requirements in major compliance frameworks. The library does not certify your organization, but it gives auditors concrete evidence of control implementation.
SOC 2: Air-gapped deployment supports CC6.1 (logical access controls). Secrets isolation maps to CC6.7 (data classification and protection). Identity-bound audit trails support CC7.2 (system monitoring). RBAC addresses CC6.3 (role-based access).
ISO 27001: Secrets management maps to A.10 (cryptography). Data-domain scoping addresses A.9 (access control policy). Audit trails support A.12.4 (logging and monitoring). Data retention aligns with A.18 (compliance with legal requirements).
HIPAA: Server-side credential injection supports the encryption addressable specification under the Security Rule. Data-domain scoping maps to the access control standard. Identity-bound audit trails support the audit controls standard.
OWASP Top 10 for Agentic Applications: The architecture is informed by OWASP's agentic AI risk taxonomy. RBAC with department scoping enforces least-privilege access. Secret scanning prevents credential leakage through inference. Identity-bound audit trails provide traceability for incident response. Some risks (hallucination detection, RAG poisoning) are outside the current scope and are documented transparently in our OWASP implementation guide.
- SOC 2 Type II — Technical controls for logical access, data protection, system monitoring, and role-based access. Map directly to Trust Services Criteria.
- ISO 27001 — Controls aligned with Annex A requirements for cryptography, access control, logging, monitoring, and legal compliance.
- HIPAA — Secrets isolation, access control, audit trails, and data retention address Security Rule addressable specifications.
- OWASP Top 10 for Agentic Applications — Governance pipeline addresses excessive agency, privilege escalation, credential leakage, and lack of traceability. These are the top risks identified by OWASP for agentic AI systems.
- retention/policies.rs RetentionConfig: tiered retention (debug 1d, info 7d, warn 30d, error 90d) with vacuum cleanup
- trace/service.rs TraceQueryService: full audit lineage from identity to tool execution to cost attribution
- log_entry.rs LogEntry: identity-bound audit records with user_id, session_id, trace_id on every event
- permission.rs Six permission tiers with hierarchy_level(), mapping to SOC 2 CC6.3 role-based access controls
- rbac.rs RBAC middleware: validates scopes per server, maps to SOC 2 CC6.1 logical access controls
- hooks.rs 10 lifecycle hooks: PreToolUse enforcement maps to OWASP excessive agency mitigation
- profile/secrets.rs SecretsValidationMode: Strict/Warn/Skip maps to SOC 2 CC6.7 data classification controls
Founder-led. Self-service first.
No sales team. No demo theatre. The template is free to evaluate — if it solves your problem, we talk.
Who we are
One founder, one binary, full IP ownership. Every line of Rust, every governance rule, every MCP integration — written in-house. Two years of building AI governance infrastructure from first principles. No venture capital dictating roadmap. No advisory board approving features.
How to engage
Evaluate
Clone the template from GitHub. Run it locally with Docker or compile from source. Full governance pipeline.
Talk
Once you have seen the governance pipeline running, book a meeting to discuss your specific requirements — technical implementation, enterprise licensing, or custom integrations.
Deploy
The binary and extension code run on your infrastructure. Perpetual licence, source-available under BSL-1.1, with support and update agreements tailored to your compliance requirements.