惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Microsoft Azure Blog
Microsoft Azure Blog
J
Java Code Geeks
量子位
腾讯CDC
C
Check Point Blog
小众软件
小众软件
IT之家
IT之家
I
InfoQ
Hugging Face - Blog
Hugging Face - Blog
Stack Overflow Blog
Stack Overflow Blog
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
GbyAI
GbyAI
Apple Machine Learning Research
Apple Machine Learning Research
大猫的无限游戏
大猫的无限游戏
博客园_首页
S
SegmentFault 最新的问题
The Cloudflare Blog
阮一峰的网络日志
阮一峰的网络日志
aimingoo的专栏
aimingoo的专栏
P
Proofpoint News Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
Google DeepMind News
Google DeepMind News
T
Tailwind CSS Blog
Martin Fowler
Martin Fowler

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant
How I built a browser watermark library that survives Dev...
Mohammed Has · 2026-05-09 · via DEV Community

Identifying the leaker after a screenshot escapes, when the obvious tricks no longer work.

The problem

Last year, I watched a customer of ours forward a screenshot of an internal dashboard to a vendor. The screenshot showed financial figures that should have stayed inside the company. Nobody could prove who took it.

If every pixel of that screen had carried that user's email or ID in faint diagonal text, we would have known the answer in five seconds.

That is the whole idea behind a watermark. It does not prevent leaks. It makes leaks attributable.

Why naive watermarks fail

The simplest version is a <div> you put on top of the page:

<div class="watermark">alice@acme.dev</div>

Enter fullscreen mode Exit fullscreen mode

The trouble: anyone who knows F12 opens DevTools and deletes the <div> in two clicks. Or overrides its CSS. Or screenshots the page before your watermark script runs.

The naive watermark catches honest users. It does nothing against the people who actually want to leak.

What I wanted

A library that:

  1. Renders the user's identifier (email, user id, whatever) tiled across the page.
  2. Re-creates itself if someone removes it from the DOM.
  3. Resists CSS overrides that try to hide it.
  4. Optionally redirects users who open DevTools to a "you do not have access to this" page.
  5. Logs every attempt to my server. Even if every browser-side defense gets bypassed, I want a record of who tried what, when.

That last one is the part most teams forget, and it is the most important.

What I built

watermark-shield is a small library on top of watermark-js-plus, which handles the actual canvas rendering very nicely. On top of it I added:

  • A self-healing overlay that comes back if removed.
  • A MutationObserver that catches CSS injection attempts on the watermark.
  • A DevTools-detection layer that fires a callback before it redirects.
  • An audit hook so the server can log attempts.

Framework-agnostic core in plain JavaScript, with bindings for Angular, React, and Vue 3.

The whole thing is roughly 6 KB gzipped, with another 6 KB lazy chunk if you turn on the DevTools guard.

Two-line install

npm install watermark-shield

Enter fullscreen mode Exit fullscreen mode

import { WatermarkShield } from 'watermark-shield';

new WatermarkShield({
  content: user.id,
  protect: { devtool: true },
}).create();

Enter fullscreen mode Exit fullscreen mode

You get a tiled watermark of the user's id, an active DevTools detector, and the always-on passive protections. Sensible defaults; nothing else to configure.

A realistic production setup

new WatermarkShield({
  content: user.id,
  fontSize: '1.5vw',                              // scales with screen
  fontColor: { light: '#000', dark: '#fff' },     // light/dark aware
  globalAlpha: 0.18,                              // survives JPEG compression
  protect: {
    devtool: true,
    devtoolUrl: 'https://yourapp.com/security/blocked',

    onDevtoolOpen: (detectorType) => {
      // The user is about to be redirected. Fire and forget.
      // No userId in the body. The server resolves the user from
      // the session cookie or JWT, never from what the client sends.
      navigator.sendBeacon('/api/audit/devtool', new Blob(
        [JSON.stringify({
          detectorType,
          url: location.href,
          ts: Date.now(),
        })],
        { type: 'application/json' },
      ));
    },
  },
}).create();

Enter fullscreen mode Exit fullscreen mode

Three things worth pointing out.

Identity belongs to the server. The audit body has no userId. The server resolves who the user is from the session cookie or the Authorization header. If you trust the client to tell you who is logged in, an attacker can forge the audit log too.

The audit log is the real backstop. A determined attacker can defeat any browser-side defense (custom Chromium build, an extension that strips watermarks, JS overrides). But the beacon already left their browser before they got that far. The server log is what gives you attribution that survives sophisticated bypasses.

Always devtool: false in development. Otherwise your engineers cannot debug their own app. Gate it with import.meta.env.PROD or environment.production.

Framework bindings

// Angular
import { WatermarkShieldService } from 'watermark-shield/angular';
this.shield.create({ content: user.id, protect: { devtool: true } });

// React
import { useWatermarkShield } from 'watermark-shield/react';
useWatermarkShield({ content: user.id, protect: { devtool: true } });

// Vue 3
import { useWatermarkShield } from 'watermark-shield/vue';
useWatermarkShield({ content: user.value.id, protect: { devtool: true } });

Enter fullscreen mode Exit fullscreen mode

Same lifecycle in every binding: create, update, destroy.

The goal is not to prevent capture. It is to make every captured image carry the leaker's name. That changes the calculation from "no one will know" to "of course I will be caught."

Try it

npm install watermark-shield

Enter fullscreen mode Exit fullscreen mode

Source and docs: https://github.com/mhwazrah/watermark-shield