Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

web-mutator

Client-side HTML mutation engine.
Rewrites your live DOM on every load so scrapers, headless browsers, and AI vision models see a different, hostile document each time — with zero visible change to real users.

version license size deps


Table of contents


Why

The HTML your server sends and the DOM a browser renders don't have to match. web-mutator runs once the page is live and renames, re-wraps, and noises up the DOM — tags become custom elements, classes and IDs are re-hashed, decoy nodes and honeypots are sprayed in, and text can be ciphered. Native DOM APIs (getElementById, querySelector, classList, …) are transparently patched so your own code keeps working against the original names.

The result: a scraper that keys off .price or #contact-form, a headless crawler diffing markup across loads, or an OCR/vision model reading the page — all get a moving target. Humans see nothing change.

Not a security boundary. Anything shipped to the browser can ultimately be read from the browser. web-mutator is a cost-raiser against automated collection, not DRM.


Quick start

<!-- configure first (optional) … -->
<script>
  window.WebMutatorConfig = {
    classes: { prefixes: ['mk-', 'btn-', 'card-'] },
    vision:  { ss_detector: { enabled: true } },
  };
</script>
<!-- … then drop in the bundle. It auto-inits on DOMContentLoaded. -->
<script src="dist/web-mutator.iife.min.js"></script>

ESM:

import WebMutator from 'web-mutator';
WebMutator.init({ /* config */ });

Layers

Every layer is independently toggleable. The first seven ship on; the vision and cipher layers are opt-in.

# Layer What it does Default
1 Tag Mutator Renames HTML tags to random custom elements
2 Class Mutator Re-hashes CSS class names (DOM + stylesheets)
3 CSS Var Mutator Renames --custom-properties
4 Noise Injector Fake classes, decoy attributes, comments, honeypots
5 ID Mutator Re-hashes element IDs
6 Structural Noise Random wrapper elements & hidden decoys
7 Attr Name Mutator Renames class/id/src attrs; patches DOM APIs
8 Vision Poison Unicode marks + CSS micro-transforms vs. AI OCR opt-in
9 SS Detector Animation-heartbeat headless-browser detection opt-in
10 Text Cipher XOR-encodes text nodes, decodes in the live DOM opt-in

Plus a NoScript Guard that gates the page when JavaScript is disabled.


Compatibility & caveats

web-mutator is most effective on sites that ship their own static CSS. A few environments need specific layers turned off — know your stack before enabling everything:

  • Utility-CSS frameworks (Tailwind, UnoCSS, …). These express layout through classes (flex, grid, hidden, block). The Tag Mutator forces a display value per element and will override those utilities, so flex/grid containers collapse. Turn tags off on utility-CSS sites.
  • Runtime CDN JIT (cdn.tailwindcss.com). It regenerates CSS from live class names and will fight the Class Mutator. Precompile your CSS to a static file first, or leave classes off.
  • Heavy inline-JS / third-party widgets (lightboxes, map SDKs, Alpine, …). The API patch keeps your selectors working, but widgets that cache elements or use exotic lookups can still trip. If a widget misbehaves, disable ids/attrNames and keep the non-structural layers.
  • observeDynamic on a non-SPA. The observer re-processes nodes the noise layers inject, which can loop. Leave it off unless you have a true SPA and have excluded injected nodes.

A safe, always-non-breaking starting point is noise + structure only:

window.WebMutatorConfig = {
  observeDynamic: false,
  layers: {
    tags: false, classes: false, cssVars: false, ids: false, attrNames: false,
    noise: true, structure: true,
  },
};

This mutates the DOM every load (decoys, honeypots, comments) without touching a single real tag, class, or ID — so nothing can break. Add the heavier layers once you've confirmed your stack tolerates them.


Configuration

window.WebMutatorConfig = {
  // ── Global ───────────────────────────────────────────────────────
  enabled:         true,   // master switch
  seed:            null,   // null = random per-session
  observeDynamic:  true,   // MutationObserver for SPA-injected nodes
  refreshInterval: 0,      // ms; 0 = off
  silent:          true,   // suppress console warnings

  // ── Per-layer toggles ────────────────────────────────────────────
  layers: {
    tags: true, classes: true, cssVars: true, noise: true, ids: true,
    structure: true, attrNames: true,
    visionPoison: false, ssDetector: false, textCipher: false,
  },

  // ── Layer 1: Tag Mutator ─────────────────────────────────────────
  tags:    { exclude: ['table', 'form'], cleanScripts: true },
  // ── Layer 2: Class Mutator ───────────────────────────────────────
  classes: { prefixes: ['mk-', 'btn-'], ignore: ['js-hook'] },  // null = all

  // ── Layer 8: Vision Poison ───────────────────────────────────────
  visionPoison: { enabled: true, selectors: ['.price', '.phone'], mode: 'both' },

  // ── Layer 9: SS Detector ─────────────────────────────────────────
  vision: {
    ss_detector: {
      enabled: true, with_animation_stop: true,
      blur_exclude: ['header', 'nav'],
      on_detect: (reason, ts) => console.log('headless:', reason),
    },
  },

  // ── Layer 10: Text Cipher ────────────────────────────────────────
  textCipher: { enabled: true, key: null },   // null = random per-session

  // ── NoScript Guard ───────────────────────────────────────────────
  noScript: { enabled: true, title: 'JavaScript Required' },

  // ── User plugins: (root, maps, engine) => void ──────────────────
  plugins: [ (root, maps) => console.log('tag map:', maps.tags) ],
};

Runtime API

const wm = window.__webMutator;

wm.findById('price-card');        // resolve by ORIGINAL (pre-mutation) name
wm.findByClass('contact-form');
wm.getMaps();                     // { tags:{…}, classes:{…}, ids:{…}, … }
wm.getLayer('textCipher');        // a specific layer instance
wm.destroy();                     // undo mutations & restore native DOM APIs

Build outputs

File Format Use
dist/web-mutator.iife.min.js IIFE (min) <script> drop-in, production
dist/web-mutator.esm.min.js ESM (min) import in a bundler
dist/web-mutator.cjs.js CJS require() / Node / SSR
dist/chunks/*.js ESM Standalone per-package chunks

Non-minified .iife.js / .esm.js are emitted too for debugging.


Monorepo layout

packages/
  core/         config schema, seeded RNG, shared helpers
  mutators/     Layers 1–7 (tags, classes, css-vars, noise, ids, structure, attr-names)
  vision/       Layers 8–9 (vision poison, headless detector)
  cipher/       Layer 10 (text cipher, char substitution)
  guards/       NoScript guard
  web-mutator/  umbrella package: engine, loader, public API

Each package is independently importable under the @web-mutator/* scope.


Development

npm install          # Node 18+, npm workspaces
npm run build        # all dist outputs
npm run build:watch  # rebuild on change
npm test             # jest unit tests
npm run lint         # eslint

License

MIT © qiyascc

About

Client-side HTML mutation engine — rewrites the live DOM every load so scrapers, headless browsers, and AI vision models see a hostile, ever-changing document, with zero change for real users.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages