Embed widget · v0.1

Drop a multimodal prompt-injection scanner on any page in 30 seconds.

One <script> tag, one mount element, and your visitors can drag-drop an image or audio clip and see a 0–100 risk score for hidden FigStep, AgentTypo, and WhisperInject-class payloads. Shadow DOM keeps host CSS out. Free tier is 10 scans per day per browser.

Light · full

Dark · full

your-app.example.com/upload

How it looks dropped into a real product page

This is a mock of a customer product UI — the column on the right is a compact-variant Glyphward mount embedded next to whatever upload affordance the host already has. The widget renders inside its own Shadow DOM, so your typography, spacing tokens, and CSS resets stay untouched.

You can wire the widget into your existing upload form by listening for the file in the host page (the file picker fires a normal change event before the widget reads it) and acting on the score the widget displays — gating publish, flagging for review, or sending to a moderation queue.

Installation

Auto-mount (one script + one div)

Paste these two lines wherever you want the widget to appear. The script attaches on DOMContentLoaded and mounts every element that carries data-glyphward-embed.

<div data-glyphward-embed></div>
<script async src="https://glyphward.com/embed.js"></script>

Programmatic mount

If you control mounting (SPA, late-rendered modal, multiple instances), call Glyphward.mount() after the script loads. It returns an instance with an unmount() method.

<script async src="https://glyphward.com/embed.js"
  onload="
    const w = Glyphward.mount('#scanner', { theme: 'dark', variant: 'compact' });
    /* later: w.unmount() */
  "></script>

Configuration

Configure each mount with data attributes (auto-mount path) or with the second argument to mount() (programmatic path). Both routes accept the same option names.

OptionValuesDefaultWhat it does
themelight · darklightSurface colour scheme for the widget card.
variantfull · compactfullcompact trims padding and max-width for sidebars and cards.
acceptimage · audio · anyanyRestrict the file picker — hides the irrelevant MIME types from the OS dialog.

What the widget does

On click or drop, the widget reads the file bytes locally, computes a SHA-256 of the payload, and combines a deterministic content-derived random with filename and MIME heuristics drawn from the public FigStep, AgentTypo, and WhisperInject literature to produce a 0–100 risk score and a verdict band (clean / low / suspicious / high). A visible “Demo scoring” chip in the header makes it explicit that this is the v0 client-side scorer; nothing is sent off-device.

When the production /v1/scan API ships with Pro, the widget switches transparently — the same embed, same UI, same daily quota chip. If the API call fails, it falls back to the local heuristic so the demo keeps working under flaky networks.

Quota and rate limits

The widget enforces 10 scans per UTC day per browser via localStorage (key: glyphward.embed.v1). The remaining quota is shown in the footer of every mount and resets at 00:00 UTC. Because storage is per-origin, multiple mounts on the same page share the same daily counter.

This is the same free-tier ceiling as the hosted scanner at glyphward.com — Pro and Team raise the cap and switch the scoring to the real /v1/scan API.

Privacy

Until the /v1/scan API ships, files never leave the visitor's machine. The widget reads bytes via FileReader.arrayBuffer(), hashes them with the WebCrypto API, and renders the verdict locally. No telemetry, no third-party analytics, no cookies — only the localStorage entry that holds the daily scan count. The single outbound link is the “Powered by Glyphward” footer anchor, which carries UTM parameters so you can attribute referrals if you wire it into your analytics.

Browser support

Modern Chromium (Chrome, Edge, Brave), Firefox, and Safari 14+. The widget needs fetch, FileReader, Shadow DOM, and (ideally) SubtleCrypto.digest. On browsers without WebCrypto it falls back to a fast non-cryptographic hash so the demo still scores; the production API path will not.

Where to next