The brief for IT & InfoSec reviewers

A user at your organization wants to run Simpler Legal. This page answers the standard software-review questions, and says for each answer what it rests on: the code, a test in the repository, or nothing yet. The files named here are in the source repository, github.com/Simpler-Systems/simpler-legal.

What it is

A local desktop application for Windows that removes people's names and identifying numbers from legal documents on the user's machine, so the redacted copy — not the original — can be pasted into an AI service or shared externally. There is no account and no server of ours; the app runs two local services of its own, on the loopback address only (below). It is licensed Apache-2.0, © Simpler Terminal Value Systems Pte Ltd. This page exists to give your existing software-review process what it will ask for — not to argue you should skip that process. If your organization routes tools that touch regulated data (MNPI, NPI, PHI) through third-party or AI-governance review, that review still applies; this page describes the software's behavior, not your data-handling policy.

Network behavior

Calls off the machineNone, by the code. Every request the app makes goes to 127.0.0.1: port 1436 (the engine service), port 49400 (the model server), and one more loopback port the system assigns at each launch, where the app's own relay carries the engine's requests to the model (below). The app's Rust side uses no HTTP client library: it writes HTTP itself on sockets it has checked, so there is no proxy setting for it to read. Every process the app starts is started with HTTP_PROXY, HTTPS_PROXY, ALL_PROXY and NODE_USE_ENV_PROXY removed from its environment and --use-env-proxy taken out of NODE_OPTIONS. An engine service started by hand refuses to start when those settings would send its requests through a proxy, and says which setting to remove. The model server is started with llama.cpp's own settings that would make it connect to another computer removed from its environment (LLAMA_ARG_RPC, which sends the model's work to other computers, the settings that download a model, and HF_TOKEN) and with LLAMA_OFFLINE=1, which llama.cpp's help describes as preventing network access. That has not yet been run against a live model server. The window's configured Content-Security-Policy is default-src 'self'; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self' data:, so the window's own requests may go to the app's own origin only. At build, Tauri adds a SHA-256 hash of each bundled script to script-src, and with a hash there WebView2 ignores 'unsafe-inline' for scripts: inline script does not run in the packaged window. Inline style is allowed. No packet capture of the packaged app is on record yet.
The model fileNot downloaded by the app. There is no in-app download and no file picker. The user downloads gemma-4-E2B_q4_0-it.gguf from Google's Hugging Face repository (Apache-2.0), at an address that names the file by its revision and that the Setup and Settings screens show as text, https://huggingface.co/google/gemma-4-E2B-it-qat-q4_0-gguf/resolve/69536a21d70340464240401ba38223d805f6a709/gemma-4-E2B_q4_0-it.gguf, or gets it through whatever channel you approve, and places it in one of the folders the app searches — the intended one is %APPDATA%\Simpler AI\models. That folder is in the roaming profile: under roaming profiles or folder redirection, a 3.3 GB file there is copied to the profile server. The list the app searches also includes %LOCALAPPDATA%\simpler-legal\models, which does not roam, or an administrator can set SIMPLER_MODEL_PATH to a folder. The file must be exactly 3,349,514,112 bytes with SHA-256 3646b4c147cd235a44d91df1546d3b7d8e29b547dbe4e1f80856419aa455e6fd; when the app starts the model server, the file is hashed against that pin (by the launcher, or by the app itself when SIMPLER_MODEL_GGUF names the file) and one that does not match is refused. The pin identifies the file the benchmark ran on, and it is the SHA-256 Hugging Face lists for that file at that revision of Google's repository (checked 2026-09-25). The file of the same name on that repository's main branch is a later upload with a different SHA-256; the app refuses it.
Installer and WebView2The app renders in Microsoft's WebView2 runtime. The repository does not set what the installer does when that runtime is missing, so Tauri's default applies (webviewInstallMode unset; the default in the Tauri build tools the repository uses (CLI 2.11.4, tauri-utils 2.9.3 in the Rust lockfile) is downloadBootstrapper, silent): the installer downloads Microsoft's WebView2 bootstrapper from https://go.microsoft.com/fwlink/p/?LinkId=2124703 and runs it without a prompt, which Tauri documents as needing an internet connection. When the runtime is already there, the installer does not connect. This is read from the configuration and Tauri's installer template; no install has been captured.
Offline bundleNot built. The installer does not carry the model.
Telemetry / analytics / updatesNone in the code: no crash reporting, no usage analytics, no update check, and no auto-updater.

The two local services, and who may use them

Loopback ports belong to the machine, not to one user. On a shared Windows host (Remote Desktop, Citrix) another signed-in account could bind port 1436 or 49400 before the app does and wait for documents. Until 2026-09-23 the app would have sent it the unredacted text. It now establishes who is on each port before any document text leaves its process.

Engine service
127.0.0.1:1436
A Node process running the frozen redaction pipeline. It answers only requests addressed to 127.0.0.1:1436 or localhost:1436, so a page reaching it through a DNS name it controls (DNS rebinding) is refused. A browser request that names an Origin is refused unless that Origin is the app's development server (http://localhost:1435 or http://127.0.0.1:1435), and a request a browser marks as coming from another site is refused even without one. The one endpoint that takes document text needs a per-launch token as a bearer credential, checked before the request body is read. The app tells the service, for each document, which port to reach the model on: the app's relay, not port 49400. A service too old to take that is refused.
How the app knows it is its own serviceThe app makes 32 random bytes when it starts the service and hands them over in the service's environment, never on its command line. Before sending a document it sends a random challenge; the service must answer with an HMAC-SHA256 keyed by that token, and the document then goes on the same TCP connection. A process on the port that cannot answer is sent nothing and the screen names it; if the model server is up, the app reads the document with its lighter in-app engine instead. A service a developer starts by hand writes its token to %LOCALAPPDATA%\Simpler AI\run\legal-service-1436.token (not the roaming profile); the app can use that service, and says on screen and on the receipt that it did.
Model server
127.0.0.1:49400
llama.cpp's llama-server, which cannot prove anything about itself. So the app reads Windows' TCP tables. If any process listening on port 49400, on any address, belongs to another Windows account, to SYSTEM, or is one the app may not inspect, the app refuses the port, sends nothing, and names the process. Otherwise the model server is the process that accepts a connection to 127.0.0.1:49400. One the app started is used. One under the user's own account that the app did not start is used only after the model file it reports matches the pinned size and SHA-256, and it reports the frozen context size (8192) and slot count (1); the screen and the receipt say it was started outside the app. One that will not answer without a key, as the model server of another open copy of the app does, is refused, and the screen says to close the other copy and press Retry.
Every connection to the modelThe pipeline's own stages connect to the model many times per document, for minutes. They are pointed at the app's relay, a loopback port the system picks at each launch that the app holds exclusively, so no other process can open a second socket on it. The relay accepts a connection only from the engine service the app started or one of its pipeline stages (or, during a run on a service started by hand, that service or a process it started), and passes on only the two requests the pipeline makes. For each one it opens its own connection to the model port and writes nothing on it until Windows' connection table names the far end as the checked model process, by process id and start time; if it is any other process, nothing is written. During a run, a change of model process, or the model process exiting, stops the run, and the app says so. The app's own requests to the model (its in-app engine and its checks) use the same per-connection check, except the status poll's GET /health, which carries no document text and only decides whether those checks run next.
Web pages and the model serverThe model server has none of the engine service's Host or Origin checks. By default, upstream llama-server publishes each slot's last prompt and output at GET /slots and answers any Origin, so a web page open in the user's browser while it runs could read the last piece of a document the model was given, or send it text. When the app starts the model server it sets LLAMA_ARG_ENDPOINT_SLOTS=0, the switch that turns that endpoint off, and a key made at that launch (LLAMA_API_KEY) that every endpoint except /health and /models then requires. The key is in the app, and in the environment of the launcher it starts and of what that launcher starts (the model server and, when no model path is set, a model-locating step); the engine service, its pipeline stages and web pages do not have it. The relay adds it to what it forwards. Neither setting has been tested against a running server. So the app asks /slots on every model server it uses, and asks its own whether a request without the key is refused; when either check fails, the screen says so and tells the user what to do, and the app still uses that server. The repository's own launcher (tools/launch-server.cmd, .sh) sets neither, so a model server started by hand with it serves /slots and takes requests without a key.
What this does not coverA program running under the user's own account. It can already read the user's files; no local check changes that. The /slots exposure above is separate: there a web page, not a program, could read document text.
How it is testedTwo test scripts run the real service on scratch ports and check its side (app/frontend/test/service-auth.mjs, service-lifecycle.mjs); both are part of the repository's one-command check (tools/verify.mjs). The app's side — the port-owner check, the relay and its per-connection check, the launch sweep — is Rust. It has unit tests (cargo test in app/src-tauri), which the one-command check runs when the Rust toolchain and the app's bundled Node runtime (app/src-tauri/binaries) are present; when either is missing it skips them and says which. No run of the frozen chain through the relay against a live model server is recorded yet: the byte-for-byte comparison of the chain's output with and without the relay has not been made (FREEZE.md, Transport changes).
SupervisionThe services the app starts, and what they start, normally run in Windows kill-on-close job objects (one for the model, one for the engine service), so closing or crashing the app stops them. The code states the exceptions. If Windows will not create the job, or will not put the model launcher in one (a parent job that forbids nesting, as some managed environments set), that process runs without the guarantee: closing the app stops the launcher, but a llama-server it started can outlive the app and keep port 49400. The app notices the second case and still recognises that server as its own. A process started in the instant between a spawn and its assignment to the job is not covered either. A service started by hand outside the app is not the app's to stop: closing the app does not stop it, and until it is stopped where it was started, any program running under the user's account can read its token file and send it text. Settings says so while the app is using such a service.

Endpoint requirements

PlatformWindows x64 only. The build produces one NSIS .exe installer; no macOS, Linux, MSI or portable build exists.
Admin rightsNot required: the installer is configured as a per-user install. The install folder must be writable, because the engine service writes its working files there (below).
SmartScreenv0.1.0, Windows, unsigned: Windows SmartScreen will warn before the installer runs. Nothing in the repository signs the installer or the app's executable, so there is no publisher for Windows to recognise. The way to know what you are running is to build it from source (BUILDING.md in the repository).
Data at restSaved exports go where the user saves them. The app keeps no document history between sessions. Kept between sessions, in the app's local web storage: the user's always-redact term list and two settings (practice and redaction profile). While the engine service works on a document it writes working copies — including the unredacted text and the key from tags back to names — under the install folder's engine\raw\stripped\_serve\, and deletes them when the run ends. If the app or the service is killed, they stay until the next launch of the app, or the next start of a service on the same port, removes them; the app's sweep deletes nothing when it cannot read which ports are in use. When a run fails or is stopped and its working copies were not removed, the engine line on the screen and on the receipt says the working copy and the key were left on the disk, in fixed words that name no folder, and says where the folder is named: in legal-serve.log, in the logs folder the History screen gives, when the app started the service (the app's next start of the service rewrites that file and removes the folder or names it there again), or in the window a service started by hand runs in. The in-app engine writes nothing to disk. Two log files, overwritten each time the app starts the service or the model server, sit in the local (not roaming) profile: %LOCALAPPDATA%\Simpler AI\logs\legal-serve.log (the service's status lines, which name paths, run ids and failures) and legal-llama-server.log (llama.cpp's own console output, at a verbosity the app fixes so that a user setting cannot raise it). The app rewrites the user's profile path and account name (when it is three characters or longer) in each line to %USERPROFILE% and %USERNAME%. In one measured model-server log at that verbosity (347 lines) the account name appeared only inside the model's path and no document text appeared; the log has not been audited for every setting, and an account name with non-ASCII letters can survive in the launcher's own echo line (not tested). Earlier builds wrote these two logs to the roaming %APPDATA%\Simpler AI\logs; the app deletes those two files at launch. A third file in the same folder, legal-app-exit.log, is written only when the app has to end itself after its window has closed: a line with the time and what it did to stop the engine, with no document text and no paths. It is kept until it passes 256 KB, then deleted at the next launch. An ordinary close writes nothing to it.
What the installer carriesThe app, a Node runtime for the engine service, the pipeline scripts, a llama.cpp build (llama-server.exe and its DLLs), and a licenses folder beside the app with this product's LICENSE, NOTICE and THIRD_PARTY_NOTICES.md, the licence texts of what it carries, and a note on the three Microsoft runtime files beside llama.cpp, which come with no licence text. The model is not included.

Application control (AppLocker / WDAC / MDM)

A per-user install answers elevation, not allowlisting. If your endpoints run application control, the app needs rules:

Verify it yourself — don't take this page's word

What files it reads, and what the .docx check cannot see

Simpler Legal never draws boxes over your document. Everything it redacts is deleted from the text and substituted with a placeholder, and a text export is a new file rebuilt from the cleaned text. A .docx export is not: it rewrites your original Word package, so its safety rests on a check rather than on impossibility — the finished file is walked again, and if the check finds a name the engine found or a term from your always-redact list still in it, the export is held back. The check has blind spots, listed below. A name the engine never found, in the body or in the headers, footers and notes it reads apart from the body, is in no table, so the check cannot catch it unless the name is on your always-redact list. A name run into a plain word of letters where nothing marks both its ends is a second: kestrelholdings where the list says Kestrel, or tanholdings where the table says Tan, is not masked in the text export or in the .docx, and the check passes. So is a name typed with its letters spaced apart. Word's hidden places are a third: a name split between separate parts of an equation, such as a fraction's top and bottom, ships, and the check passes. Tags follow spellings, not people: a different form of the same person's name, such as a surname alone or a possessive, can get a tag of its own, so the AI you hand the document to may read one person as two. The reverse happens too. Some names are replaced by a bare, unnumbered [Person] that does not tell people apart: on the benchmark's 25 court judgments, one [Person] stood for two or more differently spelled names in 15 (in none of its 46 agreements). And one spelling can carry two tags: in 9 of those 71 documents a name was tagged as a company in one place and a person in another, or as two different people. A table row can also reach into a different, longer name: with Donald in the table, McDonald becomes Mc[Person1], and the two read as one. Across separate documents of one matter the tags are not coordinated either: the same person can carry a different tag in each file. The map from tags back to names is a separate file that never exports with the document unless you explicitly say so.

What files does it read?

PDF, Word (.docx) and plain text. Scanned or image-only pages need OCR, which isn't included yet — we'd rather tell you that plainly than silently guess at a page. A PDF with no readable page is held; a scanned page inside an otherwise readable PDF is left out, and the copy carries a line in its place ([page 7 is not included: this app could not read it]). A saved email, or a file whose text carries an email's headers or encoded content such as a base64 attachment, is held before the engine reads it, because the app cannot read what is encoded and so cannot mask it; the hold says how to get through (save the message body as text or PDF, and each attachment as its own file). In a PDF, only the page that carries such a block is left out, marked the same way, and the other pages are read. Not every encoding is caught: ASCII85, for one, goes through unread, and so does a base64 run short enough, or shaped enough like a column of ids, to pass for one. A file is held when a run of characters written as % and two hexadecimal digits spells two or more plain Latin letters or digits, or, outside a web link or a file path, two or more letters of another script such as Chinese; an escaped space or punctuation mark, an escaped accented letter, and escaped letters of another script inside a link or a path go through on one line, except in a file name with a bare comma or semicolon in it, which is held, and a name written that way is masked in both exports, the escapes with it (see below); printed across rows, such a link can still be held.

A text export is a new file rebuilt from the redacted text — never your original with boxes drawn on it. A .docx export is different: it rewrites your original Word package, and its safety rests on a check rather than on impossibility. The finished file is read again: every part and the part's name, its text, every attribute value, the names of its elements and attributes (in the parts Word writes, not a name under one of Office's own namespaces), and the namespaces it declares or lists. It is held back if the check finds a name from the review table or a term from your always-redact list still in it, with its words apart or run together, or something the safety-net pattern masks. The check has blind spots, stated plainly here. The receipt states what the check reads and what it does not, and names the blind spots below that concern the check itself: a name split between the parts of an equation; a name with its letters spaced apart or each in brackets, with a hyphen, a line break or an invisible direction or format mark inside one of its words, broken across the lines of a quoted reply, or with a letter written as a symbol in the Symbol font; a name run into a plain word of letters; a name under six letters inside an address or in a value in the file's code; a row masked inside a longer name; a row of digits alone under six digits inside a longer number or value, or grouped other than by the marks it lists; a character reference without its semicolon (other than &nbsp and one by number), one HTML does not define, or one escaped again; a name under one of Office's own namespaces; and a number under one of Office's own prefixes or in a drawing's position, size or share of the page.

First, the engine reads the main body, and then, in a request of its own, the text the saved .docx carries outside it: headers (with any watermark), footers, footnotes, endnotes, chart and SmartArt text. A name it finds only there is masked wherever the saved .docx carries it, and the Review screen lists it; the copied text and the .txt are the body alone. If the body was read and that text was not, or its read could not be used, the .docx is held and says why. A name the engine did not find anywhere, and that is not on your always-redact list, is not masked anywhere in the file, and the check passes.

Second, a name or listed term run together with other words (KestrelCapital or kestrelcapital.com where the list says Kestrel, MargaretTan where the table says Margaret Tan) is masked in the text a reader sees, in the text export too, and holds the export wherever else the check finds it. In the text a reader sees, a name is found inside a longer word only where a break marks both its ends — a space or punctuation mark, letters meeting digits, a capital after a small letter — or before a plural ending when it is written with its capital (the Smiths where the table says Smith), or, from six letters and digits, inside a web or e-mail address, a path, a handle, a tag or a word with a digit in it (www.kestrelcorp.com, @kestrelholdings or #kestrelholdings where the list says Kestrel). Run into a plain word of letters where nothing marks both its ends, a name of any length ships, in the text export as well, and the check passes (kestrelholdings where the list says Kestrel, margarettanfile where the table says Margaret Tan, tanholdings or Tanholdings where it says Tan, LIWUCORP where it says Li Wu), and so does a name under six letters inside an address (tanholdings.com).

A row of digits alone, such as an account or matter number you add to the table, is found from six digits wherever they stand together, inside a longer number or a reference too (an IBAN that ends in it), and in the text a reader sees also across one mark that groups them: a space of any kind or a tab, a full stop, a middle dot or the bullet (•), a slash, a hyphen, a dash or the minus sign, a hyphen with a plain space either side (3192 - 6819), or a round closing bracket and one plain space after the first group, as (3192) 6819 is written; grouped any other way — with a comma (3192,6819), an underscore, a colon, two spaces, a line break, an en or em dash or a slash with a space either side (3192 – 6819, as a range is written), a square bracket ([3192] 6819), a no-break space beside a bracket or a hyphen, or a mark that only looks like one of those (a fraction slash, an ideographic or small full stop, a swung dash, a heavy minus sign, a white, black or triangular bullet: 3192⁄6819, 3192。6819, 3192◦6819) — it ships from both exports, and the check passes. A row of fewer than six digits, such as a five-digit matter number, is masked where it stands alone in the text, and holds the export where it is the whole of a value in the file's code that the check reads (in an add-in's attribute, in a part this app does not know, or between the tags of an element under one of Office's own namespaces); it is not looked for inside a longer number or value (9944179, or Matter 44179 as one value, where the table says 44179), in an attribute under one of Office's own prefixes (w:tel) or with no prefix on a drawn shape, or in a drawing's position, size or share of the page: there it ships, and the check passes.

A name written with percent-escapes, as a web link writes a space (Margaret%20Tan), an apostrophe, an accented letter (M%C3%BCller for Müller) or a Chinese name, or with a character reference typed out, with its semicolon or, for &nbsp and a reference by number, without it (Margaret Tan, Margaret&nbspTan, Margaret&#160Tan), is read as the characters it writes: it is masked in both exports, the escapes with it, and holds the export in the file's code. An escape that writes no character where a name could stand (Ren%E9%20Tan where the table says René Tan) holds both exports. Written as a mail gateway rewrites a link (Proofpoint's Ren*E9*20Tan or Ren-E9-20Tan), the same escape holds both exports too. A line break that a mail program's wrap put inside an escape, or inside a mail gateway's wrapping of a link, is read across where the link starts with a scheme (https://) or www., and the name is masked; in a link that starts with neither, or where the line after the break starts with a quote mark (>), as each line of a quoted reply does, a name broken that way ships from both exports, and so does a name with a line break inside its own letters (Marg and aret Tan on two lines), in a link or in prose.

The same rule can mask too much. A row is found in a longer name where a capital marks where it starts: with Donald in the table, McDonald becomes Mc[Person1]. And a row is found before a plural ending on a word written with its capital: with Law in the table, the Laws Committee becomes the [Person1]s Committee. And inside a tag, as inside an address or a handle, a row of six letters or more is found wherever it stands: with Porter in the table, #supporters becomes #sup[Person1]s. All three happen in the text export too, and the AI you hand the copy to may read someone else, or something else, as the person in your table. A name that ends in a short designator is found with the designator written out, and the whole word is masked with it: with Hickson Corp in the table, Hickson Corporation becomes [Company1], in the text export too.

Third, a Word file keeps names you never see on the page. The app gives every bookmark the author made a neutral name (bm1, bm2 and so on; the ones Word makes itself, for a table of contents or a cross-reference, keep theirs), does the same for every list and caption label the author named, and renames styles and fonts that carry a masked name. Internal links, page-number and footnote-number references and the table of contents follow the renamed bookmark and keep working, except that a table of contents whose entries are written as link fields keeps its page numbers but its entries stop being links; a cross-reference that shows a clause's number or text (Word's REF field), and a link written as a field code, become plain text and no longer update, and the save line and the receipt say so.

Measured on 2026-09-24, a name still ships, and the check passes, when its letters are spaced apart in the text or each written in brackets (M a r g a r e t T a n, ⒨⒜⒭⒢⒜⒭⒠⒯ ⒯⒜⒩), in the text export too; when a hyphen stands inside one of its words (Marga-ret Tan, as text copied from a PDF keeps a line's break), in the text export too; when an invisible mark that sets the direction or format of text stands inside one of its words (a left-to-right mark, as text pasted from a right-to-left document can carry), in the text export too; when it is written with a character reference that has lost its semicolon, other than &nbsp or one by number, that HTML does not define, or that was escaped again (Ren&eacute Tan, Margaret Tan, René Tan), in the text export too; when a letter of it is written as a symbol in the Symbol font, which Word draws as the Greek capital shaped like the Latin one (the A of Margaret TΑn), in the .docx; when it is split between separate parts of an equation, such as a fraction's top and bottom, a base and its superscript, or two cells of a matrix; when it is shorter than six letters and a program wrote it in lower case as a value in the code of one of Word's own parts (lee where the table says Lee); and when a program other than Word wrote it into one of the few places in the file's code that Word fills only with its own words.

A telephone or other number that the safety-net pattern masks in the text holds the export when an add-in wrote it into an attribute or an element of its own in one of Word's own parts, or when it is written as digits alone between the tags of an element under one of Office's own namespaces (<w14:tel>2125550147</w14:tel>), but is not found when a program wrote it into an attribute under one of Office's own prefixes (w:tel), into an attribute with no prefix on a drawn shape, or into a drawing's position, size or share of the page (<wp:posOffset>).

Fourth, a part of the file this app does not know, such as an add-in's, is kept only when what it holds reads as code — numbers, dates, ids, single code words, names Word looks up; anything typed in it, or a number the safety-net pattern would mask, holds the export. What is kept is read by the check for your table's names (a row of digits alone under six digits only where it is the whole of a value) and by the safety-net pattern, but a name that is not in your table is kept as written where it stands as a program writes code: in small letters, or starting with one and run together (whitfield, jonasWhitfield), as an attribute's value, and so at the end of a web address on a host Office's own addresses use (purl.org, www.w3.org) in an attribute; run together with digits between its tags (whitfield2291); in small letters, or so run together, as the name of an element, an attribute or a prefix; as the part's own file name; whatever it says, as the name of an element under one of Office's own namespaces; and, whatever it says, in the address of a namespace the part declares (urn:matters:Jonas Whitfield). When the export keeps such a part, the save line and the receipt carry the writer's note naming it, and the Review screen lists it under Warnings (“Unrecognized part not scanned”). If a part is named there, delete the object it belongs to or copy the document's text into a new blank document, and drop that copy instead.

Audit artifacts your users get

Every export can come with a redaction receipt. It records rows kept and left visible, tags placed, which safety-net tables ran, how many rows the user decided and how many stand as the engine marked them, whether the review was finished, a mask check, a check that none of the listed names is found in the export, a partial-name check, which engine ran (and whether a service or model server was started outside the app), and a network line counting requests off the machine as seen from the app's window, with what that count cannot see. Every line that concerns a name is a count: the receipt prints no name from the table, no always-redact term and no piece of either, apart from the fixed words of the .docx scope line, which are the same on every receipt whatever the table holds: examples such as KestrelCapital and XYZReport, the designators Co, Corp and Inc, and words such as Office and Dublin Core. A rule that looks for a client's name will match the receipt when the name shares one of those words. The names the user chose to leave readable are counted on it and listed in the name key, under "Left readable by your decision", each marked where it is boilerplate, on the always-redact list, or readable only in part. A piece of a listed name that may still be readable in the export (for example Hastings left behind by a masked Hastings Holdings Ltd) is counted on it and named only on the Export screen: printed beside the words "partial name", a piece would tell the reader which readable word belongs to a masked name. The partial-name check reads the copied text and the .txt, which stand for the body, and of a .docx export the text of every other part the writer saves (a header, a footer, a note, a chart, SmartArt), as saved; it does not read the file's code, nor the text Word keeps there as a setting (a watermark, the signer a signature line suggests, the text a list prints beside each number). It looks for each word of a masked row or a listed term from four letters, from two where both the name and the copy write it with a capital, or from four digits, and never inside a plain word of letters. It skips words too common to stand for a name alone (titles, designators, two-letter codes, the, and, for, of, and Bank, Road, Street, Place and Avenue) even where one is a surname, so Ms Street left in the copy is not flagged where the table says Della Street. Its line on the receipt says what it read, whatever it found. The original filename is withheld, because the receipt travels with the redacted copy. The name key, which maps tags back to names, is saved or copied only when the user asks, as a separate file whose name begins DO-NOT-SEND-namekey. For a .docx export the receipt also carries the writer's own notes: what it removed, what it renamed, what it wrote out as plain text (a cross-reference, for example), and any part of the file it did not know and kept. Whether the receipt satisfies a given regulator, court or file requirement is your professionals' judgment.

Plain-language regulatory framing (not legal advice)

Project facts

LicenseApache-2.0, © Simpler Terminal Value Systems Pte Ltd. The installer's licence page is the Apache-2.0 text, followed by the terms Microsoft requires users to accept for the three Visual C++ runtime files beside llama.cpp; there is no separate end-user licence for this product's own code. The installer puts LICENSE, NOTICE, THIRD_PARTY_NOTICES.md and the licence texts of llama.cpp (with the npm packages in the web page its server carries), the OpenMP runtime beside it, Node.js, the Rust crates and Microsoft WebView2 loader linked into the app, the npm packages its window is bundled from, and the setup program's NSIS parts in a licenses folder beside the app. The Visual C++ runtime beside llama.cpp is Microsoft's and comes with no licence text; licenses/msvc-runtime.txt says where each file comes from and quotes what Microsoft says about redistributing them. The OpenMP runtime beside it is the LLVM project's, under Apache-2.0 with LLVM exceptions (licenses/llvm-openmp-LICENSE.txt). The model is Google's Gemma 4 E2B, under Apache-2.0, and the installer does not carry it (THIRD_PARTY_NOTICES.md).
Source & releasesgithub.com/Simpler-Systems/simpler-legal; the installer is on GitHub Releases (v0.1.0, Windows, unsigned: Windows SmartScreen will warn).
Security disclosuresPrivately, through GitHub: github.com/Simpler-Systems/simpler-legal/security/advisories/new (SECURITY.md in the repository says what is in scope). If GitHub is not an option, write to support@simpler.asia. What the app sends and keeps on disk: PRIVACY.md.
Supply chainNo SBOM is published. licenses/rust-crates.txt and licenses/npm-packages.txt list every Rust crate linked into the app and every npm package its window is bundled from, with its version. No auto-update.
What it deliberately is notNo cloud service, no accounts, no browser extension, no Office/Outlook add-ins, no document storage between sessions. Scope is one job: local redaction, with a review the user must finish before anything exports.