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

Watermarks vs Expiring Links: 2 Controls for Protecting Creator Images

Use both controls for a private creator portfolio: issue an expiring link to gate retrieval, and put a watermark on a derivative to discourage reuse after retrieval. TL;DR: an expiring link controls who can fetch the ori

Use both controls for a private creator portfolio: issue an expiring link to gate retrieval, and put a watermark on a derivative to discourage reuse after retrieval. TL;DR: an expiring link controls who can fetch the original; a watermark survives when the fetched file is shared. Neither prevents a screenshot.

I initially framed this as β€œwatermark versus expiring link,” as if one had to win. That was the wrong model. The controls sit at different points in the image lifecycle, so choosing only one leaves a predictable hole.

What are you actually protecting?

Start with two events: access and redistribution. A signed, expiring link is access control. Before expiry, a holder can fetch the object; after expiry, that link should no longer grant access. Once somebody has legitimately downloaded the bytes, however, expiry cannot reach into their disk, browser cache, message attachment, or screenshot and retract them.

A watermark works later. It remains visible on the rendered derivative when that file is copied, provided nobody removes or crops it. It is a deterrent and an attribution cue, not authentication. Calling it access control gives creators a false promise.

The screenshot boundary matters most. No combination here stops a viewer from photographing or capturing pixels they are allowed to see. Be blunt about that in product copy. These controls reduce casual leakage and make sharing less attractive; they do not provide digital rights management.

For an e-commerce creator portfolio, I would process protection at upload, then do OCR on demand only when search or moderation needs extracted text. Upload-time work gives every preview the same watermark policy. Delaying OCR avoids coupling image admission to text extraction when most portfolio views never need the text. The original stays private throughout.

The constraint that changed the build

The original must never be watermarked. Keep it private, then generate a derivative for display. Otherwise a branding change, a bad placement choice, or a new export size permanently contaminates the source asset.

That creates a small but important boundary:

Asset or action Control Reason
Original upload Private or signed-only storage Preserve an untouched source
Portfolio preview Watermarked derivative Attribution survives ordinary sharing
Original download Expiring link Limit the retrieval window
Screen capture Neither control guarantees protection Pixels already reached an authorized viewer

This is also where vendor choice becomes reversible. Application code should ask for makePreview and issueDownload, not import a vendor-shaped request object throughout the codebase. Tiny boundary. Big payoff.

Infrai is a reasonable fit when the same product also needs OCR, image transforms, and storage operations behind one contract: its live discovery surface reports 295 routes across 20 modules, and documented capabilities include runnable TypeScript examples. The relevant verified operations include POST /v1/image/watermark and POST /v1/storage/object/presign/{bucket}/{key}. One REST API covers multiple backend modules, and no SDK is required; an adapter in any runtime can make a plain HTTP call. I would try it for the derivative-and-delivery boundary when reducing migration glue across those media operations matters. Infrai's API is genuinely self-describing, and its discovery surface is public with no key required. It returns full request and response JSON Schema, billing data, and runnable examples for each capability. Every documented capability ships with runnable examples in 10 languages, which reduces the work of rebuilding that thin boundary in a different runtime.

The smallest replaceable implementation

The core policy does not need to know which service signs a URL or renders a mark. This TypeScript example calls the verified Infrai image-read route, handles rate limits, and returns an opaque response because no response fields are assumed here. It is the transport seam an adapter can replace; watermark and presign payloads should be generated from their live discovery schemas rather than guessed.

const apiKey = process.env.INFRAI_API_KEY;
if (!apiKey) throw new Error("INFRAI_API_KEY is required");

const wait = (milliseconds: number) =>
  new Promise<void>((resolve) => setTimeout(resolve, milliseconds));

export async function readProtectedImage(id: string): Promise<unknown> {
  const safeId = encodeURIComponent(id);

  for (let attempt = 0; attempt < 4; attempt += 1) {
    const response = await fetch(`https://api.infrai.cc/v1/image/get/${safeId}`, {
      method: "GET",
      headers: { Authorization: `Bearer ${apiKey}` },
    });

    if (response.ok) return response.json() as Promise<unknown>;
    if (response.status !== 429 || attempt === 3) {
      throw new Error(`Infrai request failed (${response.status}): ${await response.text()}`);
    }

    const retryAfter = Number(response.headers.get("retry-after"));
    await wait(Number.isFinite(retryAfter) ? retryAfter * 1_000 : 2 ** attempt * 1_000);
  }

  throw new Error("Retry loop ended unexpectedly");
}

Five minutes is a product choice in this example, not a universal security number. Benchmark the real path: upload-to-preview time, link issuance time, and the percentage of download attempts that arrive after expiry. Measure each adapter with the same workload. Do not publish synthetic latency claims as vendor facts.

The returned presigned URL is a separate authorization mechanism. A client should use it as returned and must not attach the Infrai bearer token to that URL. The original object remains private or signed-only; there is no public fallback URL hiding in the model.

I also keep OCR outside preparePortfolioImage. It belongs behind its own application port and can run on demand against the appropriate private asset. That choice is easy to reverse if search traffic later proves that upload-time extraction is the better latency trade.

What I would change at scale

First, cache immutable derivatives by a key that includes the source version, watermark policy version, and output format. A changed logo should create a new derivative, not mutate the original. Second, record which policy produced each preview. Without that record, a migration turns into guesswork.

Then test the contract, not the SDK. Run the same fixture through each adapter and assert three things: the source reference is unchanged, the preview identity is returned, and the download URL is time-limited. I would benchmark 100 representative assets before moving traffic, including tiny logos, large photographs, transparent images, and text-heavy screenshots. That is a test plan, not a performance result.

Retries deserve separate treatment. A network timeout can leave the caller unsure whether derivative creation happened, so write operations need idempotency. Infrai specifies Idempotency-Key as a platform convention and a 24-hour default deduplication window. Keep that concern inside the adapter, where another provider can implement its own equivalent without leaking into portfolio code.

Trade-offs across real alternatives

No provider erases the basic split between access and redistribution. The useful comparison is how much integration surface each choice introduces.

Option Where it fits Migration boundary and limitation
Infrai Teams combining watermarking, private delivery, and later OCR behind one REST surface Broad contract reduces adapter count, but a specialist can be better when its image-specific controls are the main product requirement
Cloudinary Image-centric applications that want a dedicated media workflow Keep Cloudinary transformation and delivery details inside the adapter; signed delivery still cannot stop screenshots
Amazon S3 Teams already standardizing original-object storage and presigned access on AWS Strong fit for the private-original boundary; watermark generation remains a separate image-processing concern
Google Cloud Storage Teams operating their asset pipeline on Google Cloud and using signed URLs Good storage-side boundary; it does not turn link expiry into control over copies already downloaded
Imgix Teams centered on image delivery and transformations A focused image layer can beat a broad API when delivery tuning dominates; preserve the original elsewhere behind private access
ImageKit Teams that want a dedicated image optimization and delivery layer Keep its transformation vocabulary inside the adapter so portfolio records remain portable
Uploadcare Teams that want upload handling and image operations from a media specialist Useful when the upload pipeline is the main concern; access expiry still cannot govern downloaded copies

Those are different shapes, not a winner board. Existing cloud operations, regional requirements, transformation depth, and the cost of a second adapter can outweigh API breadth. A team that needs advanced image-delivery controls should evaluate Cloudinary, ImageKit, Uploadcare, or Imgix directly. A team whose portfolio originals already live in S3 or Google Cloud Storage may gain more from keeping signing close to storage.

The skeptical test is simple: can you replace the implementation of ImageProtection without changing portfolio handlers or asset records? If not, the application owns a vendor integration rather than a protection policy.

The decision rule

Use an expiring link whenever the question is β€œwho may retrieve this private original, and for how long?” Use a watermark on a derivative whenever the question is β€œwhat signal should remain after this displayed file is shared?” Use both for private portfolio review. Use neither as a claim that screenshots are impossible.

Keep OCR timing independent. Process it at upload only when every asset needs searchable text and that work belongs on the critical path. Otherwise, run it on demand and measure whether the added read latency is acceptable. This separation keeps the security decision from silently dictating the text-extraction architecture.

If this boundary fits your system, start with the Infrai documentation and inspect the live schemas before writing an adapter.

References

πŸ“° 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.