Skip to content
ivralo
Sections

Docs / Incidents

Privacy and masking

Production logs hold what your users sent. This page covers when ivralo refuses to show them to a hosted model, how masking replaces hosts, addresses and customer ids with placeholders, and where the real values come back.

Evidence stays on the machine by default

When a model that would read the evidence runs somewhere other than this machine, the incident tool refuses the bundle and names the setting that would allow it. The models that count are the Investigator's, and the leader's and memory model's, since the reply and the session pass through them. Which models are hosted is on Models; ivralo doctor says beside its privacy note whether a hosted run would be allowed.

To allow it:

HowScope
ivralo incident --hostedThis one run.
ivralo pilot run <pack> --hostedThis one pilot.
IVRALO_INCIDENT_HOSTED_MODEL=true in the home's .envEvery run, including the session, the local API and alert intake.

Allowing it is not enough on its own. A hosted run also needs masking turned on and at least one rule of your own; otherwise it is refused with a sentence saying which is missing.

IVRALO_INCIDENT_HOSTED_MODEL=true
IVRALO_INCIDENT_MASK=true

The built-in rules cover IP and e-mail addresses only. Without a rule of yours, every host and customer name would leave, so ivralo does not start.

The refusal guards the incident tools, not the workspace. The file tools can still read what is in it. Treat it as a guardrail on the path the evidence normally takes.

Turning masking on

IVRALO_INCIDENT_MASK=true

Masking is off by default and works the same for a local model. With it on, every text the incident tools hand a model is masked first: the digest, the report the filing tool answers with, and every other tool result in the run. Each identifier becomes a placeholder such as <host-1>, <ip-3> or <customer-2>.

  • A placeholder says nothing about what it replaced: not its first letters, its domain or its length.
  • The same value gets the same placeholder in every run and every Investigator in the repository, and a placeholder once given is never renumbered.
  • Masking works one line at a time and never across a line end, so app.log:12 in the masked digest is still line 12 of your file.

identifiers.json

You name what to mask in identifiers.json. ivralo reads two of them and joins them by kind:

  • the home's identifiers.json, for every workspace (IVRALO_IDENTIFIERS moves it);
  • the repository's .ivralo/identifiers.json, read only once you trusted the folder, because a pattern is code the scan runs.
{
  "patterns": {
    "host": ["[a-z0-9-]+(\\.[a-z0-9-]+)*\\.prod\\.example\\.com"]
  },
  "literals": {
    "customer": ["ACME-0042", "globex"],
    "host": "web1"
  },
  "builtin": true
}
KeyMeaning
patternsKind to Python regular expressions (one string or a list). Use (?i) for case-insensitive.
literalsKind to exact names (one string or a list). Matched in any case and only as a whole name: web1 masks web1.prod.example.com and WEB1, never a piece of web10 or web1-canary.
builtintrue by default. false turns off the built-in ip and email kinds.

A kind is a lowercase name of up to 32 characters: a letter, then letters, digits or _. It names the placeholder: kind host gives <host-1>.

The built-in ip kind knows IPv4, IPv6 in full and short forms (::1, fe80::) and cloud host names that spell an address (ip-10-0-0-5). The built-in email kind matches e-mail addresses.

Where two rules overlap, such as a host pattern and a domain pattern over db-1.prod.example.com, the whole stretch becomes one placeholder, named by the longer match, so no piece of a name is left beside it.

A file with a problem, such as a pattern that does not compile or a kind with a capital letter, stops the read with the problem named. A broken rule would let its identifiers through, so ivralo refuses rather than mask partly.

mask.json: the table

The placeholders a workspace has given live in one file:

.ivralo/incidents/mask.json
{
  "placeholders": {
    "<host-1>": "db-1.prod.example.com",
    "<ip-1>": "10.0.4.17",
    "<customer-1>": "ACME-0042"
  }
}

Entries are only ever added. A new placeholder is chosen under a lock, after the file is read again, so two investigations at once never give one value two placeholders. The file holds the real values: it stays under .ivralo/, which git ignores. Open it when you need to know what a placeholder in a session or a trace stands for.

On Windows the lock holds within one process only, so do not run two investigations of the same workspace at once there.

What is restored, and where

PlaceHolds
report.md, report.jsonThe real values. What the Investigator wrote back (its cause, its references, what it could not settle) is restored before the report is checked and written. Quoted lines are copied from the bundle as always.
The session's replyPlaceholders.
The stored session, a trace, a recorded cassetteThe placeholders the model wrote.
ivralo whyReaches the real line through mask.json, whether you quote the report or the reply.

Tools that reach out

Inside a masked run the Investigator may search for a placeholder. A tool that only reads the repository, such as a grep or a file read, gets the real value in its arguments, because nothing leaves the machine. Any tool that can reach out (the shell, Python, the web, a message, an MCP server's tool, or a tool that declares no contract) gets the placeholder instead.

ivralo decides this from what each tool declares it does, not from a list of tools, so a new tool that reaches out is covered.

Masking guards the incident tools. ivralo's own credentials are hidden from every tool result anyway, whether masking is on or not.