{"id":50901705,"url":"https://github.com/didrod205/unspook","last_synced_at":"2026-06-16T03:04:43.404Z","repository":{"id":361192280,"uuid":"1253478408","full_name":"didrod205/unspook","owner":"didrod205","description":"Reveal \u0026 remove invisible, dangerous \u0026 confusable characters in your text — zero-width spaces, BOMs, bidi (Trojan Source), homoglyphs, smart quotes. 100% local. Web app + library + CLI.","archived":false,"fork":false,"pushed_at":"2026-05-29T14:12:18.000Z","size":50,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-29T16:04:32.172Z","etag":null,"topics":["homoglyph","invisible-characters","prompt-injection","security","smart-quotes","text-cleaner","trojan-source","unicode","zero-dependency","zero-width-space"],"latest_commit_sha":null,"homepage":"https://didrod205.github.io/unspook/","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/didrod205.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null},"funding":{"custom":["https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010"]}},"created_at":"2026-05-29T13:59:58.000Z","updated_at":"2026-05-29T14:16:00.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/didrod205/unspook","commit_stats":null,"previous_names":["didrod205/unspook"],"tags_count":2,"template":false,"template_full_name":null,"purl":"pkg:github/didrod205/unspook","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Funspook","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Funspook/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Funspook/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Funspook/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/didrod205","download_url":"https://codeload.github.com/didrod205/unspook/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/didrod205%2Funspook/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34388683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["homoglyph","invisible-characters","prompt-injection","security","smart-quotes","text-cleaner","trojan-source","unicode","zero-dependency","zero-width-space"],"created_at":"2026-06-16T03:04:43.332Z","updated_at":"2026-06-16T03:04:43.397Z","avatar_url":"https://github.com/didrod205.png","language":"TypeScript","funding_links":["https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010"],"categories":[],"sub_categories":[],"readme":"\u003cdiv align=\"center\"\u003e\n\n# 👻 unspook\n\n### Reveal \u0026 remove the invisible, dangerous, and confusable characters hiding in your text.\n\n[![npm version](https://img.shields.io/npm/v/unspook.svg?color=success)](https://www.npmjs.com/package/unspook)\n[![bundle size](https://img.shields.io/bundlephobia/minzip/unspook?label=gzip)](https://bundlephobia.com/package/unspook)\n[![CI](https://github.com/didrod205/unspook/actions/workflows/ci.yml/badge.svg)](https://github.com/didrod205/unspook/actions/workflows/ci.yml)\n[![types](https://img.shields.io/npm/types/unspook.svg)](https://www.npmjs.com/package/unspook)\n[![license](https://img.shields.io/npm/l/unspook.svg)](./LICENSE)\n\n**[🌐 Try the free web app →](https://didrod205.github.io/unspook/)** \u0026nbsp;·\u0026nbsp; no install, nothing uploaded, works offline.\n\n\u003c/div\u003e\n\n---\n\nYour text is probably not as clean as it looks. Copy something from a website, a\nPDF, a Word doc, a chat app, or an AI assistant and you'll often paste in\n**characters you can't see**:\n\n- **Zero-width spaces** and **BOMs** that break `===` comparisons, search, and CSV imports.\n- **Non-breaking spaces** masquerading as normal spaces — the bane of every \"why won't this match?\" bug.\n- **“Smart quotes”, em–dashes and ellipses…** that wreck code, JSON, and CSVs.\n- **Bidi control characters** — the [*Trojan Source*](https://trojansource.codes/) attack (CVE-2021-42574) that makes code read one way and compile another.\n- **Unicode \"tag\" characters** used to smuggle **invisible prompt-injection** instructions into text fed to LLMs.\n- **Homoglyphs** — a Cyrillic `а` or Greek `ο` that looks exactly like Latin but isn't (phishing, impersonation, broken lookups).\n\n**unspook** finds them, shows you exactly what's there, and cleans your text —\n**100% locally**, with **zero dependencies** and **no API key**.\n\n\u003e 📸 _Screenshot / demo GIF:_ `./web/screenshot.png` — replace with a recording of the [live app](https://didrod205.github.io/unspook/).\n\n## Why it exists\n\nEvery \"text sanitizer\" you find online makes you **paste sensitive content into\nsomeone else's server**. That's exactly backwards for a privacy/security tool.\nunspook runs entirely in your browser or your terminal — your text never leaves\nyour machine. And because detecting these characters is a precise,\nspec-based problem (not a vibe), it's the kind of thing you want a small, tested,\n**deterministic** tool for — not a guess.\n\n## Who it's for\n\nDevelopers (clean code, configs, commit hooks), **writers \u0026 marketers** (clean\ncopy before publishing), **designers** (paste-safe content), **educators \u0026\nresearchers** (spot hidden characters in AI text), **ops \u0026 support** (sanitize\nlogs and tickets), and anyone who's ever fought a \"looks identical but won't\nmatch\" bug.\n\n## Install\n\n**No install needed —** just open the **[web app](https://didrod205.github.io/unspook/)**.\n\nFor the library / CLI:\n\n```bash\nnpm install unspook        # library\nnpm install -g unspook     # CLI (or use npx unspook)\n```\n\nShips ESM **and** CommonJS, with TypeScript types.\n\n## Usage\n\n### In code\n\n```ts\nimport { scan, clean, reveal, report, stats } from \"unspook\";\n\nclean(\"Hello​world\");                 // \"Helloworld\"  (zero-width space removed)\nclean(\"a b\");                         // \"a b\"         (NBSP → normal space)\nclean(\"“quote” — dash…\", { smartPunctuation: true }); // '\"quote\" -- dash...'\nclean(\"аdmin\", { homoglyphs: true });      // \"admin\"       (Cyrillic а → a)\n\nscan(\"hi​there\");\n// [{ index: 2, line: 1, column: 3, char: \"​\", codePoint: 8203, hex: \"U+200B\",\n//    name: \"ZERO WIDTH SPACE\", category: \"zero-width\", severity: \"warning\" }]\n\nreveal(\"a​b\");                        // \"a[U+200B]b\"\n\n// report() pairs each finding with its source line — for security/code review.\nreport(code);  // [{ finding: { line, column, hex, name, … }, lineText }, …]\n\nstats(text);                               // { total, byCategory, bySeverity }\n```\n\nEvery `Finding` now carries **`line` and `column`** (1-based; column counted in\ncode points, so it matches what you see) — jump straight to the offender.\n\n### On the command line\n\n```bash\nunspook notes.md                 # print cleaned text\ncat draft.txt | unspook          # use it as a filter in any pipeline\nunspook -w README.md             # clean a file in place\nunspook --reveal config.yml      # show what's hiding\nunspook --scan src/index.ts      # list findings (line:col); exits 1 if any → CI\nunspook --report src/index.ts    # show each finding with its source line + caret\nunspook --aggressive blog.md     # also fix smart quotes, homoglyphs \u0026 whitespace\n```\n\n`--report` prints a compiler-style diagnostic — perfect for catching a\n**Trojan Source** attack in review:\n\n```text\nsrc/auth.js:2:18  DANGER  U+202E RIGHT-TO-LEFT OVERRIDE (bidi)\n  if (access != \"ad[U+202E]nimda[U+202C]\") {\n                   ^\n```\n\nDrop `--scan` into a pre-commit hook or CI to **fail the build if invisible/bidi\ncharacters sneak into your codebase.**\n\n### Cleaning options\n\n| Option | Default | What it does |\n| ------ | :-----: | ------------ |\n| `zeroWidth` | ✅ | Remove zero-width / invisible chars (ZWSP, BOM, word joiner…) |\n| `bidi` | ✅ | Remove bidirectional controls (Trojan Source) |\n| `tag` | ✅ | Remove Unicode tag chars (invisible prompt injection) |\n| `control` | ✅ | Remove C0/C1 control characters |\n| `invisibleSpaces` | ✅ | Normalize NBSP \u0026 exotic spaces → space; drop soft hyphens |\n| `variationSelectors` | ❌ | Remove variation selectors (off by default — used by emoji) |\n| `smartPunctuation` | ❌ | Convert “ ” ‘ ’ — … to ASCII |\n| `homoglyphs` | ❌ | Map look-alike letters to Latin (Cyrillic/Greek/fullwidth) |\n| `collapseWhitespace` | ❌ | Collapse runs of spaces/tabs |\n| `normalizeNewlines` | ✅ | `\\r\\n`, `\\r` → `\\n` |\n| `trim` | ❌ | Trim the ends |\n\n`DEFAULT_OPTIONS` and `AGGRESSIVE_OPTIONS` presets are exported too.\n\n## FAQ\n\n**Is my text uploaded anywhere?**\nNo. The web app and the library run entirely on your device — there is no\nserver, no telemetry, no network request. You can use it offline.\n\n**Will it break my emoji?**\nNo. Variation selectors (which emoji rely on) are kept by default. Turn on\n`variationSelectors` only if you specifically want them removed.\n\n**Does it modify visible content?**\nBy default it only removes invisible/dangerous characters and normalizes odd\nspaces — your visible text is preserved. Smart-quote and homoglyph conversion\nare **opt-in** because they change visible characters.\n\n**How is this different from a regex like `/[​]/g`?**\nunspook covers dozens of code points across eight categories (zero-width, bidi,\ntag, control, exotic spaces, smart punctuation, homoglyphs, variation selectors),\nnames each finding, assigns a severity, tracks positions, and gives you a tested,\nmaintained, reversible-by-option cleaner. No regex to copy-paste-and-get-wrong.\n\n**Can I use it in CI / a pre-commit hook?**\nYes — `unspook --scan \u003cfiles\u003e` exits with code `1` when anything is found.\n\n**Why \"unspook\"?**\nIt un-spooks your text: removes the ghostly invisible characters. 👻\n\n## Contributing\n\nContributions are very welcome! See [CONTRIBUTING.md](./CONTRIBUTING.md) and the\n[Code of Conduct](./CODE_OF_CONDUCT.md). Adding a code point or a homoglyph\nmapping? Include a test and a reference.\n\n```bash\ngit clone https://github.com/didrod205/unspook.git\ncd unspook\nnpm install\nnpm test          # run the suite\nnpm run dev       # run the web app locally\n```\n\n## 💖 Sponsor\n\nunspook is free, MIT-licensed, and built in spare time. If it saved you from a\nmaddening invisible-character bug — or a security incident — please consider\nsupporting it:\n\n- ⭐ **Star this repo** — free, and it genuinely helps others find it.\n- 🍋 **[Sponsor via Lemon Squeezy](https://elab-studio.lemonsqueezy.com/checkout/buy/5d059b89-51d0-456b-b33a-ed56994f7010)** — one-time or recurring support.\n\n**Where your support goes:** keeping the character database current with new\nUnicode releases, expanding the homoglyph/confusables coverage, maintaining the\nfree hosted web app, adding integrations (VS Code extension, ESLint plugin,\npre-commit hook), and answering issues quickly.\n\n## License\n\n[MIT](./LICENSE) © unspook contributors\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Funspook","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdidrod205%2Funspook","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdidrod205%2Funspook/lists"}