DEV Community

Abhinay Gokul Pulla
Abhinay Gokul Pulla

Posted on • Originally published at blog.perceptova.com

Why a print job should never become a permanent cloud document

 How Docshy keeps files as ciphertext from phone to printer — and removes our copy when the job ends.

Problem in one sentence

Print shops need a temporary document. Messaging apps give them a permanent one. Docshy is a relay: encrypt → store briefly → decrypt only for print → shred.

This post is the architecture behind that relay as it runs in production today (docshy.com, GCP, region asia-south1 / Mumbai).

High-level topology

flowchart TB
  C[Customer phone / app] -->|AES-256-GCM encrypt| U[Ciphertext upload]
  U --> API[Cloud Run API]
  API --> GCS[(Encrypted object storage)]
  API --> R[(Redis — short-lived job state)]
  API --> FS[(Control-plane metadata)]
  API --> KMS[Cloud KMS]
  API -->|Authorized print| S[Shop dashboard]
  S -->|Print| P[Paper]
  S -->|Job done| X[Shred our copy]
Enter fullscreen mode Exit fullscreen mode

Design rule: the platform is a mere conduit. We optimize for short-lived encrypted blobs and explicit deletion — not a document archive.

Actors and trust boundaries

Actor Runs where Trust assumption
Customer app Browser / native shell loading docshy.com Encrypts before upload
API Cloud Run Stores ciphertext; decrypts only on authorized print
Shop dashboard Browser + shop auth Sees plaintext at print time (required to print)
Object storage Blind bucket Ciphertext only
Redis Shared job / queue store Short TTLs — not the durable file store
KMS GCP Wraps per-file data keys

Honest boundary: zero retention on our side ≠ zero visibility at the counter. The shop must render the page to print. Screenshots and photos of paper remain physical-world risks. Architecture removes the WhatsApp-forever + cloud-library path.

Crypto: double envelope

Layer 1 — Client (privacy layer)

  • Algorithm: AES-256-GCM in the browser (Web Crypto)
  • When: Before the upload leaves the device
  • Why: Readable IDs/PDFs are not the default thing we receive from the customer path

Larger files use a chunked format so mobile browsers stay stable; small files encrypt in one shot.

Layer 2 — Platform (infrastructure layer)

After the upload is accepted:

  1. Generate a per-object data encryption key (DEK)
  2. Encrypt the payload again for storage (AES-GCM)
  3. Wrap the DEK with Cloud KMS
  4. Write ciphertext to object storage
  5. Keep short-lived job metadata (and wrapped key material) in Redis; mirror control data in Firestore where needed for ops

Property we care about: dumping job metadata or listing the bucket without KMS does not yield plaintext PDFs. GCM tags also fail decrypt if ciphertext is tampered with.

Request path: upload → queue → print → shred

1. Bind to a shop

Customer scans the shop’s static counter QR (HMAC-authenticated, shop-bound). That is the production default for busy counters. Legacy rotating tokens are still accepted where older dashboards mint them.

2. Upload

Customer encrypts on device → uploads ciphertext with shop binding → platform re-envelopes for storage → a print job lands in the shop queue. Soft-launch caps keep a visit to a small file set (scan again for the next set).

3. Shop queue

Authenticated shop owners see jobs for their shop only. Live queue state sits in Redis so multiple Cloud Run instances stay consistent; Firestore can reconcile when the hot set is empty.

4. Print (momentary plaintext)

On an authorized print action: verify shop ownership → unwrap DEK via KMS → stream decrypt to the shop browser for print.

This is the only intentional plaintext window on the server/shop path. Printers need pixels.

5. Shred

When the job completes (or via cleanup):

  • Delete the encrypted object
  • Clear job / queue / related meta
  • Optionally notify the customer that it’s printed and gone

A scheduled sweep removes aged or already-printed leftovers so failures don’t leave ciphertext for days.

“Shred” means delete from our stores and clear pointers — not marketing-speak for wiping every disk sector. The guarantee: no retained product object after success or expiry policy.

State: what lives where

Store Holds Lifetime intent
Object storage (GCS) Encrypted print payloads Until shred or policy expiry
Redis Queues, job records, short relay temps, shop QR / session counters Minutes–hours (TTLs)
Control plane DB (Firestore) Jobs mirror, shop registry, usage Ops/audit — not a second copy of file bytes
KMS Key-encryption key for DEK wrap Long-lived infra key

Redis = brain of the live job. Object storage = blind bag of ciphertext. Confusing those two is how architecture posts go wrong.

Auth and abuse controls (architecture-relevant)

  • Shop APIs require verified shop ownership
  • Customer upload requires a valid shop QR binding
  • Rate limits and strict browser security headers on the edge
  • Signed shop tokens so QR codes can’t be trivially forged
  • Liveness vs readiness: the process can be “up” while still reporting not ready if Redis is down (fail closed at scale)

DPDPA angle (technical, not legal advice)

DPDPA stresses purpose and not keeping personal data longer than needed. For a print job, purpose ≈ print.

Mechanism Effect
Client + server ciphertext We don’t casually store readable docs by default
Short-lived objects + shred Post-job retention on our storage is designed out
Shop dashboard instead of WhatsApp Stops “staff phone gallery forever” as the transport
Mumbai region Data path stays in-India for latency and trust

What it does not do: replace shop policy, stop someone photographing the screen, or act as a regulator-issued compliance certificate.

Failure and cleanup modes

Failure Behaviour
Print never confirmed TTLs + sweep still remove aged ciphertext/meta
Redis unavailable Multi-instance consistency degrades; readiness fails closed
Partial shred Cleanup reconciles printed/expired objects
KMS unavailable Decrypt/print fails closed — no plaintext fallback

What we’re hardening next

  • Richer session ledger and shop settlement visibility (visit caps already live; full counter UI still maturing)
  • Stronger audit visibility of upload → print → shred for operators
  • Optional B2B surfaces for orgs that already own document workflows

Summary

Docshy’s production architecture is intentionally boring in the right places:

  1. Encrypt on device (AES-256-GCM)
  2. Re-envelope for storage (AES-GCM + Cloud KMS)
  3. Blind object storage + short-lived job state in Redis
  4. Static shop QR to bind upload to a counter
  5. Decrypt only on authorized print
  6. Delete ciphertext and clear job state when done (plus sweep)

That is the technical meaning of our zero-retention print relay: ciphertext for a short job, then removal — not a document CMS.


Abhinay Gokul Pulla

Founder & Director — Perceptova Intelligence Private Limited

docshy.com · perceptova.com

Top comments (0)