AI privacy and control
Run AI on your own hardware, control what may leave, and audit every AI action.
Updated
Most AI features are a data-export decision in disguise: your documents and prompts go to someone else's servers, and you find out afterwards. Railbase is built the other way round. The AI engine is part of the core binary on your server, it can run a model on your own hardware, and every route to an outside model is one you switch on deliberately.
The AI can run entirely on your hardware
Two ways to run a model locally, both keeping the text on the machine.
A local model server. Point Railbase at any OpenAI-compatible server on
loopback or your LAN — Ollama, LM Studio, a llama.cpp server, or the bridge to
a Mac's built-in foundation model. In Settings → AI → LLM Railbase probes
well-known loopback ports and lists what it found under Detected local
endpoints, so a running server is one click to connect. Nothing is connected
for you. This is a provider of kind local.endpoint, and its address is
pinned: it must resolve to a loopback or private address. One pointed at a public
address is refused, so it can never quietly become a route to a cloud model.
A model file executed inside the core process. Railbase can load a native
inference library and a .gguf weights file and run generation in its own
process — no subprocess, no port, no network call at all. The Local models
(local.native) card on the same screen installs both; the download streams into
a content-addressed store and is checked fail-closed against the SHA-256 you
enter from the signed catalog entry, so a tampered or truncated file leaves
nothing installed — and an install with no hash is refused outright. The This
host card shows this machine's CPU, RAM, free disk and GPU, and the model tier
it can run.
Note
The in-process runtime ships verified for macOS; on other platforms — and anywhere the library or weights aren't installed — it reports itself unavailable as a typed, degraded state the AI screen surfaces, rather than failing calls hard. A local model server is the portable local option today.
Secrets are stripped on both boundaries
Redaction runs at two separate places, and neither depends on the other.
Before anything is written to the audit trail. The AI audit writer redacts
at the single point where rows are created, whatever a caller passes in: it masks
Authorization-style values, vendor API-key shapes (sk-…, AKIA…, ghp_…,
xox…), JWTs, passwords inside URLs, and any key: value fragment whose key
counts as sensitive. Structured context attached to a row is walked by key name
and blanked the same way. The reason is blunt: the trail is append-only and
tamper-evident, so a secret written into it could never be scrubbed out later.
Before anything is sent to an external provider. The same text-level filter runs on the prompt and system text on the way out to a cloud model, so what the audit row stores is what actually left your process. Local providers get the text unchanged — a loopback endpoint and the in-process model never cross a trust boundary, so their input passes through byte-identical.
On top of that, a payload carries a sensitivity class. restricted never
goes to an external model; confidential goes only if you raised the ceiling. An
unrecognised or unresolved class is blocked, not treated as ordinary data — a
false positive costs you a call, a false negative costs you a leak.
What you control as an operator
Settings → AI → Policies holds the switches:
| Control | Setting key | Effect |
|---|---|---|
| External models allowed | ai.external.allowed |
Off means no cloud provider is used at all |
| Sensitivity ceiling | ai.external.sensitivity_ceiling |
Highest class permitted to leave: normal, confidential, restricted |
| Default provider | ai.provider.default |
Which configured provider generation routes to |
A provider you connect with your own API key is write-only in the admin: after saving, the screen shows only whether a key is set plus a short hint, never the key. There is no reveal and no in-place edit, so rotating a key means deleting the provider entry and connecting again.
The AI audit trail is tamper-evident
Every AI call lands in an append-only _ai_audit row: who acted, which provider
and model, capability, token counts, the policy decision, the outcome, and the
redacted prompt and output. Each row also stores a SHA-256 of the raw prompt and
output — a correlator, so a row ties back to what was really sent without the raw
text ever being persisted.
The rows form a hash chain: each commits to the previous row's hash and to
its own contents. Settings → AI → Audit has a Verify integrity action
that recomputes the whole chain and reports how many rows verified, or marks the
chain broken — the response names the first row that fails to recompute. Editing
a row, deleting one, or reordering them all break verification. The same screen
exports the filtered stream as an .xlsx workbook.
Retention is set with ai.audit.retention_days (empty or 0 keeps everything).
When it's on, a nightly job archives before it prunes: the old prefix of the
chain is sealed into a JSON bundle under <data dir>/ai_audit_archive, and only
then are those rows removed. Each bundle records its own chain seed and anchor,
so it stays independently verifiable offline, and the live chain still verifies
from where the prune left off. If the archive write fails nothing is deleted — and if
the chain is already broken the prune refuses to run, so pruning can never hide
tampering.
Note
This is the AI-specific trail. The core's general audit log is a separate append-only hash chain with its own verification — see How Railbase is secured.
The AI acts under its own account
The AI is not a service key borrowed from a person. Each company gets exactly one
platform AI principal, stored in _ai_agents and created the first time that
agent is used in that company. When the agent executes an action it goes through
the ordinary request pipeline under its own identity — the same authentication,
role checks and audit a human request gets, resolving to the AI, not to a user.
Audit rows carry the actor type ai, so an AI action is never indistinguishable
from a person's after the fact. Disabling the agent pauses execution without
deleting the principal or its permission grants, so attribution survives.
A module declares whether it may use an external model
A business module declares its AI needs in its bundle manifest, and the platform enforces the declaration rather than trusting it:
- The declared
requireslist is a closed set — calling an AI capability outside it is denied. external_alloweddefaults to false. When a module has not declared external use, cloud providers are removed from the candidate list for that module's calls — not warned about and allowed. Local backends still work.- If such a module runs where the only configured provider is a cloud one, it gets a clear policy denial: add a local provider, or the module must declare the need.
The only route to an external model is the AI engine
A module cannot bypass the redaction and policy layer by making its own HTTP
call. The core's outbound-HTTP capability for modules refuses foundation-model
API hosts outright — a built-in list covering the major vendors — and re-checks
on every redirect hop, so a harmless-looking URL cannot redirect into one. You
can extend that list for your own environment with ai.egress.deny_hosts
(Settings → Network access), which applies live. The block is one-sided by
design: it constrains a module's raw fetch, not the AI engine's own client — the
engine is the sanctioned channel precisely because it redacts and applies policy
first.
FAQ
Can I use AI with no internet access at all?
Yes, if you run a local model — a local server or the in-process model file.
Turning off ai.external.allowed means no cloud provider is used at all.
If I use a cloud provider, what exactly leaves?
The prompt and system text after secret redaction, to the provider you configured
with your own key, and only if the payload's sensitivity class clears your
ceiling — restricted never does. The audit row stores that same redacted text,
so you can read afterwards what left. railbase.app is never in the AI path — it
handles the catalog, licences and signed artifacts only.
Can someone quietly delete an AI action from the log?
Not without it showing. Deleting, editing or reordering a row breaks the hash chain, and Verify integrity fails, naming the first row that no longer recomputes.