@tindalabs/blindspot
OTel browser observability - spans for every navigation, interaction, and fetch without PII.
@tindalabs/shield
Tamper detection - DevTools, automation drivers, extension injection, headless environments.
@tindalabs/scent-sdk
Probabilistic identity continuity - confident even after cookie deletion, VPNs, and browser updates.
Every navigation, click, form submission, and fetch call becomes an OpenTelemetry span - without capturing IP addresses, user agents, or any other PII. Drop it into your existing OTel pipeline; it speaks OTLP natively.
useSpan / useBlindspot hooks for manual instrumentationgrantConsent() is calledimport { BlindspotProvider } from '@tindalabs/blindspot-react';
import { useSpan } from '@tindalabs/blindspot-react';
import { recordEvent } from '@tindalabs/blindspot';
// Wrap your app once
<BlindspotProvider config={{ serviceName: 'my-app', endpoint: '/v1/traces' }}>
<App />
</BlindspotProvider>
// Annotate any component
function CheckoutButton() {
const { setAttribute } = useSpan();
function handleClick() {
setAttribute('checkout.cart_value', cartTotal);
recordEvent('checkout.initiated');
}
return <button onClick={handleClick}>Checkout</button>;
}import { assess, ContentProtector } from '@tindalabs/shield';
// One-shot environment assessment
const result = await assess();
console.log(result.signals);
// {
// 'shield.devtools.open': false,
// 'shield.automation.webdriver': false,
// 'shield.automation.headless': false,
// 'shield.frame.embedded': false,
// 'shield.extension.detected': false,
// }
console.log(result.risk);
// { score: 0.4, flags: ['devtools_open'] }
// Attach directly to an OTel span
span.setAttributes(result.spanAttributes);
// Or merge into a Scent observation
const obs = await scent.observe({ extraSignals: result.signals });
// ── Active protection ──────────────────────────────
// Block selection, copy, print, and screenshots; watermark.
const protector = new ContentProtector({
targetElement: document.querySelector('#invoice'),
preventSelection: true,
preventClipboard: true,
preventScreenshots: true,
enableWatermark: true,
});
protector.protect();A single assess() call returns structured risk signals and a calibrated 0-1 risk score. Signals are namespaced as shield.* OTel attributes - ready to attach to Blindspot spans or pass into Scent's risk engine.
navigator.webdriverContentProtectorTracks whether a returning visitor is likely the same entity - even after cookie deletion, VPN changes, browser updates, or anti-fingerprinting tools. Returns a calibrated confidence score with a signal-level explainability breakdown.
scent.identify(userId) links anonymous device identity to authenticated accounts - enabling "N accounts, same device" fraud queriesstorage.restricted)conservative | balanced | aggressive | forensic) as a first-class compliance leverdocker compose upimport { init } from '@tindalabs/scent-sdk';
const scent = init({
apiKey: 'proj_...',
endpoint: 'https://your-scent-server/v1',
persistence: 'balanced',
});
const obs = await scent.observe();
// obs.identity.confidence → 0.91
// obs.identity.continuity → 'confirmed'
// obs.identity.isNew → false
// After the user logs in, link their account ID.
// Enables: "how many accounts share this device?"
await scent.identify(currentUser.id);
await scent.flush();
// Query the reverse: all identities ever seen for this account
// GET /v1/account/:userId/identities → fraud cluster detection
scent.on('risk_elevated', ({ score, flags }) => {
// Block signup, require CAPTCHA, trigger step-up auth
});Each package replaces a tool you might reach for first - and is built differently on purpose.
Hotjar, FullStory and RUM tools record the DOM - snapshots that carry PII and CIPA / wiretapping exposure. Blindspot emits structured OTel spans instead: what happened, not a recording. No DOM capture, no PII, and it drops straight into the OTel pipeline you already run.
disable-devtool?disable-devtool gives you a boolean and only watches DevTools. Shield returns structured risk signals and a calibrated 0-1 score across DevTools, automation drivers, headless and extensions - as shield.* OTel attributes you compose into a decision, not a blunt block.
A deterministic hash mints a brand-new visitor the moment one signal changes. In a reproducible benchmark under realistic drift (browser updates, VPNs, anti-fingerprinting), deterministic fingerprints re-identify ~45-55% of returning visitors - Scent, 100%, with an explainable confidence score. Self-hostable, MIT.
Each package works independently. Together they give you a complete picture of every session: what happened, who did it, and whether to trust them.
import { BlindspotProvider, useSpan } from '@tindalabs/blindspot-react';
import { assess } from '@tindalabs/shield';
import { init } from '@tindalabs/scent-sdk';
const scent = init({ apiKey: 'proj_...', endpoint: '/v1' });
async function onPageLoad() {
// 1. Detect environment threats
const shield = await assess();
// 2. Resolve identity - shield signals merge into the snapshot
const obs = await scent.observe({ extraSignals: shield.signals });
await scent.flush();
// obs.identity.confidence → how sure we are this is a returning user
// obs.risk.score → composite threat score (device + behavior + shield signals)
}
// 3. After login: link the authenticated user to the Scent identity.
// Enables "how many accounts share this device?" fraud queries.
async function onLogin(userId: string) {
await scent.identify(userId);
await scent.flush();
}
// 4. Every OTel span gets identity + risk context automatically
// scent.identity.id, scent.identity.confidence, scent.risk.score
// shield.devtools.open, shield.automation.webdriver, ...
// ux.session.time_to_first_interaction_ms, ux.input.paste_ratio, ...Blindspot is instrumenting every click and navigation you make right now. The panel below shows live Shield and Scent output from your current session.
Click Run to assess this session.
Runs after Shield assessment.
await scentClient.identify('demo-user@tindalabs.io');
// Links this fingerprint → account ID in one callRuns after identity is resolved above.
Q4 Financial Summary
Try selecting or copying this text. Enable protection to block it.
Enable protection, then toggle strategies individually.
Click Run to assess and apply policies.
Blindspot emits an OTel span for every navigation, click and fetch - correlated by W3C traceparent, no PII. They render in-page here; in production they ship to your collector (Tempo / Grafana). recordEvent() attaches a custom event to the active route span.
recordEvent('checkout.started', {
'cart.item_count': 3,
'cart.total_usd': 142.50,
'user.plan': 'pro',
});
// → event added to the active route spanClick Generate trace to emit a span tree and see it rendered here.