PDFPipe

Document security / What is inside it

What metadata a generated PDF carries, and how to check

The fields a document format carries alongside its visible content, which are invisible on the page and present in the file.

The exposure

A PDF can carry an information dictionary and metadata packets holding a title, an author, a producer, creation and modification timestamps and arbitrary custom fields. None of it appears on any page and all of it travels with the file. Where those fields are populated from application data, or from the environment that produced the document, they can disclose an internal path, a username, a system name or a timestamp that contradicts what the document says. The reliable approach is not to reason about what a pipeline sets but to inspect a real file.

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.

  • Inspect a finished document rather than assuming. Open its properties, or read the metadata programmatically, and see what is actually there.
  • Do that on a document produced by the real pipeline, not a local test, because the environment contributes to some of these fields.
  • Never put application data into metadata fields without deciding to. A title populated from a template variable can carry a customer name into a field nobody reviews.
  • Know what this API writes deliberately: the pdf_a option writes an XMP packet declaring PDF/A-1b identification, and the response says in as many words that the marking is best-effort. That is the metadata written on purpose.
  • Strip or set metadata downstream if a requirement demands specific values, since that is a post-processing step rather than a render option here.
  • Include a metadata check in the test suite once you know what should be there, so a change is caught rather than discovered.

In practice

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

js
// Inspect a real file rather than reasoning about the pipeline.
const meta = await readPdfMetadata(pdf);
console.log(meta);
//   { title, author, subject, keywords, creator, producer,
//     creationDate, modDate, custom: { ... } }

/* Do it on output from the real pipeline, not a local run: some of
   these fields pick up the environment that produced the document.

   What this API writes on purpose: with options.pdf_a set, an XMP
   packet declaring PDF/A-1b identification, and the response tells you
   the marking is best-effort. That is the deliberate metadata.

   What to check for, because it is invisible on every page:
     a title populated from a template variable      a customer name
     an author or creator from the environment       a username
     custom fields added by a library                anything
     a creation timestamp                            contradicting the
                                                     document's own date */

// Once you know what should be there, assert it.
test("document metadata carries nothing personal", async () => {
  const meta = await readPdfMetadata(await renderFixture("invoice", "typical"));
  expect(meta.title ?? "").not.toContain(customer.name);
  expect(meta.author ?? "").toBe("");
  expect(Object.keys(meta.custom ?? {})).toHaveLength(0);
});

// Where a requirement demands particular values, set them downstream:
// that is a post-processing step, not a render option here.

What people do instead

Setting a document title from the same variable used in the heading. It is a natural thing to do, it puts a customer name into a field that appears in a reader's title bar and in file listings, and nobody looks at metadata in review.

How this is found out

By somebody opening the document properties, which is rare, or by a tool during an assessment. It can persist for years because nothing about it is visible in normal use.

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.