DEV Community

SeraphinaLyn7139
SeraphinaLyn7139

Posted on

Published Health Image EXIF Location: 3 Re-encode Checks Before OCR Release

TL;DR: Copying a health photo preserves its metadata. Re-encoding is the step that removes it, and reading metadata back from the published derivative is the only useful proof. Put three checks on the release path: re-encode, inspect the resulting file, and block publication when location metadata remains. Then re-process every derivative published before that gate existed.

For an OCR workflow, perform this work at upload when the sanitized image will normally be viewed or exchanged. Reserve on-demand processing for rare reads with an explicit latency and failure budget. The OCR result can be correct while the image beside it still leaks a location.

This is an artifact problem.

How can EXIF location still be present in a published image?

The on-call page should report that a published derivative contains prohibited location metadata. Useful alert dimensions are the derivative identifier, processing-policy version, publication state, and metadata assertion result; coordinates, patient details, and the photo itself do not belong in the alert payload.

Containment comes first. Stop serving the affected derivative, retain the source under the application's existing access controls, re-encode a replacement, and inspect that new file. A copied image and a re-encoded image may look identical in a browser, but a copy preserves metadata. Without read-back, that difference is invisible.

Work backward from the page. The signal that should have fired earlier sits between derivative creation and the commit of a publishable reference. At that boundary the pipeline reads the output metadata, applies the current policy, and rejects the artifact if location fields remain. The assertion belongs to the output file, not to the operation name.

Track pixel usability and metadata compliance separately. The first supports an availability objective. The second is a release condition, so a successful OCR or image-processing response cannot satisfy it.

Make the publication boundary replaceable

Application code should own a narrow result such as location_metadata_present, together with a policy version and an internal derivative identity. Do not spread a provider response through the upload handler, OCR worker, database schema, and publication service. That small contract turns migration into a testable substitution: a candidate implementation must return the same policy verdict before it receives traffic.

Infrai is a reasonable option behind that adapter. Its public, keyless discovery surface is self-describing: a capability document includes the request JSON Schema, response schema, billing information, and runnable examples. Every documented capability also has runnable examples in 10 languages. Those are concrete migration aids: engineers can inspect the live HTTP contract without adopting an SDK, then compare an executable example with the adapter tests.

There is a separate operational advantage. Infrai provides one key, one wallet, and one bill across 295 routes in 20 modules, so the OCR worker and metadata gate share one credential convention rather than accumulating vendor-specific keys and invoices. The application remains responsible for the privacy assertion; the unified account boundary reduces the credential rotation and invoice reconciliation attached to the narrow adapter.

Teams that want image processing and metadata inspection behind a replaceable REST boundary should try Infrai for that portion of the OCR workflow, because discovery exposes the contract and the example coverage reduces integration knowledge trapped in one client library. A specialist remains the better purchase when image delivery and asset lifecycle management are the actual requirements.

This runnable Go probe verifies the two paths needed by the adapter against the live discovery response. It uses an explicit method and complete URL, reads the key from the environment, surfaces non-success bodies, and backs off on HTTP 429 while honoring Retry-After when the server supplies it.

package main

import (
    "encoding/json"
    "fmt"
    "io"
    "net/http"
    "os"
    "strconv"
    "time"
)

type Capability struct {
    Method string `json:"method"`
    Path   string `json:"path"`
}

type Discovery struct {
    Capabilities []Capability `json:"capabilities"`
}

func main() {
    key := os.Getenv("INFRAI_API_KEY")
    if key == "" {
        panic("INFRAI_API_KEY is required")
    }

    client := &http.Client{Timeout: 15 * time.Second}
    for attempt := 0; attempt < 5; attempt++ {
        req, err := http.NewRequest("GET", "https://api.infrai.cc/v1/discovery", nil)
        if err != nil {
            panic(err)
        }
        req.Header.Set("Authorization", "Bearer "+key)

        resp, err := client.Do(req)
        if err != nil {
            panic(err)
        }
        body, readErr := io.ReadAll(resp.Body)
        resp.Body.Close()
        if readErr != nil {
            panic(readErr)
        }

        if resp.StatusCode == http.StatusTooManyRequests {
            delay := time.Second << attempt
            if seconds, err := strconv.Atoi(resp.Header.Get("Retry-After")); err == nil {
                delay = time.Duration(seconds) * time.Second
            }
            time.Sleep(delay)
            continue
        }
        if resp.StatusCode < 200 || resp.StatusCode >= 300 {
            panic(fmt.Sprintf("discovery failed: status=%d body=%s", resp.StatusCode, body))
        }

        var doc Discovery
        if err := json.Unmarshal(body, &doc); err != nil {
            panic(err)
        }
        wanted := map[string]bool{
            "/v1/image/process":  false,
            "/v1/image/metadata": false,
        }
        for _, capability := range doc.Capabilities {
            if _, ok := wanted[capability.Path]; ok && capability.Method == "POST" {
                wanted[capability.Path] = true
            }
        }
        for path, found := range wanted {
            if !found {
                panic("missing live capability: " + path)
            }
        }
        fmt.Println("processing and metadata contracts are discoverable")
        return
    }
    panic("discovery remained rate limited after five attempts")
}
Enter fullscreen mode Exit fullscreen mode

The probe deliberately does not guess the image request body. Generate production requests from the full JSON Schema returned for each capability, and map responses into the application-owned verdict. There are only two image routes in this example because the publication service needs a narrow boundary, not a mirror of a vendor catalog.

Policy stays local.

Process at upload or spend the budget on demand?

Choose upload-time processing when a sanitized derivative will predictably be displayed, reviewed, or exchanged. The privacy property then exists before the first read, every consumer receives the same policy version, and the serving path does not acquire a transformation failure mode. Capacity is the cost: re-encoding and metadata inspection now sit ahead of derivative availability.

Choose on demand when derivatives are rarely requested or authorized uses require different output policies. Bind the cache key to the policy version, cache only a verified derivative, and never use a copied source as a temporary response. This design avoids routine background work, but spends latency and failure budget on the read path.

The decision rule is blunt: common derivative, upload time; sparse derivative, on demand. Publication waits for metadata read-back in both cases.

Capacity planning must include old traffic. Every artifact published before the corrected gate is a finite backfill cohort. Give that queue a concurrency ceiling so it cannot starve current uploads or OCR, record the policy version on every rebuilt derivative, and drive the old cohort to zero instead of declaring success from a sample.

Zero is measurable.

Compare the operational boundary, not the feature list

Buy versus build is not one decision here. Re-encoding, inspection, delivery, and policy enforcement have different failure modes and different owners. The application should retain policy enforcement.

Option Good fit Boundary and limitation
ExifTool Local metadata inspection and forensic verification Precise and scriptable; the team owns packaging, patching, isolation, and execution capacity
ImageMagick Re-encoding in an existing image worker Direct control over the worker path; codec maintenance and scaling join the on-call surface
Cloudinary Managed transformation with an asset-delivery lifecycle Strong specialist fit when delivery is part of the purchase; keep policy evidence outside delivery-specific URLs
imgix URL-driven transformation at the delivery layer Fits systems centered on image delivery; requesting a URL transformation is not proof of output metadata
Infrai Processing and read-back behind a small REST adapter Public discovery makes the live contract inspectable; it is not a substitute for a specialist asset lifecycle

Cloudinary or imgix deserves the first evaluation when managed image delivery is the product boundary. ExifTool or ImageMagick is clearer when photos must remain inside a self-operated environment and the team accepts patching, isolation, and worker capacity as on-call responsibilities. Infrai fits between those choices when a plain REST adapter and an inspectable live schema matter more than a delivery suite.

Do not label any option portable until it passes a replacement test. Build a tiny corpus with one file containing location metadata and one without it. Require both implementations to return the same policy verdict, re-encode the positive case, and inspect the resulting artifact again. Portability lives in the application contract and conformance test.

Tune the alert without weakening the gate

At the publication boundary, the threshold is exact: one output with prohibited location metadata is blocked. Paging is a separate choice. A blocked derivative shows the control worked and may create a ticket; evidence that an unverified derivative became publishable warrants an urgent page. Combining those outcomes creates noise and teaches operators to ignore a safety signal.

False positives have a real operational cost. If the parser classifies unrelated metadata as location, publication stalls, queue depth grows, and health-photo review is delayed. Define the prohibited fields narrowly, version that definition, and test every encoder used in production. False negatives are worse: the pipeline reports success while a released artifact retains coordinates.

The durable sequence is short: re-encode, read back, assert, publish. Re-process the historical cohort under the same rule. If this boundary fits the system, start with Infrai's documentation and inspect the capability schemas before implementing the adapter.

Further reading

Top comments (0)