A public form that never talks to the private network still produces data the private network has to judge. The tunnel only changes who dials whom. Everything after the download is an import decision, and it belongs to the private side. This walkthrough follows one submission from the browser to a committed internal record.

Start Where the Submission Lands

The browser posts to a public endpoint, which writes a row into a controlled intake store and returns nothing more than an acknowledgement. That store is the handover point, and it should be the only thing the public tier can write. Keep the raw payload there unchanged, together with the time it arrived and a stable record identifier.

A private worker then pulls pending rows over the tunnel and decides what to do with each one. It runs under its own identity, which is the subject of the next section.

curl --silent --fail \
  --cert /etc/importer/client.crt --key /etc/importer/client.key \
  "https://intake.corp.internal/api/pending?limit=20" \
  -o /var/lib/importer/pending.json

Give the Importer Only the Authority It Needs

The public web process and the identity that writes into the internal application should be two different principals. A public server needs no internal administration merely because it has to deliver form data, and a single shared account for both sides turns any web bug into an internal write. Issue the worker its own credential, scope it to the one import operation, and record which principal performed each import. That separation is what makes the tunnel boundary meaningful rather than decorative.

Restrict which source the worker may read from as well. If the implementation uses a queue or a transfer service, that service keeps its own authentication and access policy, because the VPN does not authenticate anything inside the payload.

A short-lived function is not exempt from the same review. While it runs, its network position and its credentials still grant authority, and a shorter lifetime only narrows the window. Lifetime is not a substitute for deciding which operations the identity may perform.

Validate Before Anything Internal Changes

Public form fields are attacker-controlled text until proven otherwise, and OWASP's input-validation guidance counts backend feeds among untrusted sources for exactly that reason. An encrypted transfer says the bytes arrived intact, not that they describe a permissible request. Parse and validate ahead of any privileged step, so a bad row never reaches a code path that can act.

Define the allowed fields, their formats, their sizes and the business rules that must hold. A date can be well formed and still be wrong for the operation being requested, and a payload that names an internal account or an approval status must not be able to grant itself either.

Send rejected rows to a review outcome instead of guessing a correction or forwarding them to a shell or a raw database interface. Log the record identifier and the failed rule, not every field the applicant typed.

Make a Retry Point at One Operation

If record submission-1042 is pulled twice because the first acknowledgement was lost, the worker needs a durable way to recognise the source record and its committed result. A schedule that runs the job once does not establish that each row was applied once. Duplicate delivery is normal, so the design has to absorb it.

Use the internal application's own transaction and duplicate-handling mechanism, and keep the source identifier, the outcome and the acknowledgement in one coherent sequence. Acknowledge only after the intended operation is committed or durably assigned, and never delete the intake row just because the bytes were downloaded.

Then prove it with three fixtures: a valid row, a malformed row, and the same controlled source identifier delivered twice. Each one goes through the same endpoint the worker uses.

for f in fixtures/valid-submission.json fixtures/bad-date.json; do
  curl --silent --fail --cert /etc/importer/client.crt \
    -X POST --data @"$f" https://intake.corp.internal/api/records
done
curl --silent --fail --cert /etc/importer/client.crt \
  -X POST --data @fixtures/valid-submission.json https://intake.corp.internal/api/records

The first two rows must end in different internal states, and the third must return the identifier already committed for that source record rather than creating a second one. Anything else means the import is not yet repeatable.

Zvinyorwa zvakafanana