Shield covers the full environment and tamper-detection surface: DevTools detection, WebDriver/Playwright/CDP automation flags, headless browser heuristics, patched-API detection, canvas entropy spoofing, extension detection, and active content protection (selection blocking, print prevention, screenshot prevention, watermarking). A cross-suite gap analysis found no new detection categories needed at this time — the pending items below are the right next work.
Priority: High | Effort: 1-2 hours | Status: Done ✅
Event handlers use unsafe as type assertions instead of leveraging the existing EventDataMap:
// Current (unsafe):
const data = event.data as { isOpen: boolean };
// Recommended (type-safe):
type DevToolsData = EventDataMap[ProtectionEventType.DEVTOOLS_STATE_CHANGE];Implemented via the isEventType() type guard in src/core/mediator/eventDataTypes.ts, used across all event handlers.
- Create type guard helpers for event data validation
- Update
devToolsEventHandler.tsto use typed event data - Update
extensionEventHandlers.tsto use typed event data - Update
protectedContentManager.tsto use typed event data - Update
securityOverlayManager.tsto use typed event data
Priority: Medium | Effort: 30 mins | Status: Done ✅ (resolved by deletion)
screenshotDetector.ts added event listeners directly to window instead of using eventManager. Investigation found the file was dead code: zero references anywhere, not exported (the utils barrel only exports environment and dom), and its heuristics were already duplicated by ScreenshotStrategy, which correctly uses eventManager via registerEvent. The leak concern was therefore moot (the class was never instantiated), so the orphan was deleted rather than refactored.
If screenshot detection later grows more independent heuristics, adopt the DevTools Strategy → DetectorManager → [detectors] pattern starting from ScreenshotStrategy's real logic.
-
Refactor→ deleted dead duplicate;screenshotDetector.tsto useeventManagerScreenshotStrategyalready useseventManager
Priority: High | Effort: 1-2 hours | Status: Done ✅
ClipboardStrategy is now integrated into ContentProtector (see src/core/ContentProtector.ts, gated on options.preventClipboard).
Current Status: The strategy is mature with:
- ✅ Copy/cut/paste event handling
- ✅ Clipboard API interception
- ✅ document.execCommand interception
- ✅ Proper cleanup/restore methods
- Add
preventClipboardoption toContentProtectionOptions - Add
clipboardOptionstoContentProtectionOptions - Integrate ClipboardStrategy initialization in
ContentProtector.initializeStrategies() - Add unit tests for clipboard protection (
src/tests/strategies/ClipboardStrategy.test.ts) - Update documentation (README
ContentProtectorexample + new complete "ContentProtector Options" table in REFERENCE.md coveringclipboardOptionsand all other strategy options)
Priority: Low | Effort: 2-3 hours | Status: Done ✅
Three abstract classes duplicate logging/error-handling functionality:
| Class | Duplicated Features |
|---|---|
AbstractStrategy |
safeExecute, handleError, log/warn/error |
AbstractDevToolsDetector |
safeExecute, handleError |
AbstractEventHandler |
log/warn/error |
LoggableComponent now owns COMPONENT_NAME + debugMode + logger + setDebugMode/isDebugEnabled/log/warn/error. The three abstracts extend it and keep only their own concerns. safeExecute/handleError stayed on the strategy/detector bases because their error-type enums diverge (StrategyErrorType vs DetectorErrorType) — sharing them would require generics that obscure the call sites. AbstractEventHandler's prior log/warn overrides were redundant (they re-implemented gates that SimpleLoggingService already does) and were removed.
- Create
src/utils/base/LoggableComponent.ts - Refactor
AbstractStrategyto extend it - Refactor
AbstractDevToolsDetectorto extend it - Refactor
AbstractEventHandlerto extend it - Update tests, verify build passes (tsc clean, build green, 46/46 tests pass)
Priority: High | Effort: 3-5 hours | Status: Done ✅ (implemented in src/policy.ts, exported from src/index.ts, tested in src/tests/policy.test.ts)
assess() and ContentProtector are currently independent APIs — users must manually read the assessment result and activate strategies. A declarative policy bridge closes this gap: run assess() once, then activate only the strategies that are warranted by the detected signals. Legitimate users see no protection overhead; automation and scrapers trigger it automatically.
Proposed API:
import { assessAndProtect } from '@tindalabs/shield';
const protector = await assessAndProtect(element, {
policies: [
// Watermark all medium-risk sessions (headless, automation)
{
when: { riskScore: { gte: 0.3 } },
enable: ['enableWatermark'],
watermarkOptions: { text: (assessment) => `PROTECTED-${assessment.sessionId}` },
},
// Add clipboard + selection protection for high-risk sessions
{
when: { riskScore: { gte: 0.6 } },
enable: ['preventSelection', 'preventClipboard'],
},
// Screenshot prevention specifically for headless automation
{
when: { signals: { 'shield.automation.headless': true } },
enable: ['preventScreenshots'],
},
],
// OTel span emitter — policy triggers emit span events
spanEmitter: (name, attrs) => span.addEvent(name, attrs),
});Why this matters:
- No legitimate user friction — protection is proportional to detected risk, not always-on
- Traceable watermarks — watermark text can embed the assess() session token, so scraped content carries a forensic trace back to the session that extracted it
- OTel-native policy observability — every policy trigger emits a span event (
shield.policy.triggered,shield.strategy.activated) visible in Grafana; operators can see in real time how often and by what signal their content is being targeted - Composable with Scent — pair with
scent.observe({ extraSignals: assessment.signals })for identity-aware policies ("this device has triggered protection 12 times this week")
Use cases / marketing angles:
- Adaptive content protection — the primary positioning; broader than any single use case
- Anti-AI scraping — headless + automation signals map directly to scraper profiles; watermark embeds a forensic trace into any scraped content
- Risk-proportional DRM — financial, legal, and media documents get protection only when the session warrants it
- Design
PolicyEnginetype:PolicyRule[]withwhen(signal conditions + risk threshold) andenable(strategy keys) - Implement
assessAndProtect(element, options)— runsassess(), evaluates policies in order, initialisesContentProtectorwith the union of matched strategies - Support dynamic watermark text via
watermarkOptions.text: string | ((assessment: ShieldAssessment) => string) - Emit OTel span events for each triggered policy rule (name:
shield.policy.triggered, attrs: matched signals + enabled strategies) - Export
assessAndProtectandPolicyRulefromsrc/index.ts - Add unit tests: no-match policy (no protector created), single-match, multi-match, watermark text factory, OTel emit
- Add example to README and
REFERENCE.mdunder "Adaptive Protection" - Add use-case section to README: "Anti-AI scraping / adaptive content protection" — new "Use cases — adaptive content protection" section covering anti-AI scraping (with forensic-trace watermark factory example), risk-proportional DRM, and a "when not to reach for it" note pointing back to plain
ContentProtector
Findings from a full C-level assessment (CTO / CPO / COO / CMO / CFO + competitive research).
Overall score: 6.3/10 — technically superior to the incumbent; held back entirely by distribution.
Full report: c-level/reports/shield_2026-05-19.md
- Fix
BrowserExtensionOptions.showOverlay: true→showOverlay?: booleaninsrc/types/index.ts:203— compile-breaking for any consumer of extension detection - Add
coverage/to.gitignore— generated output should not be in version control - Add
SECURITY.mdwith maintainer contact and responsible disclosure path (90-day timeline) - Tag
v0.1.0and push ✅ — tag exists locally;publish.ymlis wired to trigger on tag push. Verify the npm publish actually resolved (npm view @tindalabs/shield) — see follow-up in the 2026-05-29 backlog.
-
Move
attachShieldToSpan()example to main README ✅ — new top-level section "OTel-instrumented protection" right afterContentProtector, with quick-start example, Blindspot integration variant, and the full table of emitted span events + attributes. (The "Zero runtime dependencies" badge already shipped in the badges commit above.) -
Add README badges (npm version, CI status, MIT license, zero-runtime-deps) ✅ — coverage badge skipped: no coverage service is wired into CI yet (Codecov/Coveralls would need configuring). Worth adding once that's set up.
-
Add CONTRIBUTING.md (done ✅) + PR template ✅ at
.github/PULL_REQUEST_TEMPLATE.md -
Add
.github/dependabot.ymlfor monthly dev-dep updates ✅ — npm (grouped minor/patch into one PR per cycle) + github-actions, both monthly. -
Increase test coverage to 60%+ on— verified met. Baseline as of last audit:ContentProtector,ClipboardStrategy, and allAbstractStrategy.remove()/ cleanup pathsContentProtector.ts88%,ClipboardStrategy.ts61%,AbstractStrategy.ts71%. Remaining gaps in those files (ContentProtectorlines 74-78/98/107/229/326-329;ClipboardStrategylines 125-143/180-220;AbstractStrategyremove()paths at 89-90/205-210) are minor and can be picked up opportunistically. -
Broader coverage uplift — in progress. Overall coverage now at 76% stmts / 61% branch / 83% funcs / 78% lines (372 tests, up from 40%/28%/37%/42% with 58 tests). Highest-leverage gaps to attack next, ordered by impact-per-test:
→ 92.68% ✅ (16 tests; SSR baseline, signal flags, extension detection, risk clamp/rounding, lean spanAttributes)assess.ts— 2.43%→ 100% ✅ (13 tests; everyotel.ts— 0%attachShieldToSpanhandler + emitter-throw isolation)→ 91.62% lines ✅ (31 tests; document/window/element targets, owner/selector/type queries, conflicts, isolation).eventManager.ts46%→ 100% lines ✅,intervalManager.ts37%→ 100% lines ✅ (28 tests across both).timeoutManager.ts59%→ 92.04% lines ✅ and the 5 event handlers:ContentProtectionMediator.ts— 19%devToolsEventHandler 11%→ 100%,extensionEventHandlers 7%→ 86.2%,iFrameEventHandlers 17%→ 94.4%,screenShotEventHandlers 21%→ 100%,abstractEventHandler 63%→ 87.5%. Registry: 92.85%. (40 tests across two new suites.)→ 90.47% lines ✅,protectedContentManager.ts— 11%→ 90.56% lines ✅ (38 tests across two suites; supersession bug surfaced — see follow-up above).keyboardShortcutManager.ts— 11%Individual DevTools detectors — 0% each→ all 7 detectors + manager covered ✅:sizeDetector81%,timingDetector90%,dateToStringDetector95%,funcToStringDetector97%,regToStringDetector55% (Firefox/QQ-specific branches not reachable in jsdom),defineGetterDetector81%,debugLibDetector89%,debuggerDetector23% (Worker not supported in jsdom — limited to constructor/dispose/isSupported paths),devToolsDetectorManager91%. 41 tests across two new suites.→ 100% ✅ (9 tests; log gating, warn brief/verbose, debug-mode toggle)LoggableComponent.ts— 40%→dom.ts,orientation.ts,LoggingDelegate.ts— 0%dom.ts88%,orientation.ts100%,LoggingDelegate.ts100% ✅ (18 tests in one combined suite).Bonus consolidation while testing: the 7 utility classes that still construct their own✅ Done across 3 commits (54306c8, 67a3e7c, 584840a). All 7 (SimpleLoggingServiceTimeoutManager,IntervalManager,EventManager,SecurityOverlayManager,ProtectedContentManager,DevToolsDetectorManager,ContentProtector) now extendLoggableComponent. Net ~80 lines removed of boilerplate;SimpleLoggingServiceis now imported only by the base + the legacyLoggingDelegatecompanion. All 372 tests still green; behavioral parity preserved (same log prefixes, same "Debug mode enabled" line).
Recent slice ✅: heavy strategies —
DevToolsStrategy25% → 80.95%,ScreenshotStrategy35% → 83.33% (31 net new tests; both existing single-test stubs replaced with comprehensive suites covering lifecycle, event handlers, mediator publish paths, updateOptions branches).Recent slice ✅: long-tail strategy polish —
ContextMenuStrategy44% → 59.8%,ExtensionStrategy48% → 65.93%,IFrameStrategy58% → 82.52% (27 net new tests across the three suites). Lower per-file leverage than past slices but solid polish — overall now at 77.86% lines.All numbered coverage items are now ✅. Remaining gaps are long-tail branches (DOM-observer paths in
ContextMenuStrategy, configPath fetch + fallback inExtensionStrategy, fallbacksetIntervalpaths). Marginal cost-per-pp is high; ~78% is a reasonable "done" point.Distribution polish ✅ done. Only remaining gating work for v0.1.0: tag and push
v0.1.0(line 164) — that triggerspublish.ymland makesnpm install @tindalabs/shieldwork. -
Write "migrating from disable-devtool" guide — captures incumbent's user base via search; name the gap: structured output, OTel, no boolean-only API
-
✅ Fixed: the displaced active state is now re-queued before the higher-priority replacement is applied, mirroring the SecurityOverlayManager re-queue fix in 4d14467. Pinned test inverted to assert the correct fallback behaviour, plus a new test covering the dismiss-and-resurface flow.ProtectedContentManagerpriority-supersession orphan bug. -
Prune the aspirational✅ Done. Dropped 19 unused event types from the enum and their matchingProtectionEventTypeenumEventDataMapentries:STRATEGY_APPLIED/UPDATED,SELECTION_ATTEMPT,CONTEXT_MENU_ATTEMPT,KEYBOARD_SHORTCUT_BLOCKED,PRINT_ATTEMPT,DRAG_ATTEMPT,WATERMARK_TAMPERED/CREATED/REMOVED,FULLSCREEN_CHANGE,MEDIATOR_INITIALIZED/DISPOSED,ERROR_OCCURRED,DEBUG_MESSAGE,CONFIG_UPDATED,KEYBOARD_SHORTCUTS_REQUESTED/PROVIDED/UPDATED. Also removed the unusedWatermarkEventinterface. The enum is now 10 entries — exactly what's wired through the mediator today — and the file carries a header explaining the detect-and-react fan-out vs direct-blocking asymmetry. All 372 tests still green. -
Decide if blocking strategies should adopt the mediator (deferred — defensible no). Pros: single subscribe point for OTel telemetry (today wired via
attachShieldToSpancallbacks — a parallel path); pluggability for consumers who want to react to blocked events without subclassing; future cross-component reactions (e.g. throttled toasts on repeated clipboard attempts). Cons: ceremony with no current decoupling benefit. Don't refactor until a concrete need surfaces; revisit if/when item above gets a real second consumer.
- Coordinate simultaneous Show HN with scent + blindspot-ux launches as "The Tindalabs browser stack" — three composable libraries are a stronger story than three separate posts
- Feature
attachShieldToSpan()in OTel community channels (CNCF Slack, OTel SIG discussions) — OTel community is the highest-conversion distribution channel for this unique capability - Build community extension signature database (PR-contributed, similar to uBlock filter lists) — each new signature increases Shield's value and creates a contribution flywheel
- Fix event-handler type assertions (ROADMAP item #1) using
EventDataMap— improves type safety and reduces contributor friction
- Chrome/Safari DevTools heuristics — major browser updates break timing-based detection; monitor
disable-devtoolGitHub issues for breakage reports as a leading indicator (same heuristics apply to Shield) - Clipboard API permission changes — Chrome has been restricting programmatic clipboard access; monitor Web Capabilities Working Group for changes affecting
ClipboardStrategy @fingerprintjs/botdfeature expansion — if Fingerprint adds active content protection features, Shield's positioning narrows; monitor their changelog
Findings from the second full C-level assessment (CTO / CPO / COO / CMO / CFO / CSO + competitive research).
Overall score: 7.7/10 (up from 6.3 — every internal gap from the May 19 round is closed; remaining work is non-technical).
Full report: c-level/reports/shield_2026-05-29.md
The cross-cutting finding: the product is built, distribution is the bottleneck, and detection claims need real-browser verification before being marketed aggressively.
-
Verify v0.1.0 is live on npm.✅ Confirmed:npm view @tindalabs/shield versionreturns0.1.0; tarball resolves atregistry.npmjs.org/@tindalabs/shield/-/shield-0.1.0.tgz. -
Enable npm publish provenance + 2FA on the publishing account.✅ Three layers shipped: (1)auth-and-writes2FA enabled on theisonimusnpm account (manual). (2)--provenanceflag +id-token: writeinpublish.yml→ signed attestation linking tarball to workflow run + commit SHA. (3) Trusted Publishing (OIDC) wired inpublish.yml— noNPM_TOKENsecret needed; GitHub's OIDC token is exchanged at publish time for a short-lived npm token. Manual one-time setup: add GitHub Actions as a trusted publisher in https://www.npmjs.com/package/@tindalabs/shield/access (org=tindalabs, repo=shield, workflow=publish.yml). -
Cut a real✅ Done in commit[0.1.0]CHANGELOG section.d447a04—[Unreleased]rotated into[0.1.0] - 2026-05-29capturingassess(),attachShieldToSpan(),assessAndProtect(),ContentProtector,ClipboardStrategyintegration, CI/publish workflows, and the jest-environment-jsdom dependency relocation. -
Add CodeQL workflow✅ Added at.github/workflows/codeql.yml— runsjavascript-typescriptanalysis with thesecurity-and-qualityquery pack on every push tomain, every PR, and weekly on Monday 06:00 UTC. Findings surface in the repo's Security tab. -
Add a 30-second "which API do I want?" decision tree✅ Added as a new## Which API do I want?section between Install and Quick Start. Three-row table maps user goal (observe / block / both) to the right API (assess()/ContentProtector/assessAndProtect()) with one-line "what it does" descriptions, plus a pointer toattachShieldToSpan()for OTel-instrumented wrapping.
- Cross-browser Playwright smoke job in CI. ✅ Added the
e2e/package: a Vite-built fixture loads Shield's source and exposesassess()onwindow; the spec drives it viapage.evaluateand asserts result shape, thenavigator.webdriversignal mirrors the live engine, a forced__REACT_DEVTOOLS_GLOBAL_HOOK__signature composes risk → flags →spanAttributesend-to-end, and a clean session stays lean. Crucially this exercises the Worker-based DevTools debugger detector that jsdom cannot run (the path pinned at 23% unit coverage). CI runs Chromium + Firefox onubuntu-latestand WebKit onmacos-latest(Playwright's WebKit throws an "internal error" on Linux regardless of the static server — confirmed locally — so it is gated off Linux and verified natively on macOS, the engine where timing heuristics diverge most). 8/8 green on Chromium + Firefox locally. - Write "Migrating from disable-devtool" guide (carried over from the May 19 backlog; still the highest-leverage CMO action). Title for the search query: "Migrating from disable-devtool to a structured, OTel-native tamper detection library." Cross-post to Dev.to and r/javascript.
- Host the
demo/SPA publicly (Vercel / GitHub Pages /shield.tindalabs.dev) with a "copy as JSON" button on the output. Currently localhost-only — the most shareable asset Shield has is unreachable to prospects. - Add bundle-size badge to README + CI (
size-limitorbundlejs). "Zero deps" sells; "X kB gzipped" closes. - Add issue templates (
.github/ISSUE_TEMPLATE/bug-report.md,false-positive.md,feature-request.md) andgood-first-issuelabel on signature-registry work — opens the contribution flywheel before community PRs land blindly. - Add "Privacy & Data Handling" + explicit "What's NOT detected" README sections. Shield runs entirely client-side and transmits nothing unless the consumer wires
spanEmitter— saying so explicitly closes the GDPR-conscious user's first question. - Enforce coverage threshold in CI (
coverageThreshold: { global: { lines: 70 } }injest.config.js). Backstops the 78% baseline against regression without forcing it higher. - Pin GitHub Actions versions to commit SHAs. Dependabot already keeps these fresh; SHA-pinning (
actions/checkout@<sha>instead of@v6) closes the supply-chain window between major-version float releases.
- Coordinated Tindalabs stack launch — Show HN + Product Hunt with blindspot + scent + shield as one narrative, not three. (Carried over from May 19; still the single highest-leverage strategic action.) Every other distribution recommendation amplifies after the first 100 strangers install Shield.
- OTel SIG demo + CNCF Slack thread + PR to
awesome-opentelemetry. This is the highest-conversion community for Shield's unique OTel-native wedge; no incumbent in the category is here yet. - Community extension signature registry (carried over from May 19; the contribution flywheel). Each PR-contributed signature increases library value uBlock-style.
- Resolve Tindalabs commercial intent. Is the stack a portfolio play (CV / authority) or a future commercial entity? Either is defensible; ambiguity costs strategic clarity. If commercial, scope the SaaS layer (hosted policy editor / managed signature registry) — Shield itself stays MIT.
- Address bus factor. Recruit one co-maintainer or publish a succession plan in SECURITY.md — materially de-risks the project for any downstream commercial customer.
- Anti-detect arms race (Camoufox / Nodriver / Patchright). Detection-side libraries co-evolve continuously with these; falling behind = silent false negatives. Monitor the 2026 anti-detect benchmarks as a leading indicator of which heuristics are still discriminating.
disable-devtoolactivity. If the incumbent resumes active development or adds structured output, Shield's wedge over it narrows. Last release as of this assessment: 10 months stale.