AI features
Source of truth:
docs/ai.md(synced at build time — edit the original, not this copy). Veltro doesn’t host a model. You bring your own: an OpenAI-compatible endpoint, Anthropic, or a local Ollama daemon. AI is off by default. Nothing reaches a model until an admin saves a provider in Settings > AI and turns AI on.
This page covers what leaves your install once you do, how secrets are masked, what gets audited and how to switch it all off. The engineering detail (provider contract, refusal order, per-product storage) is in AI governance.
Versions
Section titled “Versions”Settings > AI needs VectorFlow 0.25.0, Sigmatic 0.49.0 and Warden 0.17.0 or later.
Those are the first releases that take their AI settings from identity, and
every pin in versions.env is newer. Identity can’t reach a product on an older release: it shows
the product as not answering, and “Turn AI off” stays not settled and keeps
retrying it without ever turning AI off there. On those releases, set AI up and
switch it off on the product’s own Settings > AI page.
Off by default
Section titled “Off by default”Out of the box no product will call a model:
- VectorFlow stores AI as off (
aiEnableddefaults tofalse). - Sigmatic and Warden have no provider stored, so every AI feature refuses with
ai_not_configured. - The Settings > AI form leaves “Turn AI on when saved” unticked, so you can save a provider without turning anything on.
- Each product’s list of allowed data classes starts narrow:
pipelinesin VectorFlow, nothing in Sigmatic or Warden. A Sigmatic install upgraded from before the data-class picker keeps thealertsandeventsgrant it already had.
Settings > AI
Section titled “Settings > AI”Settings > AI is one form for every installed product, and only admins can open it.
Saving a provider
Section titled “Saving a provider”The form takes a provider (OpenAI-compatible, Anthropic or Ollama), a base URL
(optional for Anthropic), a model, an API key, and whether to turn AI on.
“Save to every product” sends those values to each installed product at
PUT /api/peer/v1/settings/ai as enabled, provider, base_url, model
and, if you typed one, api_key. Each product encrypts the key into its own
settings store and never returns it; the table on the page only says whether a
key is stored.
Leave the key field empty to keep the key each product already has. A product refuses that if you’ve changed the provider or base URL, so a stored key never goes to a host it wasn’t entered for.
Identity holds no AI key. It keeps one row per product in
veltro.ai_settings_writes: whether you wanted AI on or off, whether the
product confirmed, when, any error, and what the product reported back. That
table has no key column. Saving and switching are audited in identity as
authority.ai_settings.saved, authority.ai_settings.enabled and
authority.ai_settings.disabled.
The kill switch
Section titled “The kill switch”The “AI everywhere” card has “Turn AI off” and “Turn AI on” buttons and shows on, off or not settled.
“Turn AI off” sends {"enabled": false} to every installed product at once,
before it waits on any of them. Then it reads each product back. The switch
shows off only when every product has confirmed off. A product that didn’t
answer, or answered without applying it, shows as “turning off, retrying” with
the reason in the Problem column. Identity keeps retrying it: every 5 seconds
while the page is open, and in the background with a backoff from 5 seconds up
to 60, which also restarts when identity does. Only turning off is retried. A
failed save or turn-on isn’t replayed, because identity doesn’t keep the key.
While AI is off, every AI feature refuses before it calls the provider: Sigmatic
and Warden with ai_kill_switch, VectorFlow with ai_disabled. Each refusal
is audited. The provider, model and key stay stored, so turning AI back on
picks up where you left off.
What stays in each product
Section titled “What stays in each product”Each product’s own Settings > AI page keeps the choices that are about that product: which data classes its AI features may send, and in Warden the token, temperature and timeout limits. Identity’s form doesn’t change them.
What’s sent to the model
Section titled “What’s sent to the model”There are four data classes: alerts, events, cases and pipelines. A
feature that needs a class you haven’t allowed is refused with
ai_data_class_denied before anything is sent.
| Product | Feature | Data classes sent |
|---|---|---|
| VectorFlow | VRL writer, pipeline builder, pipeline proposals, cost optimizer, migration translator | pipelines |
| VectorFlow | VRL chat | pipelines, plus events when you include error context |
| VectorFlow | Pipeline debugger | pipelines, plus events when log lines are attached |
| VectorFlow | Schema field-mapping proposals | pipelines, events |
| Sigmatic | Rule drafting | none: your description and log source hint |
| Sigmatic | Rule explanation | none: the rule YAML you give it |
| Sigmatic | Alert summary, alert explanation, attack narrative summary | alerts |
| Sigmatic | Exception suggestions, field-mapping suggestions | events |
| Sigmatic | Plain-English Explore questions | events: the data source’s field names and the time range |
| Warden | Runbook draft | none: your description and optional starting YAML |
| Warden | Run explanation | alerts, cases |
| Warden | Case triage | cases, alerts, events |
| Warden | ai_agent runbook step |
the classes the step declares (cases if it declares none) |
Sigmatic’s and Warden’s connection test sends a fixed probe prompt and no data.
Redaction
Section titled “Redaction”Each product masks secrets in the prompt before it leaves. From VectorFlow
0.24.0, Sigmatic 0.49.0 and Warden 0.17.0 that’s the shared provider package:
redact_secrets in veltro-ai (Sigmatic, Warden) and redactSecrets in
@veltrosecurity/ai (VectorFlow). A match becomes [REDACTED_<KIND>] and any
label in front of it stays, so password=hunter2 goes out as
password=[REDACTED_CREDENTIAL]. It masks:
BearerandBasiccredentials, and the value of anAuthorizationorProxy-Authorizationheader under the common schemes (Digest, NTLM, Negotiate, AWS SigV4 and others)Signature=values from signed requests and presigned URLssk-keys (OpenAI, Anthropic), GoogleAIzakeys, GitHub tokens, Slackxoxtokens, and AWSAKIA/ASIAaccess key IDs- the value after
password,passwd,secret,token,api_key,api_token,credential,aws_secret_access_keyand similar keys, written askey=valueorkey: value - JWTs
Earlier releases, including the versions.env defaults above, carry their own
copy of the contract with a smaller table. It masks Bearer tokens, AWS
AKIA key IDs, JWTs, the value after api_key, token, secret,
password or passwd, and sk- keys whose body is only letters and digits.
Everything else in the list above, including an sk- key with a - or _ in
its body, goes out unmasked on those releases.
The same masking applies to error text written to the audit log.
Redaction matches patterns. A secret in any other shape goes to the model as is: a PEM private key block, say, or a password inside a connection-string URL. So does everything else in the data you allow, like hostnames, usernames and IP addresses. The data-class list is how you control that.
Sigmatic also fences untrusted input. Alert documents, example events and imported
rules sit between >>>BEGIN_UNTRUSTED_... and <<<END_UNTRUSTED_... markers,
with an instruction to treat what’s inside as data and never follow
instructions in it. VectorFlow and Warden prompts don’t fence their input yet.
Model output is a proposal. Drafted rules, VRL, pipelines and runbooks go through the product’s own validator and need a person to save them, and AI can’t close a case or skip an approval.
The audit record
Section titled “The audit record”Every call that reaches a provider writes one ai.invocation row to that
product’s own audit log (the AuditLog table in VectorFlow, the hash-chained
audit_log in Sigmatic and Warden). The prompt and the answer are never stored.
| Field | What it holds |
|---|---|
product |
vectorflow, sigmatic or warden |
feature |
Which feature made the call, such as copilot.summarize_alert |
model, provider |
What answered |
prompt_digest, output_digest |
sha256: of the redacted prompt and of the answer |
tokens |
prompt, completion and total |
duration_ms |
How long the call took |
actor |
Who asked, or null for scheduled work |
data_classes_used |
The data classes the prompt carried |
A call the provider rejected, or an answer the feature couldn’t use, is still
an ai.invocation, because the data left the install.
A call stopped before it reached a provider writes ai.refusal instead:
product, feature, refusal_code (ai_kill_switch, ai_disabled,
ai_not_configured, ai_data_class_denied and so on), a redacted message,
actor and data_classes_used. It has no digests and no tokens, because
nothing was sent.
Settings > Audit shows these rows from every product; filter by product to
narrow it down. Warden’s SOC metrics report
counts token use and refusals from Warden’s own ai.invocation and
ai.refusal rows.
Local Ollama
Section titled “Local Ollama”The suite ships an optional Compose profile that runs the upstream
ollama/ollama:0.6.5 image on loopback. It ships no model and never pulls one
by itself:
docker compose -f compose/ollama.yml --profile ollama up -ddocker compose -f compose/ollama.yml --profile ollama exec ollama ollama pull <model>The second command puts the weights in the ollama-data volume. Which model,
and those bytes, are yours.
Started like that, Ollama is its own Compose project on its own network. It’s
reachable at http://127.0.0.1:11434 from the host only, and the products
can’t resolve the name ollama. compose/ollama.yml isn’t in the root
docker-compose.yml include: list on purpose. To let the products reach it
as http://ollama:11434, add -f compose/ollama.yml --profile ollama to the
suite’s own raw-compose command (the one in the README, with both
--env-files, the profiles for your products and SIGMATIC_INGEST_BIND_ADDRESS):
docker compose \ --env-file versions.env --env-file .env \ -f docker-compose.yml -f compose/ollama.yml \ --profile core --profile sigmatic --profile opensearch --profile ollama up -dThat example assumes VELTRO_PRODUCTS=sigmatic in .env; use the profiles that
match your products.
Then in Settings > AI pick Ollama, set the base URL to http://ollama:11434
and the model to the one you pulled. Ollama needs no API key. The same base
URL goes to every product, and every product’s container is on the suite
network, so one Ollama serves them all.
VectorFlow’s strict outbound mode (VF_STRICT_OUTBOUND=true) refuses private
and loopback addresses, which rules out a local Ollama. Nothing in this repo
sets it.
A local model keeps prompts inside your network. It doesn’t change the rules: the same data classes, redaction, kill switch and audit rows apply.