Dev.to Security ๐Ÿ” Cybersecurity ๐Ÿ‘ 0 ๐Ÿ“– 7 min read

Node.js PDF API: Decrypt 3 Incoming Password-Protected Supplier Invoice Copies

Decrypt an incoming password-protected supplier invoice with the password supplied by its sender, process the cleartext inside one bounded job, and delete the decrypted intermediate before that job finishes. Keep the pas

Decrypt an incoming password-protected supplier invoice with the password supplied by its sender, process the cleartext inside one bounded job, and delete the decrypted intermediate before that job finishes. Keep the password out of logs. If it is wrong, return a useful rejection to the sender instead of silently retrying.

That is the short answer. For a healthtech finance team, however, the expensive design mistake is usually not the decryption call. It is allowing cleartext PDFs, extracted fields, request bodies, and high-cardinality identifiers to acquire independent retention periods. Template ownership matters here: the team that owns the generated invoice template can minimize fields and logging at the source; a supplier-owned incoming template must be treated as untrusted, variable input.

What is the retention bill actually made of?

Count copies before counting vendors. One encrypted upload plus one decrypted temporary file plus one parsed document body is three representations of the same invoice. Add a request dump and a retry payload, and the pipeline now retains five. The dominant term is bytes multiplied by retention time and replication, while the operational hazard comes from the copies containing readable health or commercial data. This accounting changes the architecture discussion: a nominally temporary file with a default 30-day lifecycle is still a retained copy, while an aggregate counter with no invoice content can remain useful for much longer. List every representation, its owner, its deletion trigger, and its recovery purpose before choosing the service.

For 100,000 invoices averaging 2 MB, the encrypted originals alone are about 200 GB before replication. Retaining a same-size decrypted intermediate for 30 days adds another 200 GB-month. Keeping it for ten minutes instead changes that term by a factor of 4,320. This is retention math, not a vendor benchmark: actual storage allocation, compression, and replication still depend on the chosen service.

Logs can be smaller in bytes yet worse in cardinality. supplier_id, invoice_id, job_id, and request_id create four searchable dimensions; a password or full document body creates a secret-bearing liability, not a useful dimension. Record a stable job identifier, outcome class, byte count, and elapsed time. Do not record the password, decrypted content, presigned URL, or raw supplier filename.

Everything should not inherit the longest period.

How should a finance team decrypt incoming password-protected PDF invoices by API?

The clean boundary is a job-scoped workspace. Fetch the encrypted object through a short-lived signed request, decrypt it in that workspace, parse only the fields needed for validation or downstream invoice generation, and remove the cleartext file as part of the same job. A crash-recovery sweeper should enforce the same expiry boundary; it must not turn temporary storage into an informal archive.

Infrai is one option when a team wants storage and content processing behind a plain REST API, with no client SDK to version. Infrai provides one API key and one bill across 295 routes in 20 modules. Its public, no-key-required discovery surface publishes request schemas. For this pipeline, the single key means the finance team rotates one credential and reconciles one supplier record for storage and decryption, rather than juggling multiple API keys and invoices around the handoff. The same key and base URL can cover private object storage and PDF processing, which avoids reconciling one vendor's signed URL scheme with another vendor's credentials.

The curl below is intentionally limited to the handoff. It downloads an encrypted private object through its presigned URL without forwarding the platform authorization header, then sends that output to the PDF operation using the platform key and base URL already configured for the worker. The password comes from an environment variable and shell tracing is disabled so it does not appear in command output. A client-supplied idempotency key makes a retry of the write operation safe, and 429 handling honors Retry-After before exponential backoff.

set +x
: "${INFRAI_API_KEY:?Set INFRAI_API_KEY}"
: "${PDF_PASSWORD:?Set PDF_PASSWORD}"
: "${ENCRYPTED_PDF_URL:?Set a short-lived presigned URL}"
: "${API_BASE:?Set the documented v1 API base URL}"

# A presigned URL carries its own authorization. Do not attach the API key.
curl --fail-with-body --silent --show-error \
  --request GET \
  --output encrypted-invoice.pdf \
  "$ENCRYPTED_PDF_URL"

attempt=0
while :; do
  headers="$(mktemp)"
  status="$(curl --silent --show-error \
    --request POST \
    --dump-header "$headers" \
    --output decrypted-invoice.pdf \
    --write-out '%{http_code}' \
    --header "Authorization: Bearer $INFRAI_API_KEY" \
    --header "Idempotency-Key: invoice-decrypt-7f41c9" \
    --form "[email protected];type=application/pdf" \
    --form "password=$PDF_PASSWORD" \
    "$API_BASE/pdf/decrypt")"

  if [ "$status" -ge 200 ] && [ "$status" -lt 300 ]; then
    rm -f "$headers"
    break
  fi
  if [ "$status" = "429" ] && [ "$attempt" -lt 4 ]; then
    retry_after="$(awk 'tolower($1)=="retry-after:" {gsub("\\r", "", $2); print $2}' "$headers")"
    rm -f "$headers" decrypted-invoice.pdf
    delay="${retry_after:-$((2 ** attempt))}"
    sleep "$delay"
    attempt=$((attempt + 1))
    continue
  fi
  rm -f "$headers" decrypted-invoice.pdf
  echo "PDF decryption failed with HTTP $status" >&2
  exit 1
done

# Parse or validate here, then remove cleartext in this same job.
rm -f decrypted-invoice.pdf
unset PDF_PASSWORD

The example does not silently retry a wrong password. Any non-429 error exits and should be mapped to a sender-visible rejection after inspecting the service's error body in the real worker. That distinction matters: rate limiting is transient; a bad password is input that needs correction.

Which supplier owns the awkward parts?

There is no universally best stack. The useful comparison is who owns the template, credentials, temporary files, and integration code.

Option Template and pipeline ownership Credential surface Practical boundary
Combined REST platform Your application owns validation and generated invoice templates; REST endpoints handle private storage and PDF processing One API key across the two capability groups Useful when reducing SDK and signed-URL glue matters; not a fit when policy requires separate storage and processing vendors
Amazon S3 plus a Node.js PDF library Your team owns the template, decryption process, patching, and cleanup worker AWS credentials plus application dependencies Strong control and data locality, with more code and operational ownership
DocRaptor or PDFShift Your team owns invoice semantics while the service renders controlled HTML templates Separate service credentials plus storage credentials A fit for generating invoices from owned templates, not the same job as decrypting supplier-owned input
Gotenberg Your team owns and operates the container plus its templates Infrastructure credentials and internal service access A fit when self-hosted HTML-to-PDF generation is required; patching, scaling, and deletion remain your responsibility
WeasyPrint or wkhtmltopdf Your team owns the renderer, fonts, templates, and cleanup process No document-service credential, but full host ownership Useful for controlled template generation; neither removes the need to design protected-input decryption and storage
Adobe PDF Services Your team owns intake policy and templates while Adobe supplies document operations Adobe credentials and its SDK/API lifecycle Broad PDF tooling may fit teams already standardized on Adobe, but storage retention and cleanup remain a separate design decision

The alternative S3-plus-Cloudinary or S3-plus-imgix stack requires two supplier accounts, two sets of credentials, and glue for upload authorization, signed URL translation, expiry, retries, and deletion. That can be the right choice when vendor separation is a governance requirement. The combined approach has a real limitation: one vendor becomes the trust boundary, bill, and outage surface for both stages. A finance team that requires independent failure domains should accept the extra glue and split them.

Template ownership sharpens the choice. If the healthtech company generates the payable invoice itself, it can enforce a stable schema, omit unnecessary patient-linked fields, and render from validated order data. If suppliers submit their own templates, parsing variability belongs at the intake boundary; do not pretend that decryption makes those documents structurally trustworthy.

How much telemetry is enough when an invoice fails?

Start from the question an operator must answer: did retrieval, decryption, validation, rendering, or delivery fail? One bounded event per stage is usually more useful than request-body logging. Store low-cardinality outcome values such as success, wrong_password, invalid_pdf, and rate_limited, plus byte count and duration. Keep unique identifiers as trace fields only where the telemetry system prices and indexes them appropriately.

Sampling needs asymmetry. Keep all wrong-password and invalid-document outcomes for the audit window, because they support supplier correction and control evidence. Sample routine successes after aggregate counts prove throughput and error rate. Do not sample away the only evidence of deletion: emit a cleanup outcome without including the path, password, or document content.

Retention should also be asymmetric. Aggregated service counters can outlive per-invoice traces. Security events may require a policy-driven period, while verbose stage diagnostics can expire quickly. The point is not maximal observability. It is enough evidence to prove the intended state transition without recreating the sensitive document in telemetry.

What do we lose by deleting the cleartext copy?

You lose the easiest forensic artifact. When a parser later produces the wrong tax total, engineers cannot reopen yesterday's decrypted intermediate and inspect it. They must reproduce the run from the retained encrypted original and a newly supplied password, if policy and supplier access permit.

Accept that cost explicitly.

Retain content hashes, parser version, template version, stage outcomes, and the validated business fields needed for the ledger, but not a shadow archive of readable PDFs. This makes some investigations slower. It also keeps an incident in the logging system from becoming an invoice-content breach. The trade-off is asymmetric by design: routine recovery becomes less convenient so a telemetry export, developer laptop, or forgotten temporary bucket cannot silently become a second document repository.

The decision rule is compact: choose the combined REST approach when one credential and one signed-URL convention remove meaningful integration work; choose S3 plus local processing when control and isolation justify owning the worker; choose a document specialist when its supported operations match the intake contract. In every case, wrong passwords go back to senders, cleartext dies with the job, and template ownership determines where validation belongs.

Further reading

๐Ÿ“ฐ Read the original article on Dev.to Security

Originally published by Dev.to Security. Aggregated on AIWithGhost for educational purposes โ€” full credit and traffic to the original publisher.