Plugins

A plugin answers one yes/no question about one file, fixed in its signed manifest. The only thing that leaves it is 1 or 0.

Questions are about format, structure, integrity or rendering, never about what the content means or says. Plugins take no parameters: with one bit of output, a parameter is the way to aim that bit at the content (“does page 2 contain X?”). A variant is another plugin, with its own question and its own review.

The official marketplace

The lockfile-marketplace repository holds the official plugins, one Go module each, with the SDK and the conformance harness they are tested with. Its signer is pinned in the lockfile binary. Its source holds these 13 plugins, all at version 1.0.0:

Official plugins
PluginQuestion1 means
file.non-emptyDoes the file contain at least one byte?size > 0
format.is-pdfDoes the file start like a PDF?%PDF- appears within the first 1024 bytes
pdf.parsesDoes the file parse as a PDF without errors?pdfcpu reads and validates it (relaxed mode)
pdf.truncatedDoes the PDF look truncated?no %%EOF in the last 1024 bytes, or the xref table needs repair
pdf.encryptedIs the PDF encrypted?an /Encrypt dictionary is present
pdf.has-blank-pageDoes the PDF have at least one page that paints nothing visible?at least one page has no painting operator, or only full-page white fills
pdf.all-pages-blankDoes every page of the PDF paint nothing visible?no page has a painting operator other than full-page white fills
pdf.fonts-not-embeddedDoes the PDF use at least one font that is not embedded?a used font has no FontFile, FontFile2 or FontFile3
pdf.has-javascriptDoes the PDF contain JavaScript?a JavaScript action, a /JS entry or a JavaScript name tree
pdf.has-incremental-updateWas the PDF modified by at least one incremental update after its first save?the cross-reference chain has more than one section
pdf.has-non-a4-pageDoes the PDF have at least one page whose visible size is not A4?a page’s crop box differs from 210 × 297 mm by more than 1 mm on a side
pdf.has-rotated-pageDoes the PDF have at least one page with a /Rotate other than 0?a page’s effective /Rotate is 90, 180 or 270
pdf.claims-pdfaDoes the PDF declare PDF/A conformance in its XMP metadata?the catalog’s XMP has a pdfaid:part property; conformance itself is not checked

The exact wording of each question and of both answers is in the plugin’s plugin.toml, and lockfile plugins shows it to the agent. A plugin becomes installable once its tag is released and listed in the marketplace’s signed index.

Installing and updating (console account)

lockfile plugin install pdf.parses@1.0.0        # exact versions only; signatures checked with cosign
lockfile plugin update pdf.parses@1.1.0         # shows the manifest diff; a changed question needs the name typed
lockfile plugin list
lockfile plugin verify [NAME]
lockfile plugin remove NAME

An install checks the signed index, then the signed manifest and binary and their hashes. A running console treats a newly installed plugin as a policy loosening and applies it only after :review.

Unofficial marketplaces

lockfile marketplace add acme --index https://plugins.acme.example/index.json \
  --identity-regexp '^https://github\.com/acme/lockfile-plugins/\.github/workflows/release\.yml@refs/tags/'
lockfile marketplace list
lockfile marketplace remove acme

An unofficial marketplace publishes the same index format, signed keyless by its own CI identity. It needs its name typed when added, is flagged in red on every approval screen and in every agent result (warning: unofficial_marketplace), and never runs in auto mode. Keyless signers only in v0.2: --public-key is refused.

Write a plugin

The plugin contract is the reference. In short:

The manifest

name = "pdf.has-blank-page"
version = "1.0.0"
protocol = 1
question = "Does the PDF have at least one page that paints nothing visible?"
one  = "at least one page has no painting operator, or only full-page white fills"
zero = "every page paints something"
accepts = ["application/pdf"]
limits = { cpu_s = 20, memory = "512MiB", timeout_s = 30 }
  • accepts documents the inputs; the console does not sniff the type, so the plugin checks its own input.
  • Changing question, one or zero needs a new major version, and users type the plugin name to accept it.

The runtime

Plugin runtime
WhatValue
Inputthe file as an inherited read-only fd 3; no path
stdin/dev/null
Environmentempty except LOCKFILE_PLUGIN_PROTOCOL=1; cwd /
FilesystemLandlock: execute and read its own binary only
Networknone (seccomp denies sockets; Landlock denies TCP with ABI ≥ 4)
LimitsCPU seconds, address space, no core, file size 0, few processes, wall-clock timeout then SIGKILL of the group
stderrdrained and discarded; only its byte count is audited

The answer

Write exactly 1 or 0 on stdout (one trailing newline tolerated) and exit with status 0. Anything else is a failure: other or more bytes, nothing, a non-zero exit, a signal, the timeout, an rlimit. Every failure reaches the agent as plugin_error with result: null, which is not a 0; the precise cause goes to the audit only. Exit-code-only answers are not accepted: a panic must never read as an answer.

The marketplace’s sdk/ module gives the fd-3 reader, the answer helpers and a test harness that runs a plugin under the same sandbox on synthetic fixtures.

Publishing

Official plugins go through the marketplace pipeline: a pull request with synthetic fixtures for both answers (no real document is ever committed), CI (vet, staticcheck, govulncheck, contract tests under the sandbox, fuzzing, an import check), a maintainer’s review against REVIEW.md, then a tag <name>/vX.Y.Z whose release workflow builds, hashes and signs the binaries and the manifest with cosign keyless, and a signed index.json.