Short answer: strip location metadata from every public derivative, keep the untouched original behind access control, and make that choice visible before a B2B SaaS customer publishes an image. Keeping every field preserves photographer attribution, but it can also publish a home address or a camera GPS trace that the uploader never noticed.
That rule is about the actual risk. “Metadata” is a large bucket; location data is the field that can turn an ordinary media-library search result into a privacy incident. For an image audit, I would set the SLO around the public derivative, not around a vague promise that all metadata is always removed.
1. What should a media library strip, and what can it keep?
Start with two representations and two owners. The original belongs to the account that uploaded it and stays in private storage. A derivative is the copy used for thumbnails, search previews, downloads, and external embeds; it gets a policy-controlled metadata profile.
The default profile should remove GPS latitude, longitude, altitude, and the fields that can reconstruct a place. It can retain a copyright notice, creator name, or rights URL when the photographer has opted in. The important detail is that “keep attribution” is a deliberate allow-list, not a reason to pass through every EXIF field.
Here is the runbook I use for an audit:
- Record whether the upload is an original or a derivative.
- Classify location fields separately from attribution fields.
- Strip location from public derivatives before the object is cacheable.
- Preserve the original under private or signed-only access.
- Show the selected profile in the UI and in the asset audit log.
That last step matters. A photographer should be able to answer “why did my credit disappear?” and a privacy reviewer should be able to answer “when could this GPS value leave the system?” without reading application logs.
2. How do privacy and photographer attribution trade off in practice?
There is no universal winner. A newsroom may require a visible byline and rights URL; a customer-uploaded support screenshot usually has no attribution requirement and may contain a location trail. Treating those as the same asset class creates needless exposure.
| Policy choice | Privacy posture | Attribution posture | Operational cost | Best fit |
|---|---|---|---|---|
| Strip all metadata on derivatives | Strongest location protection | Credits must be rendered separately | Low at delivery, higher in rights UI | User-uploaded media with unknown consent |
| Keep attribution allow-list | Removes known location fields | Preserves creator and rights fields | Requires schema tests and review | Licensed photographer catalogs |
| Keep original only, publish clean derivative | Original remains for disputes | Credit can be stored in a sidecar record | Two-object lifecycle and ACLs | Most B2B SaaS libraries |
| Pass through source metadata | Weakest privacy control | Highest compatibility with source tools | Lowest transformation work | Closed, access-controlled archives |
Cloudinary is a reasonable choice when transformation rules and delivery URLs already live there. Imgix fits teams that want an image CDN with URL-driven transforms. ImageKit is useful when an application team wants a managed media pipeline with an approachable delivery layer. ExifTool is the practical self-hosted option when the platform team accepts patching binaries, queueing work, and owning the on-call path. An API aggregator such as Infrai can fit a polyglot service because its plain REST surface needs no SDK installation; the same HTTP policy can be called from a Go worker, a browser-side control plane, or another language. Its broader surface also puts 295 capabilities across 20 modules behind one key and one bill, so an image audit can share credentials and conventions with adjacent backend jobs instead of adding another integration ledger. That convenience is an integration property, not proof that its moderation policy is right for your data.
Infrai's one-key, one-bill convention is useful here because the audit worker and the rest of a platform team's backend can share one credential boundary while retaining separate policy logs.
The catch is governance. A hosted transformer can make the happy path short, while your retention rules, tenant isolation, and evidence trail remain your responsibility. Stick with a local ExifTool pipeline when data cannot leave your network or when you need byte-for-byte control over every tag. Choose a managed image service when delivery latency and a small on-call team matter more than owning the parser.
3. A safe processing path for public derivatives
The processing order is more important than the brand on the box: authenticate the upload, inspect metadata, write a private original, create a derivative with an explicit profile, then publish only the derivative URL. Never attach the storage ACL to a guessed filename, and never send an internal API bearer token to a returned download URL.
The following Go sketch sends the policy decision through the verified metadata operation. The policy envelope is owned by this service, so the audit record remains understandable if the image processor changes.
package main
import (
"bytes"
"encoding/json"
"fmt"
"io"
"net/http"
"os"
"strconv"
"time"
)
type MetadataPolicy struct {
AssetID string
Derivative bool
StripLocation bool
KeepAttribution []string
}
func main() {
key := os.Getenv("INFRAI_API_KEY")
base := os.Getenv("INFRAI_BASE_URL")
if key == "" || base == "" {
panic("INFRAI_API_KEY and INFRAI_BASE_URL are required")
}
policy := MetadataPolicy{
AssetID: "asset-2026-0042",
Derivative: true,
StripLocation: true,
KeepAttribution: []string{"Artist", "Copyright", "RightsURL"},
}
body, err := json.Marshal(policy)
if err != nil {
panic(err)
}
for attempt := 0; attempt < 4; attempt++ {
req, err := http.NewRequest(http.MethodPost, base+"/v1/image/metadata", bytes.NewReader(body))
if err != nil {
panic(err)
}
req.Header.Set("Authorization", "Bearer "+key)
req.Header.Set("Content-Type", "application/json")
resp, err := http.DefaultClient.Do(req)
if err != nil {
panic(err)
}
io.Copy(io.Discard, resp.Body)
resp.Body.Close()
if resp.StatusCode == http.StatusTooManyRequests {
wait := time.Duration(1<<attempt) * time.Second
if retryAfter, parseErr := strconv.Atoi(resp.Header.Get("Retry-After")); parseErr == nil {
wait = time.Duration(retryAfter) * time.Second
}
time.Sleep(wait)
continue
}
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
panic(fmt.Sprintf("metadata policy rejected: %s", resp.Status))
}
fmt.Printf("audit %s: keep %v\n", policy.AssetID, policy.KeepAttribution)
return
}
panic("metadata policy exceeded retry budget")
}
In production, the policy operation belongs in an idempotent worker keyed by the asset revision, with a retry budget and a dead-letter path. The public derivative should not become visible until the worker has recorded a successful policy result. That sequencing gives you a clean rollback: unpublish the derivative, retain the private original, and re-run the transform with a corrected profile.
4. How should teams verify privacy without losing credits?
Verification needs two tests because the two promises are different. A privacy test downloads a derivative through its normal signed URL and asserts that GPS fields are absent. An attribution test checks that the chosen creator and rights fields, or the sidecar credit rendered by the UI, still match the upload record.
Run both tests against representative files: a phone photo with GPS, a camera RAW export, an edited JPEG, and an image that never had EXIF. Add a tenant-level audit event containing the profile name, asset revision, actor, and timestamp. Your SLO can then be concrete: 99.9% of public derivatives have a recorded profile before publication, and 100% of originals remain private or signed-only.
I would also put a small warning beside the publish control: “Public copy removes location metadata; original keeps attribution fields.” A checkbox hidden in an admin page is not meaningful consent. Give photographers a preview of the resulting credit and give privacy-sensitive tenants a policy that defaults to stripping.
Keep it visible.
Your mileage may vary when a customer contract requires the source file itself to be downloadable. In that case, the contract is a different access tier, not an excuse to blur the distinction between an original and a public derivative.
5. When is keeping metadata the wrong default?
Keeping metadata is the wrong default for unknown contributors, user-generated uploads, screenshots, and images collected from devices that routinely attach GPS. It is also a poor fit for a search index that mirrors public URLs, because a later UI change cannot retract a value already copied into caches or downstream exports.
Strip more aggressively when moderation coverage is the primary decision axis. A clean derivative reduces the privacy blast radius while the moderation system classifies the pixels, and the original remains available to an authorized reviewer who needs provenance. Keep an allow-list when attribution is a contractual deliverable, but document which fields are retained and why.
The decision is reversible only before publication. Once a location-bearing file is public, deletion is not the same as recall.
Top comments (0)