An internal audit asks a simple question about a working document pipeline: what personal data leaves this company, to whom, and on whose decision? Nobody can answer. The pipeline's LLM fallback stage posts the full page text of every hard document to a hosted model API, and the page text carries buyer names, delivery addresses, contact phone numbers, GSTINs and the hand-written name of whoever signed for the goods. There is no redaction step, no record of who approved sending it, and no retention statement. The request and response bodies are also written to the application log in full, where they have been sitting for four months.
Any pipeline that keeps a hosted-model stage for the documents rules cannot handle — which is the standard design, and a good one. The risk appears at the moment the LLM rung is added, usually late in a build when the pressure is to make the last ten percent of pages work. Especially common with invoices, KYC forms, delivery slips and medical bills, because those documents are made of personal data. Linked modules: P2 (Document intelligence pipeline), 3.11 (Safety, guardrails, DPDP), 3.2 (Working with LLM APIs properly), 4.8 (Monitoring).
Three separate failures wear one costume here, and a fix that addresses only the first will not survive the next audit.
The first is data minimisation. The LLM rung needed the total, the invoice number and the date. It was sent the whole page, because sending the whole page was one line of code shorter. Under India's DPDP Act 2023 the governing idea is purpose limitation: personal data collected for billing cannot be quietly used for something else, and sending more than a task needs is the easiest way to break that rule without meaning to.
The second is the missing decision. A hosted model is a third party processing personal data on the company's behalf. That is a decision somebody must actually take, with the data categories named, the vendor named, the retention period stated and a person's name against it. In this pipeline the decision was made by a developer choosing a default, and an unrecorded decision cannot be defended, cannot be reviewed and cannot be reversed in a controlled way.
The third is the log. Even if every byte sent outward were justified, writing full request and response bodies to an application log spreads the same personal data to everyone with log access, into backups, and into whatever the log shipper forwards. This is the quiet one: it survives long after the API call is fixed, and four months of logs is four months of exposure.
Confirm it fast: grep the last day of logs for a phone-number pattern and for the GSTIN shape. If either returns hits, the log leak is real and it is the first thing to stop.
Stop the bleeding first. Switch the logger to record field names, lengths, hashes and outcomes rather than values, and put the log retention window on a timer. Then purge or rotate what already exists, following whatever your organisation's process is — do not delete evidence on your own initiative during a live audit.
Then redact before send, not after. Build one function that runs on the page text on its
way to any external call: replace phone numbers, GSTINs, buyer names and receiver names
with stable neutral tags such as <PHONE_1>, <PARTY_1>, and keep the mapping in memory
only, inside the process, so the model's answer can be mapped back locally. The model
needs the shape of the page, not the identities on it. Test it as a unit test with a
page that contains all five categories, and assert that no raw value survives.
Then write the decision down. A short data-flow record naming what is sent, what is removed first, which vendor receives it, whether the vendor trains on it, how long they keep it, and who approved it — one page, dated, in the repo. Where the answer is uncomfortable, the honest option is on the table: run the fallback on a local model, or route those pages to the human queue instead. Sending nothing is always a valid design.
Make redaction a property of the boundary, not of a stage. Every outbound call goes through one client wrapper, that wrapper redacts, and there is no second path. A rule that lives in one place gets tested; a rule that lives in a habit gets skipped at 11 pm.
Write the data-flow record on the day the external call is added, and treat it as part of the definition of done alongside tests and the container. Keep a fixed list of what counts as personal data for your documents — name, address, phone, email, GSTIN, PAN, signature, account number — and let both the redactor and the log filter read from the same list, so adding a category fixes both at once. Finally, ask the design question before the code question: which of these pages actually need an outside model? On most invoice corpora the answer is under ten percent, and shrinking that share is a privacy control as much as a cost control.
Q: "Your pipeline sends hard pages to a hosted LLM. What did you have to decide before that was allowed?" Strong answer: name three things and connect them. First, minimisation — send the smallest text that does the job, with names, phone numbers, GSTINs and signatures replaced by tags before the request leaves the process. Second, a written data-flow decision — which vendor, which categories, retention, training use, and who approved it — because a third party processing personal data under DPDP is a decision, not a default. Third, safe logging, since the log is the leak that outlives the fix. Close by saying that "route those pages to the human queue instead" was a real option you considered and priced, which shows you treated it as a design choice rather than a compliance form.