Security and signing
A printer on a shop counter should print only what the shop's own software sends. JodaPrint enforces this at several levels.
Request signing (default)
Each organization creates one or more Ed25519 signing keys in the dashboard. The private key is shown once and stays on your backend; the public key goes to your agents with their configuration.
The browser SDK never holds a private key. For each request it calls your signer, which asks your backend to sign (see First print from the browser). The agent then checks:
-
the signature, over this string:
v1{method}{orgId}{ts}{nonce}{sha256_hex(canonical_json(params))}Canonical JSON is RFC 8785 (JCS): keys sorted, no spaces.
@jodaprint/nodeand@jodaprint/protocolimplement it. -
the timestamp: a signature older than 300 seconds is refused (
SIGNATURE_EXPIRED); -
the nonce: a nonce seen in the last 10 minutes is refused (
NONCE_REUSED).
Your signing endpoint decides who may print: signRequestHandler requires an authorize callback, and can limit the methods it signs.
To rotate a key, create a new one, deploy it to your backend, then revoke the old one.
Allowed origins
The agent refuses WebSocket connections from pages whose origin is not in the organization's list (ORIGIN_NOT_ALLOWED). With dev mode on, http://localhost:* is allowed too; turn it off in production.
Unsigned mode (optional)
For small businesses without a backend, an organization can turn on unsigned mode. When an allowed origin sends an unsigned request, the tray asks the person at the computer: Allow, Block or Always allow. The choice is stored per site. It is off by default.
Local connection
- The agent listens only on
127.0.0.1and::1, onwss://localhost:8182(then 8283, 8384, 8485). - At install, the agent creates a certificate authority for this machine only, limited to
localhostnames, and trusts it. The CA private key never leaves the machine; no certificate or key is shared between customers.
Devices and the cloud
- At activation each agent creates an Ed25519 device key; it proves its identity on each connection by signing a challenge from the cloud.
- The agent always opens the connection (outbound). The cloud never connects into your network.
- The agent only prints: no remote shell, no reading files outside its own folders, no running programs except its bundled PDF helper.
- Updates are signed: the agent verifies each release with the JodaPrint release key embedded in it, and rolls back if a new version does not start.
Cloud
- API keys (
jp_live_…,jp_test_…) are stored as SHA-256 hashes; only the first characters are kept to recognize them. Give each key only the scopes it needs. - Tenant isolation: every API key, device, printer and job belongs to exactly one organization, and every query filters by it.
- Job content is encrypted at rest (AES-256-GCM) and deleted after the organization's retention period (default 7 days; 0, 1, 7, 30 or 90). Job metadata stays for the history.
- Rate limits: by default 20 jobs per second per device and 100 requests per second per API key.
- Webhooks are signed with HMAC-SHA256: see Webhooks.
Threat model and reporting
The full threat model (a STRIDE table per component, with the tests that cover each threat) is docs/security.md in the JodaPrint repository. Report vulnerabilities privately to [email protected] (see SECURITY.md).