Security
Who may connect, what they can do, and how to take it back — allow-lists, pairing and permissions.
Last updated
PrintLink's security model in one sentence: local connections only by default; a page can only print after the local user approves it; widening network reachability is an explicit console action and can be taken back at any time.
The four checks
A web page's call passes through four independent checks:
| Layer | Checks | Managed in |
|---|---|---|
| Listen scope | Which addresses accept connections | Console → Settings |
| Source allow-list | Which subnets may connect | Console → Security & sites → Network access |
| Pairing | Which pages may call at all | Console → Security & sites |
| Interface permissions | What that page may actually do | Granted at pairing, revocable |
Adding a subnet to the allow-list does not grant pages from that subnet any permission — they still need to reach an allowed state by the rules below.
Two channels: source address and token
Who may connect is decided by two independent channels, and satisfying either one is enough:
┌─ with a token ─────────► the token decides (no IP authorization)
client connects ────┤
└─ without one ──────────► the source address decides (list / confirm)| Transport | With a token | Without one |
|---|---|---|
WebSocket (/ws) | allowed, no IP authorization needed | the source-address rules apply |
HTTP (/rpc) | allowed, the source address is not consulted | refused (PAIRING_REQUIRED) |
HTTP has no "no token but the source is trusted" path: HTTP has no connection, so every request stands alone. Treating the source address as a per-request credential would give any machine on the subnet the same reach as a program the user approved. In one line: a token needs no IP authorization.
Tokens are issued by the user in the console
A token (plk_…) is a standalone credential: it proves identity on its own
and is bound to neither a source address nor a page address. The plaintext
appears exactly once, when it is issued; PrintLink stores only its SHA-256 hash.
Treat the token as a real credential.
Tokens are created in Console → Security center → Tokens, by the user:
- pick the permissions (what it may call) and the transports (where it is valid — WS and/or HTTP, at least one);
- optionally set an expiry and a note;
- the name is required — a token is not bound to any source, so the name is the only thing that tells you which one is for whom;
- the plaintext is shown once: copy it and hand it to the program.
Since v1.0.10 a client can no longer obtain a token by asking
auth.pair used to return a token once the user approved. It now only triggers
source-address authorization: on WebSocket an allowed address is usable on its
own, so the caller is told it does not need a token; over HTTP the user must issue
one in the console first.
The reason is that a token can be copied and carried to another machine — a credential like that should be issued by a person, not by whoever connects first.
Origin is a record, not a gate
A page's Origin is a header the site sends and any non-browser program can
write freely. Using it for admission stops no real attacker while misjudging
"the same app on a different port" or "http became https" as a breach. Origin
is still recorded (the job list's source column and reports read it), but it
neither decides access nor appears in the token list.
What the console lets you do
Security center has two tabs:
- IP authorization: which address may connect. Remote sources outside the lists enter a pending queue; approving can also add "this IP only / this subnet / a larger range". Loopback is always allowed.
- Tokens: which program may connect. Create tokens here, review the ones you issued (name, permissions, transports, expiry) and revoke them one by one. The HTTP access switch also lives here.
Revocation is precise
- Revoking a token immediately drops the connections holding that token; other tokens and IP authorization are unaffected.
- Blocking a source rejects that address outright and it never re-enters the pending queue.
Use the former to cut off one program; do not block a whole source, which would also cut off other well-behaved programs on the same machine.
Listen scope
| Scope | Meaning | Extra requirements |
|---|---|---|
| Local only (default) | Accepts local connections only | None |
| LAN | Binds an explicitly chosen private address | Each address picked from the machine's real NICs |
| Public | Binds an explicitly chosen routable address | A dangerous confirmation plus a narrow allow-list |
- Port, scope, bound addresses and the source allow-list are one transaction: any failure rolls the whole thing back and the old service keeps running.
- Tightening (public → LAN → local) immediately drops sessions that are no longer allowed.
- No built-in TLS. The public scope means plaintext connections; pair it with a VPN or a reverse proxy. A token is not a substitute for TLS.
Source allow-list
Once the listen scope is widened you can configure source IP rules (IPv4 and IPv6):
- Denial wins: a denied source is dropped even if it is also allowed.
- With an allow-list configured, anything not matched is denied.
- Very broad rules need an extra dangerous confirmation.
- The allow-list only controls network reachability; pairing and permissions still apply individually.
- When approving a new LAN site you can tick "this IP / this subnet" to add the source to the allow-list right there.
Which connections count as local
Browser connections always carry Origin; PrintLink's own UI and same-machine
automation do not. A missing Origin is not an identity:
| Connection | Comes from | Result |
|---|---|---|
No Origin | The local machine | Trusted local session with full permissions |
No Origin | LAN or the internet | Refused (REMOTE_NATIVE_CLIENT_DENIED) and audited |
With Origin | Anywhere | Browser rules: allow-list → pairing → permissions |
A reverse proxy must not strip Origin
When a same-machine reverse proxy sits in front of PrintLink it must forward
Origin untouched: PrintLink sees a loopback peer, and once Origin is stripped
every proxied remote connection is mistaken for a trusted local client.
Remote services cannot connect directly yet
No-Origin remote connections are always refused, deliberately: the
service/device token system for remote non-browser clients does not exist yet.
Until it does, use the alternatives listed in
controlled remote access.
Auditing and sanitisation
- Authorisation, refusals, configuration changes and listener migrations are all audited (retention is configurable).
- Audit and diagnostics output contains no document content, plaintext tokens or binary assets — only event types, origins, times and sanitised context.
- Scanning is a high-risk capability, not in the default permission set, and every scan requires desktop confirmation.
The Playground is not a trusted console
The Playground runs code you type. It is sandboxed, but it calls the real local service. Trusted operations — site management, reports, diagnostics — are only available in the desktop console.
Advice for integrators
- Request only the permissions you use; ask for printing and preview separately as needed.
- Keep the listen scope local unless cross-machine printing is genuinely needed; prefer LAN with a narrow allow-list and a precise bound address, and do not start with the public scope.
- Handle the structured "needs authorisation / waiting / permission denied" errors instead of telling users to turn security off.
- Remote service processes should not try to connect without
Origin; use a controlled browser path or a same-machine bridge.
See also
Controlled remote access
What to do when the caller and PrintLink are on different networks: VPN, reverse proxy or opening the public internet, and who is responsible.
Troubleshooting
Nothing prints, it hangs, or the layout is wrong — the order to check connection, authorisation, printers, options and logs.