PDFPipe

Document security / What is inside it

Internal information that ends up printed on a customer document

The values that reach a customer-facing document because they were available, which are usually internal identifiers, judgements and debugging aids.

The exposure

Documents accumulate helpful additions. A support reference in the footer, a batch identifier for tracing, an internal status, a template version. Each is added for a good internal reason and each is visible to the recipient forever. Some of them are merely untidy. Others disclose an internal judgement about the customer, reveal system topology, or contradict something the document says. The pattern is the same in every case: nobody decided to show it to the customer, it was just easier to include than to exclude.

The decisions

Reasons rather than a description of the code. Each one has a default that is fine for a page and wrong for a file somebody keeps.

  • Decide per field whether the recipient should see it, and treat the default as no. Internal information belongs in your logs and your audit record, which is where it is useful.
  • Never print an internal classification, score or flag. A customer segment or a risk indicator on a customer document is a disclosure of an opinion about them.
  • Keep internal identifiers off unless the recipient needs to quote them. Where they do, use a reference designed for that purpose rather than a primary key.
  • Keep system information off entirely: hostnames, environment names, queue identifiers, template versions. They tell a recipient about your infrastructure and mean nothing to them.
  • Watch the footer particularly, because it is where debugging aids are added and it is the part of a template nobody reviews after the first time.
  • Assert the absence of internal patterns in the rendered text, so an addition made for a good reason is caught before it ships.

In practice

A fragment, with the thing that goes wrong kept in a comment where it is the more instructive half.

js
/* Everything on this list was added for a good internal reason and
   is visible to the recipient forever.

     internal customer segment      an opinion about them
     risk or credit score           an opinion about them
     account notes written by staff about them, in their words
     database primary keys          not a reference they can quote
     environment or hostname        your topology
     queue or batch identifiers     meaningless to them
     template version               meaningless to them
     "generated by ... at ..."      usually meaningless, sometimes
                                    contradicts the document's date   */

// The footer is where these appear, because it is the part nobody
// re-reads after the first review.
const footer = `<div style="font-size:7pt">
  ${company.registeredDetails}
</div>`;

// Not: `ref ${invoice.id} · ${process.env.NODE_ENV} · tpl ${TEMPLATE_VERSION}`

// A guard, so a helpful addition is caught rather than shipped.
test("no internal information reaches the document", async () => {
  const text = await extractText(await renderFixture("invoice", "typical"));

  expect(text).not.toMatch(/\b(staging|production|prod-\w+)\b/i);
  expect(text).not.toMatch(/\btpl[- ]?v?\d+\b/i);
  expect(text).not.toContain(invoice.id);          // internal key
  expect(text).not.toContain(customer.segment);
  expect(text).not.toContain(customer.riskBand);
});

What people do instead

Adding a trace identifier to the footer during an incident so support can correlate a document with a log. It solves that day's problem, it is never removed, and every document from then on carries an internal identifier and, often, an environment name.

How this is found out

Occasionally by a customer asking what a code on their invoice means. More often never, because it is small text in a footer that nobody reads including the people who put it there.

Frequently asked

Does this page tell me what the law requires?

No, and deliberately not. Retention periods, erasure obligations and residency rules vary by jurisdiction, by industry and by the kind of document, and they change. What these pages describe is the shape of the problem and the mechanisms a system needs in order to implement whatever answer your own advisers give you. Where a genuine tension exists, such as an immutable record against a right to erasure, it is named as a tension rather than resolved.

Why is so much of this about the contents rather than access control?

Because access control decides who can obtain a copy and has no opinion at all about what happens to the copy. Once a document is on somebody's laptop, forwarded to a colleague or printed, every control listed here has already stopped applying to it. What is inside the file is therefore the part that keeps mattering, which is the opposite of the balance you would strike for a page.

How much of this applies at a small volume?

Most of it, because these are decisions rather than infrastructure. Redacting by omitting rather than covering costs nothing. Deciding what goes on a template costs one review. Classification is one object in code. Legal hold and an audit trail are the two that take real work, and both are far cheaper to build before they are requested than under the deadline that comes with the request.

Related security topics

The decisions that depend on each other, then the rest of the same group.

Most of these are decisions rather than features, and the cheapest time to make them is before the first document is delivered rather than after one reaches the wrong person.