Gå til hovedinnhold

Redaction

Redaction replaces sensitive content (people, unreleased product names, internal roles, secrets) with protected placeholders before a document is sent for AI translation or handed to an external translator, then restores the originals once the translation comes back. The sensitive value never leaves the local machine.

redacttranslate, then unredactEmail Sarah Chen the Project Halcyon launch date.Email <x id=1/> the launch date.what the model sees: opaque tokensEnvoyez à Sarah Chen la date de lancement de Project Halcyon.originals restored locally

For the architecture and design decisions behind this, see C-10: Content Redaction.

In the CLI, redact and unredact are pipeline steps. They run in four places:

  • the built-in secure-translate flow runs redact, translate, unredact (kapi run secure-translate);
  • a project flow can place the same steps around its own pipeline;
  • kapi extract --redact protects a translator hand-off;
  • in a project that declares a redaction policy, kapi exec <tool> puts the two steps around a tool that calls a model, for each file under the policy that covers it (see Recipe configuration).

Under kapi exec, the steps apply the policy's rules to every block the tool reads, blocks marked not translatable included, to each translation beside the source, plural forms included, and to everything the tool sends with them (see What the policy covers). A policy that detects entities stops the run, because no step ahead of the tool finds them. A tool with no declared contract does not run under the policy, because kapi cannot tell whether it calls a model. See Understanding the CLI layers for the flow and single-tool surfaces.

The policy also covers external-command, because the program it runs may send what it reads anywhere. The program reads each withheld value as its placeholder, and kapi puts the original back into the program's output. This holds for kapi exec external-command and for a flow, which needs a redact step ahead of the tool. When the output drops, alters, repeats or adds a placeholder, the block keeps its content and the run reports a failing finding that names the block and the placeholder, and kapi exec and kapi up exit 3.

What the policy covers​

Under a declared policy, the policy's rules apply to every piece of the project a request to a model carries:

  • the blocks the tool reads, blocks marked not translatable included, each translation beside the source, and every plural form;
  • the blocks before and after a block, sent to show where it sits;
  • the block's key in its document;
  • the block's previous version and its approved translation, read from the content memory;
  • the term rules: each term, its replacement, and the do-not-translate terms;
  • the voice profile: its guidance, style, patterns and examples;
  • the instruction, from --instruction or the recipe;
  • the domain named for term extraction and voice inference;
  • the text an MCP tool call passes, and the translation passed beside it.

This holds for kapi exec, for each step of a flow that runs after its redact step (kapi run, kapi up), for the MCP tools, for Deeper look with AI, and for the AI actions in Kapi Desktop's review (Fix with AI, Retranslate, Explain and the AI pre-review). A value a block withholds reads as the same placeholder wherever the request carries it. A value that appears only beside the block, in a term rule say, gets a placeholder of its own, and kapi restores it when the model copies it into a translation. A tool that reads text alone, such as term extraction, review or voice inference, reads each withheld value as its visible placeholder ([REDACTED:Person]).

Entity detection reads the blocks and does not reach the rest of a request. A person named only in the voice profile or a previous version is redacted there when a rule names them, or when a block of the same request withholds the same text.

kapi voice check --ai applies the policy of the file it reads, or defaults.redaction for text given inline. kapi terms expand applies the rules of defaults.redaction to the terms it sends, and writes no forms for a term the policy withholds.

The rules cover text only, so they do not reach the image crop or speech clip media-refine sends. A machine-translation engine from a plugin reads the redacted block, each withheld value as a placeholder element it keeps in place, and nothing else from the project.

How it stays local​

A redacted span becomes a placeholder, the same protected inline-code primitive kapi uses for variables and tags, with a type like redaction:person. The placeholder carries only a category and a stable token; the original text is held in a local vault and is never written into the content that leaves the machine.

For AI translation the protection is structural: kapi sends the model an opaque token (<x id="…"/>) in place of each placeholder, so the model receives neither the original text nor its visible label, then re-attaches the placeholder by token when the translation returns. For external translation the placeholder travels in the bilingual file as ordinary protected markup while the original↔token mapping stays behind in a gitignored sidecar.

Detection​

Redaction finds sensitive spans with one or both detectors:

  • Rules (default) are literal terms and regular expressions you declare. Fully offline and deterministic.

  • Entities (opt-in) are named entities (people, organizations, products, locations, dates, …) recognized automatically and redacted by category. A fast local model keeps detection on the machine; a cloud model trades that for broader coverage during the detection step. The same detection feeds entity-generalized content-memory reuse, so one annotation serves both.

    The entity-extract step selects the detector with engine: llm (the configured AI provider; the default), ner (an on-device model, with no provider call, no credentials and nothing leaving the machine), or hybrid (both, merged). With engine: ner the step carries no remote-egress side effect, so the placement pass accepts it ahead of redact without qualification. The browser Flow workspace lab runs this end to end with a GLiNER ONNX model loaded in the page.

Each match is assigned a category. The recommended categories are person, role, product, org, location, and custom, but categories are free-form, so use whatever the placeholder template should display.

Rules file​

The term list is itself sensitive, so it lives in a dedicated file outside the committed recipe. Keep it gitignored:

# redaction.yaml: bind it with defaults.redaction.rules, and gitignore it
version: v1
placeholder: "[REDACTED:{category}]" # {category} and {n} are substituted
detectors: [rules]
rules:
- term: "Sarah Chen"
category: person
- term: "Project Halcyon" # unreleased codename
category: product
- pattern: "ACME-[A-Z]+-\\d+" # regular expression: internal SKUs
category: product
flags: [ignorecase] # also: wholeword (literal terms)

Workflows​

In-process: secure-translate​

The built-in secure-translate flow redacts, AI-translates against the placeholders, and restores the originals, all in a single run, so the secret is only ever in memory:

kapi run secure-translate -i src/locales/en.json --target-lang fr
readerchanredactplaceholders + vaultchantranslatesees tokens onlychanunredactrestore originalschanwriter

redact is an ordinary transformer step: the framework applier rewrites the source, replacing sensitive spans with placeholders and vaulting the originals fail-closed, before the next step observes it. Because redaction's edit plan is structured, run-anchored overlays attached upstream (terms, entities) survive the rewrite by rebasing; only spans overlapping a redacted span are dropped. The flow's placement pass keeps the ordering safe: it rejects a redact placed after any step that sends source to a remote service. unredact is an ordinary late step that restores the originals on the way out. You can compose both into your own flows as ordered steps:

steps:
- tool: redact
- tool: translate
- tool: unredact

In a project, a redact step that names no rulesPath or rules of its own redacts each file under the policy the recipe declares for that file. A step that names its own rules keeps them.

External: extract, translate, merge​

When a human translator or CAT tool does the translation, redact on the way out and restore on the way back. kapi extract --redact emits a bilingual file containing only placeholders and writes the originals to a local vault sidecar; kapi merge restores them after the translator returns the file.

# Emit redacted XLIFF: originals stay in .kapi/vault/batches/<batch>.json
kapi extract --redact

# ... translator fills in targets, preserving the placeholders ...

# Restore the originals into the merged target
kapi merge -i out/app.en-to-fr.xliff

Pass --redact-rules <path> to point at a rules file directly, or enable redaction in the recipe (below) so plain kapi extract redacts. Use kapi merge --no-restore to keep the placeholders in the merged output.

Recipe configuration​

Declare redaction in the project recipe, and kapi applies it wherever content leaves the project: kapi push, kapi exec, kapi extract, Deeper look with AI and the record kept of each edit. A policy can be declared in three places:

  • defaults.redaction, for every file of the project;
  • redaction: on a collection, named or bare, for the files it holds;
  • redaction: on a content item, for the files that item matches.

Each file is redacted under the most specific declaration that covers it: its item's, else its collection's, else the defaults. That declaration replaces the others for the file. Its fields are not merged with theirs, so a collection that names its own rules file redacts with that file alone, and one that needs the default rules as well lists them in its own rules file. A redaction: at any of the three places that sets rules, detectors or placeholder must set enabled: true, and the recipe fails to load if it does not; enabled: false alone turns redaction off for those files.

defaults:
source_language: en
target_languages: [fr, de]
redaction:
enabled: true
rules: redaction.yaml
detectors: [rules]
placeholder: "[REDACTED:{category}]"

collections:
- name: legal
redaction: # replaces defaults.redaction for legal/
enabled: true
rules: legal-redaction.yaml
content:
- path: "legal/*.md"

- path: "src/locales/*.json"
format: json
redaction:
enabled: false # no redaction for this entry

Content that sits in no file, such as the text an MCP tool call passes, is redacted under defaults.redaction. A .kpz package holds files of every collection, so in a project that declares a collection's or an item's policy, kapi exec refuses to run a tool that calls a model over a package; run it on the project's files instead. A redacted package (below) that kapi extract --format kpz wrote in this project is the exception: its files already hold placeholders, so the tool runs and reads them. A flow places its own redact step, and kapi refuses to run a flow that sends source to a remote provider without one. A project flow runs over every file of the run, so it needs the step when any declaration in the recipe turns redaction on. A built-in flow run on one file needs it when a policy covers that file.

Where the originals live​

WorkflowVault
In-process (secure-translate, custom flows, kapi exec)In memory on the block; removed after restore, and never written to output
External (extract --redact → merge).kapi/vault/batches/<batch-id>.json, in a directory kept out of version control and written private (0700, files 0600)
Package from source files (extract <files> -o work.kpz --redact-rules <file> → merge work.kpz)work.kpz.vault beside the package, written private (0600)

The vault sits in .kapi/vault/, beside .kapi/work/ and outside it. Everything under work/ rebuilds, so you can delete it; a withheld original cannot be rebuilt. The vault is never committed and never synced to a server, so the only copy is the one on your machine. rm -rf .kapi/work stays safe at any time; deleting .kapi/ wholesale strands any batch you have out with a translator, because the merge that would restore the originals has nothing left to read.

Restoration matches placeholders back to originals by token where the format preserves inline structure (in-process pipelines and XLIFF), and by the unique visible token text where the format flattens placeholders to plain text (such as JSON). On extract, redaction runs before content-memory pre-fill, so no sensitive value can reach the emitted file through a memory match.

A vault keeps each original under the file it came from, the block, and the placeholder's token. Block IDs and tokens repeat from file to file, so two files that each withhold a different name keep both names, and kapi merge gives each file its own.

A vault written by an earlier kapi records no file, and kapi refuses it. The message names the vault file and what to run:

  • For a batch vault under .kapi/vault/batches/, run kapi extract again. The new extraction writes a new vault with new files to translate, and a file returned from the old extraction no longer merges.
  • For the project vault, .kapi/vault/redaction.json, move the file aside and run the command again. kapi writes a new vault from the content it reads. A recorded edit whose originals are in the old file keeps its placeholders, and the moved file is the only copy of those originals.

A vault written by a newer kapi is refused too; upgrade kapi to read it.

A PO file has no inline codes, so kapi extract --format po writes each withheld value as its visible placeholder ([REDACTED:Person]), and kapi merge restores it by that text.

Redacted .kpz packages​

Under a redaction policy, or with --redact-rules, kapi extract --format kpz and kapi extract <files> -o work.kpz write a redacted package. Each source file in it holds the visible placeholder ([REDACTED:Person]) wherever the policy withholds a value: the skeleton, the raw source that --with-source adds, and the content-memory matches and terms that travel with the package. A match or a term in which the policy's rules find a value stays out of the package. The package's manifest records that it is redacted and the ID of the vault that restores it.

The vault never travels inside the package:

  • kapi extract --format kpz keeps it in the project, at .kapi/vault/batches/<vault-id>.json, one vault for every package of the run.
  • kapi extract <files> -o work.kpz --redact-rules <file> writes it beside the package, as work.kpz.vault. Extract never replaces a vault already at that path: merge the package it restores first and remove the vault, or extract to another name.

Whoever receives the package works on the placeholders. A receiver who runs kapi exec on the package needs the source text in it, so extract it with --with-source: a package that carries only skeletons gives a tool no text to work on, and merges empty values. kapi merge restores each file's values, in every plural form, from the vault whose ID the manifest records: in the project for a project package, and beside the package for merge work.kpz. When the vault is not there, or its ID differs, the merge stops before it writes a file and names the vault it looked for. Keep the placeholders intact during translation: a translation that still holds a placeholder after the restore stops the merge, which names the file and the block.

kapi exec runs a tool over a redacted package on the placeholders and never reads the vault, so the package keeps holding placeholders until the merge. In a project that declares a collection's or an item's policy, a tool that calls a model runs over a package only when kapi extract --format kpz wrote the package in this project, so its vault is among the project's batch vaults.

Extract refuses a file it cannot redact into a package: one in which the policy's rules still find a value once the blocks are redacted (in its markup, in a field kapi does not read as a block, or across an inline code), and one whose block IDs change once the values are withheld. It also refuses a policy that detects entities, since a .kpz extraction runs no entity-extract step. A project's XLIFF 2 or PO extraction carries the blocks alone.