Skip to content

Mask PII in the engine (off by default), privacy switch, Console skills - #84

Merged
its-janghoon merged 5 commits into
developfrom
feature/privacy-masking
Oct 12, 2026
Merged

its-janghoon merged 5 commits into
developfrom
feature/privacy-masking

Conversation

@its-janghoon

Copy link
Copy Markdown
Member

Summary

The engine half of the insights / skills / privacy work. Everything is done once here so every Redrob app that runs on the engine inherits it.

  1. PII masking, off by default. privacy.mask_pii = off | regex | model in redrob.json. In session/runner/llm.ts, the one place every provider request passes, emails, phone numbers, card numbers (Luhn-checked) and Korean resident numbers are replaced by placeholders ([EMAIL_1]) before the request leaves the machine, for every provider. model adds people, organisations and places named by a LOCAL model the engine already serves (privacy.model = provider/model, e.g. an Ollama model): the new text of each turn is sent to it, the answer is parsed as strict JSON, anything else is ignored. One vault per session, so the model sees the same placeholder for the same value across turns; placeholders are restored in every text delta (a placeholder split across two chunks is held back and joined) and in every tool-call input, so tools run on the real thing and the person reads the real thing. The system prompt is ours and is left alone. No new dependency, no on-device ONNX, no npm package.
  2. GET and PUT /api/privacy. An app shows a switch and nothing more. PUT takes effect on the next message and is written to the user global redrob.json, so it holds across restarts and across apps.
  3. x-redrob-app. The engine sends the app name from REDROB_APP (set by the app on the engine process) next to the X-Session-Id it already sent, so the Console can group AI work by app. A session on a non-Redrob provider never reaches the Console, so nothing is sent for it.
  4. Console skills. https://console.redrob.ai/skills/ is the first skill source on every engine, before anything in config.skills. The discovery code already existed; this is the URL.
  5. Client and SDK regenerated. They had fallen behind the routes (browser requests, skill autoInject, now privacy). The plugin API is typed from the SDK, which has no readonly arrays; the host converts at that boundary with the mutable() helper it already had.

/v1/chat/completions is already served on develop; nothing to add. develop contains no desktop shell; the drafts live on alpha/desktop and feature/desktop-shell and stay unmerged.

Not in this PR

Image and speech tools and the /redrob/speech, /redrob/transcribe routes (next PR). The on-device labellers (route-labeller, work-labeller): the gateway already classifies task type and language on the request it is serving, and these labels are the only thing that was going to leave the machine, so a 435 MB on-device model bought nothing.

Tested

  • bun turbo typecheck --concurrency=2: 18/18.
  • packages/core: bun test test/privacy test/config test/plugin test/skill 193 pass; test/session* 211 pass. New test/privacy/mask.test.ts covers the regex tier, request masking, split-placeholder streaming, tool-input restore, strict JSON parsing and the default.
  • packages/redrob httpapi-public-openapi.test.ts 21 pass with the new group.
  • oxlint 0 errors; prettier clean on changed files.

privacy.mask_pii = off | regex | model. One vault per session in session/runner/llm.ts, the
one place every provider request passes: emails, phones, cards and resident numbers (regex), plus
people, organisations and places named by a configured LOCAL model (model), are replaced by
placeholders on the way out and restored in every text delta and tool-call input on the way back.
The system prompt is ours and is left alone. Also sends x-redrob-app from REDROB_APP so the Console
can group AI work by app.
The value takes effect on the next message and is written to the user's global redrob.json.
console.redrob.ai/skills/ is the first skill source, before anything in config.skills, so every
Redrob app that runs on the engine has the same skills.
The regenerated SDK gains SkillV2.autoInject, which the plugin API is typed from; the host converts
readonly arrays at that boundary with the mutable() helper it already had.
@its-janghoon
its-janghoon merged commit 98296d9 into develop Oct 12, 2026
26 of 27 checks passed
@its-janghoon
its-janghoon deleted the feature/privacy-masking branch October 12, 2026 17:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant