Security

Last updated:

This page says what the agent and the service do, what they do not do, where data lives and how long it is kept. It also says what is not claimed. Where something is designed but not yet running, it says so.

What the agent does

The agent is the small program installed on each computer that has printers. It is the only part of SpoolStream that runs inside your network.

  • The agent only dials out, over HTTPS on port 443. A site opens no inbound port and changes no inbound firewall rule.
  • The agent opens no listening port. It long-polls the service, claims a job, and reports back.
  • Each computer's agent has its own identity key, generated when it is enrolled. The private key never leaves the computer, and a stolen credential alone renews nothing.
  • On Windows the agent's credential and key are stored encrypted with DPAPI, in a folder restricted to administrators and the service.
  • A document above 5 MB goes from your system straight to the document store with a short-lived signed link, a smaller one travels inside the API request, and the agent receives a fresh five-minute link only when it prints.
  • A PDF is treated as untrusted input: it is rendered in a single-use child process with size, page and time limits, so a bad file ends the child and not the agent.
  • The agent keeps a durable local ledger and a status outbox, so accepted jobs still print, and report afterwards, through a network drop.
  • The agent tells the service the computer's name, its operating system and architecture, the agent's version, and the printers it can reach (queue name, print server and driver).
  • The Windows agent updates itself: a scheduled task takes the release, its hash and its size from the service, installs a strictly newer final release when the agent is idle, and puts the previous version back if the new one does not run. The installer can turn this off, and the service can refuse an agent older than a minimum version.

What the agent does not do

  • It does not accept a connection from the internet: The agent opens no listening port. It long-polls the service, claims a job, and reports back.
  • It does not print to a network path that arrives in a job. The agent prints only to queues it reported itself: a job is resolved to one of those printers, never to a free-form network path.
  • It does not decide for itself that an uncertain job should print again. When nobody can know whether a page printed, the job is marked uncertain and is never printed again by the system. A person looks at the printer and decides.

Separation between customers

Every table that holds a customer's data carries the organization's identifier, a filter in the application and PostgreSQL row-level security. Our integration tests prove that organization B cannot read or change organization A's data, and receives 404, not 403.

An API key belongs to one organization. A test key sees and acts only on test jobs and on the virtual printer.

No independent test of this separation has been published. No independent security audit has been published. One is planned for the production launch of the hosted service.

Sign-in, keys and the audit trail

  • The dashboard signs people in with email and password plus a one-time code, with Google, or with Microsoft Entra ID, and an organization can require its own single sign-on. Roles are owner, admin, operator and viewer.
  • After 5 failed sign-in attempts an account is locked for 15 minutes, and one-time codes count toward the same limit. An organization cannot yet require its members to use a second factor.
  • A dashboard session ends after 8 hours without activity, and after 7 days at most.
  • API keys and agent credentials are stored only as a keyed hash (HMAC-SHA256 under a secret pepper). A lost key is replaced, never recovered.
  • Second-factor secrets, recovery codes and webhook signing secrets are stored encrypted (AES-256-GCM) under a key kept in the secret store, not in the database. Passwords are stored only as a hash made by ASP.NET Core Identity.
  • Administrative actions and sign-ins are written to an append-only audit trail that the database refuses to alter or delete, except for the scheduled removal of rows older than the retention period.
  • Each audit entry records who acted (a person, an API key or an agent), what was done, when, and the caller's network address and browser or client name; it never records a password, a token or a document.
  • Webhooks are sent only to public HTTPS addresses, through a client that refuses internal and private network addresses.
  • Our rule is that document bytes, file names and external references never appear in logs, traces, exceptions, diagnostics, webhook delivery logs or the audit trail.

Access by our staff

  • The service has no operator console, no platform administrator screen and no way for its staff to sign in as a customer.
  • Operator tasks (setting a plan, linking a billing customer) are one-off commands run with the database owner's login, which row-level security does not restrict; each change they make is recorded in the organization's audit trail.

Where data lives

The hosted service runs in Toronto on DigitalOcean (App Platform for the application, managed PostgreSQL for the database), with Cloudflare for its domain name and Cloudflare R2 for documents. Payments through Stripe and monitoring on Grafana Cloud are planned and not switched on.

The application and the database run in Toronto, Canada. The region of the document store is not chosen: the store (Cloudflare R2) gives no guarantee that documents stay in Canada.

In the designed hosting, Cloudflare carries all the traffic to the service and ends TLS, so it can read API traffic, cookies and the signed document links; the documents themselves are stored in Cloudflare R2.

In the designed hosting, Cloudflare reaches the application over strict TLS, the application reaches the database over TLS that checks the server's certificate and name, and mail leaves over SMTP with TLS.

The providers, what each receives and where, are on the subprocessors page.

What is stored, and for how long

  • Documents. By default a document is deleted from storage 24 hours after its job ends, and 168 hours (7 days) after a failed one, and never while the job is in progress or uncertain. Those two periods are set per organization by the operator of the service, up to one year (8760 hours); an organization cannot change them itself yet.
  • The history of a job. A job's record and its history outlive the document and are kept for as long as the organization exists; no separate retention period applies to them yet.
  • Evidence reports. Evidence reports are not stored: each one is computed from the job's recorded history when it is asked for, so it lasts as long as that history.
  • The audit trail. An organization's audit trail is kept 7 years by default (its owner can choose 1 to 25). Security events that belong to no organization are kept one year.
  • Webhook delivery records. Webhook delivery records are trimmed 30 days after the delivery finishes.
  • Daily statistics and the usage ledger. Daily statistics of each organization's jobs (counts by state and printer, no document) are kept 25 months; the usage ledger is kept until the organization is erased.
  • A registration that is never verified. A registration that is never verified is deleted after 24 hours.
  • A member who is removed. Removing a member deletes that person's account, sign-in methods and second factor; the jobs they sent stay with the organization and the audit trail keeps the account's identifier.
  • An organization. Deleting an organization erases everything, jobs and their history included, 30 days after the owner asks; the owner can restore it during those 30 days. One thing is kept: if the organization was billed through Stripe, its Stripe customer number, with the date of the erasure and nothing else of the customer in our database, so that a late message from Stripe is recognised and ignored. When the erasure is carried out, the organization's subscriptions at Stripe are cancelled, so Stripe stops billing it; that number is never linked to an organization again. Only an owner can delete an organization, after signing in again and typing the organization's name; the other members are signed out at once, the owners are reminded 7 days and 1 day before the erasure, and an export of the audit log (CSV and JSON, with a manifest and SHA-256 sums) is prepared for them to download until then.
  • After an erasure. After an erasure, SpoolStream keeps a security event that names the erased organization, for one year. The link to the organization's customer record at Stripe is deleted with the organization; only the Stripe customer number is kept, with the date of the erasure, while Stripe keeps its own record.

Backups and incidents

Backups are designed and their figures are proposals not yet accepted: the database can be restored to a point in time and is copied to an encrypted dump kept 35 days in another account and region; documents are deliberately not backed up. The restore procedure is designed to erase again, before the service reopens, every organization that was erased after the backup was taken.

Monitoring

Service monitoring (metrics, traces and logs) is planned on Grafana Cloud and is not set up yet. Its metrics are never about a person or an organization, and its logs are redacted.

What is not claimed

  • SpoolStream holds no security or compliance certification, and this site claims none.
  • printed means the operating system reported the print complete. It is not proof that paper came out.
  • Every job keeps an append-only history, and a sealed evidence report can be downloaded for it. The seal is tamper evidence, not a qualified electronic signature.

Reporting a problem

Write to contact@spoolstream.ca and say what you found and how to reproduce it. Please do not test against an organization that is not yours; the rules are in the acceptable use policy. This site does not promise a response time.

Questions about this page: contact@spoolstream.ca