A real invoice-link failure

An invoice may expose much more than a total. A customer's name, phone number, email, delivery address and purchase details can appear together. The Hong Kong Privacy Commissioner published a case in which an online-store customer changed the final five digits of an invoice link and reached other customers' invoices. The store fixed the problem after intervention and the Commissioner issued a warning. That case gives a direct acceptance question for any new invoice feature: can a customer alter a URL, identifier or request and obtain another person's document? A link that opens successfully for its intended recipient proves only that delivery works, not that access control works.

The central distinction is between knowing an address and being allowed to read the document at that address. An invoice number may be sequential, a random identifier or a long opaque token. OWASP explains that attackers can manipulate object identifiers of any of those forms, and that endpoints acting on an object need an object-level authorisation check. Longer or harder-to-guess identifiers can reduce casual discovery, but they do not turn possession of an identifier into permission. The design should state which person or organisation may access each invoice and which route enforces that rule at the moment of download.

Start by mapping all ways the same bytes can leave the system. There may be a customer-account page, an email link, a support-agent resend action, a mobile app, a PDF preview, a cloud-storage URL and a bulk export. If the account page checks ownership but the preview or attachment endpoint serves a file directly, the policy is still broken. OWASP's authorisation guidance says to deny by default and validate permission on every request. Inventory each route by the actual document it returns, not merely by its button label. Include cache and storage behaviour in that map, because an authorised page can accidentally expose a static file through an unprotected public path.

Decide who may see each invoice

For a consumer purchase, the basic rule may be that the authenticated account linked to the order can obtain its invoice. Business accounts complicate this: the buyer, finance contact and company administrator may have different rights, and a former employee should not retain access simply because their email appears on an old order. Write these cases down before building the endpoint. An invoice belongs to an order, an order belongs to a customer or organisation, and the current requester has a role in that relationship. Do not infer ownership solely from an email address typed into a form or from a customer ID supplied by the browser. The server should derive the requester from a trusted session and evaluate the current relationship to the specific invoice.

A guest checkout needs its own policy. The business may use an authenticated claim flow, a limited delivery channel, or another carefully scoped method. Avoid quietly treating the order number and email address as a universal password for all future documents. Decide what a recipient can do when the message is forwarded, an inbox is shared, or a buyer asks support to resend the invoice. A temporary download link can be useful, but its validity, audience and revocation rules must match the sensitivity of the document. It should be generated only after the system has checked that the requester may receive that invoice; expiration alone is not the ownership check.

Support staff require a separate, auditable route. A staff member may need to locate an invoice to answer a query, yet that does not mean every staff account should browse every customer's purchase history. Specify roles, reason for access, and the allowed action: view, resend, correct or export. When resending, confirm the destination using existing customer records and the company's support procedure rather than accepting a new address in an unauthenticated chat. Log who accessed or sent the document and which invoice was involved. Logs help investigation, but they should not become a second copy of full invoice content or a place to store more personal data than necessary.

Enforce the decision at the document boundary

A robust download flow first resolves the invoice requested, then asks whether the current requester has the permitted relationship to that exact object, and only then retrieves or streams the file. Apply the rule to PDF downloads, browser previews, thumbnails, email-link redemption and API responses. A route must not trust a browser field saying `customer_id` or `organisation_id`; those fields are inputs, not proof. Avoid a public storage bucket or predictable permanent URL that bypasses the application entirely. If a storage service issues temporary links, keep issuance behind the same authorisation decision and test whether a link can be shared or reused beyond its intended scope.

Use a concrete test. Customer A has invoice 2417; Customer B has invoice 2418. First confirm that A can obtain 2417 through the normal account page. Then, while signed in as A, request 2418 by changing the URL or API parameter. Also try the preview and PDF endpoints directly, without entering through the account page. Expected result: no contents of B's invoice, regardless of whether the identifier is sequential or opaque. Repeat with a logged-out browser and with a staff account lacking the relevant role. The numbers are illustrative test data, not a claim about a real breach. Capture responses carefully so the test record does not spread live customer documents.

Test the legitimate edges as well. An authorised company finance user should still be able to retrieve an invoice their role permits; a guest should have a clear way to request help; and a customer whose order was reassigned or refunded should see the correct current state. Check pagination and search results for leaked document names or totals, not just the download itself. When access is denied, return a plain explanation that does not reveal another customer's name or invoice contents. Review logs for repeated attempts to vary invoice IDs. Alerting can help identify abuse, but it should supplement, never replace, the server-side ownership check.

Release and maintain the feature

Before launch, put each access rule into a test matrix: requester type, invoice owner, route and expected result. Include one allowed and one denied case for every route, plus a revoked staff role and a forwarded or expired link where those features exist. Review whether a content delivery network or browser cache could return another user's document without consulting the application again. Store files with access controls consistent with the design. Test against synthetic records so the team can repeat the checks after code changes without handling customer data unnecessarily. Keep the matrix with the feature specification; a verbal assurance that the developer checked security is hard to reproduce.

If testing finds that one customer can open another invoice, disable or restrict the vulnerable route while the issue is investigated. Identify which documents and paths were exposed, preserve relevant logs, correct the access rule, and retest all delivery paths before restoring service. The published Privacy Commissioner case involved a store that removed external access through the vulnerable link and changed its delivery approach. That was the outcome in that case, not a universal rule that every business must email PDFs. The remedy for your system depends on its actual data, access model and incident facts. Seek appropriate privacy and legal advice for a real incident rather than assuming a technical fix resolves every duty.

For an owner commissioning the work, ask the supplier or developer to demonstrate two screens and one denied request: the rightful customer downloads an invoice, an authorised support user follows the approved resend path, and a different customer cannot retrieve the same file by changing a request. Ask them to repeat it through the actual PDF or storage endpoint, not a mock account page. This makes the commercial acceptance test clear. The goal is a useful customer self-service feature with a traceable permission decision for every document, so staff do not have to trade privacy for convenience when volume grows.

A migration or design refresh is a good time to find forgotten paths. Older invoices may have been stored under an earlier URL scheme while new ones use a protected endpoint. A tester should sample both generations and ask whether a retired customer account, merged company account or expired support role can still fetch the older file. Check that file names in email archives and search indexes do not point straight to a public object. When changing providers, verify that moving files did not make the storage container public by default. These are inventory and access questions, not a reason to delete records needed for legitimate accounting or customer service. Document the approved retention and retrieval route for historic invoices, and give support staff a safe way to help customers who no longer have their original email. Otherwise, staff may improvise a link-sharing workaround that recreates the exposure the new feature was meant to remove.

Before you proceed

  • Does every document route check the current requester's right to that exact invoice?
  • Can another customer retrieve a PDF by changing an identifier or opening a direct storage URL?
  • Are guest, company and support permissions written and tested separately?

Sources & further reading