npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@blursec/sdk

v1.0.1

Published

Official isomorphic TypeScript SDK for Blursec — pre-auth credential & session leak detection backed by live stealer-log threat intel.

Readme

@blursec/sdk

Official isomorphic TypeScript SDK for Blursec — pre-auth credential & session leak detection backed by a live stealer-log threat-intel database.

Node TypeScript Bundle Edge


What Blursec does

Most authentication breaches don't happen because someone "hacked" you. They happen because infostealer malware on a user's device harvested their saved credentials — for your app and every other platform, SaaS, or provider they use — into a stealer-log database, and an attacker logged into your app with a perfectly valid username + password.

Blursec sits in front of your /login endpoint and asks one question: "Is this credential already in the wild?" — answered in under 50 ms by a continuously-updated stealer-log feed. If the answer is yes, the SDK tells you what to do about it.

The SDK exposes four things:

  1. blursec.checkCredential(email, password) — Layer 1. K-anonymity SHA-256 lookup. The raw password never leaves your server.
  2. blursec.verifySession(req) — Layer 2. Extracts a session token from a Request (Authorization header or cookie), auto-detects JWT vs opaque, hashes appropriately, and looks it up against the live stealer-log feed.
  3. blursec.webhooks.verify({ secret, payload, signature, timestamp }) — Verify HMAC-SHA256-signed deliveries Blursec posts to your endpoint when a previously-clean session shows up in a fresh stealer-log batch after login.
  4. dryRun: true — Observe-only mode on any check, so you can stage Blursec into production traffic before enforcing.

Install

npm install @blursec/sdk
# or
pnpm add @blursec/sdk
# or
yarn add @blursec/sdk

Requires Node 18+, a modern browser, Cloudflare Workers, Deno, or Bun. Zero runtime dependencies.

Get an API key: create a free workspace and grab your license key at blursec.dev. Full reference docs live at docs.blursec.dev.


The 4-line login endpoint

import { Blursec } from "@blursec/sdk";

const blursec = new Blursec({ apiKey: process.env.BLURSEC_API_KEY! });

app.post("/login", async (req, res) => {
  const { email, password } = req.body;

  const risk = await blursec.checkCredential(email, password);
  if (risk.leaked) {
    return res.status(403).json({
      error: "credential_compromised",
      action: risk.recommendedAction, // "block" | "force_reset" | "monitor"
    });
  }

  // ...your normal password verification continues here
});

That's the integration. Everything below is detail.


How it works (prefix-blinded lookup)

The SDK never sends the raw password to Blursec. The flow:

┌──────────────────────────────────────────────────────────────────────────┐
│  1. SDK hashes  email + ":" + password  with SHA-256 locally             │
│     → 64-char hex digest                                                 │
│                                                                          │
│  2. SDK sends ONLY the first 10 hex characters (the prefix) to the API   │
│     → 40 bits ≈ 1.1 trillion possible buckets; the prefix is one-way     │
│       and cannot be reversed back to the password                        │
│                                                                          │
│  3. API returns every stealer-log entry that hashes into that bucket     │
│     → array of { hashSuffix, severity, firstSeen, sources }              │
│                                                                          │
│  4. SDK compares the remaining 54 hex chars LOCALLY                      │
│     → match ⇒ this exact credential is leaked                            │
│                                                                          │
│  5. SDK maps severity → recommendedAction and returns to your code       │
└──────────────────────────────────────────────────────────────────────────┘

Same wire model as HaveIBeenPwned's range API, applied to full credential pairs rather than passwords alone, with a longer prefix so each bucket is small. The prefix is one-way (SHA-256 is preimage-resistant), but because credential pairs come from a high-entropy keyspace, a 40-bit prefix is effectively a unique identifier for the underlying credential.

Threat model. The SDK's confidentiality story relies on two trust assumptions:

  1. TLS terminates inside Blursec. Network observers cannot link prefixes to client IPs / accounts.
  2. Blursec does not log or correlate the prefixes it observes. The prefix alone leaks no credential bits, but a malicious or compromised Blursec backend that stored prefix-by-prefix request logs could in principle build a per-user lookup table out of repeated requests.

If your threat model does not allow either assumption, you should run your own copy of the stealer-log database and hash entirely on-prem.

See examples/k-anonymity.ts for a step-by-step walkthrough you can run yourself.


The CredentialRiskResult

Every credential check returns the same shape:

{
  leaked: boolean;                    // is this credential in the database?
  severity: "low" | "medium" | "high" | "critical" | null;
  recommendedAction:
    | "allow" | "monitor" | "force_reset" | "kill_sessions" | "block";
  firstSeen: string | null;           // ISO 8601, earliest sighting
  sources: number;                    // distinct stealer-log sources
  checkedAt: string;                  // ISO 8601, when the SDK ran the check
  durationMs: number;                 // end-to-end SDK time
  failedOpen: boolean;                // true if SDK returned a safe default
}

Default severity → action mapping (overridable per call):

| severity | recommendedAction | | ---------- | ----------------- | | critical | block | | high | force_reset | | medium | force_reset | | low | monitor | | (none) | allow |


Fail-open guarantee

If the Blursec API times out, returns 5xx, or fails for any network reason, the SDK does not throw. It returns:

{ leaked: false, recommendedAction: "allow", failedOpen: true, /* ... */ }

This is intentional: a legitimate user logging in must never be locked out because of Blursec's infrastructure. You can opt out per call with failOpen: false.

const risk = await blursec.checkCredential(email, password, {
  failOpen: false,        // throw on timeout / 5xx
  timeoutMs: 500,         // tight latency budget
});

Errors that always throw (and indicate a bug in your integration, not a transient failure):

  • BlursecAPIError with status 400499 (bad request, unauthorized, forbidden)
  • BlursecValidationError (you called the SDK with invalid arguments)

See docs/errors.md.


Protective actions

The SDK does not kill sessions or flag users on your behalf — Blursec is a threat-intel API, not a session manager. Use the returned recommendedAction to drive your own session store / auth provider:

const risk = await blursec.checkCredential(email, password);

if (risk.leaked) {
  switch (risk.recommendedAction) {
    case "block":
      // attacker likely already in — revoke every session you've issued
      await yourSessionStore.revokeAllFor(userId);
      return res.status(403).json({ error: "credential_compromised" });

    case "force_reset":
      return res.redirect("/reset-password?reason=leaked");

    case "monitor":
      // allow the login, but record the event for incident response
      auditLog.warn("compromised_credential_login", { userId });
      break;
  }
}

Verify live sessions (Layer 2)

Drop verifySession(req) into your authenticated-request middleware to catch a session token that surfaces in a stealer-log batch after the user already logged in. Token-type detection is automatic — JWTs are hashed by their signature segment only; opaque tokens are hashed in full.

app.use(async (req, res, next) => {
  const result = await blursec.verifySession(req, {
    cookieName: "sid",        // optional — defaults to "session"
    timeoutMs: 200,           // tight — runs on every request
  });

  if (result.compromised && result.recommendedAction === "block") {
    await yourSessionStore.revokeAllFor(req.user.id);
    return res.status(401).json({ error: "session_compromised" });
  }

  next();
});

result.tokenType ("jwt" | "opaque") is exposed so dashboards can break stats down by token shape.


Verify webhooks

Blursec posts asynchronous notifications (newly-leaked credential detected for a known customer email, session token observed in a fresh stealer-log batch, key rotated, ...) to a customer-supplied URL. Each delivery carries an HMAC-SHA256 signature plus a millisecond-precision timestamp; the SDK verifies both locally — no round-trip to Blursec.

app.post(
  "/webhooks/blursec",
  express.raw({ type: "*/*" }),    // MUST be raw bytes — signature is over them
  async (req, res) => {
    const ok = await blursec.webhooks.verify({
      secret: process.env.BLURSEC_WEBHOOK_SECRET!,
      payload: req.body,
      signature: req.header("X-Blursec-Signature") ?? "",
      timestamp: req.header("X-Blursec-Timestamp") ?? "",
    });
    if (!ok) return res.status(401).end();

    const event = JSON.parse(req.body.toString("utf8"));
    // ...handle event
    res.status(204).end();
  },
);

Deliveries older than 5 minutes are rejected automatically (configurable via toleranceMs).


dryRun — stage Blursec into production traffic safely

Pass dryRun: true to any credential / session check during the first 1–2 weeks of an integration. The SDK still calls the API and populates leaked / severity / enforcedAction so you can dashboard the real signal — but recommendedAction is forced to "allow" so no live traffic is blocked.

const result = await blursec.checkCredential(email, password, { dryRun: true });

metrics.increment("blursec.would_have_blocked", {
  enforced: result.enforcedAction,   // "block" | "force_reset" | "monitor" | "allow"
  leaked: String(result.leaked),
});

// Continue with your normal login flow regardless of the verdict.

Flip dryRun to false once you trust the rates.


Runtimes

The SDK uses only platform-standard APIs — fetch, AbortController, crypto.subtle.digest, TextEncoder — and ships as both ESM and CJS, with runtime-specific bundles selected by your bundler:

| Runtime | Supported | Bundle (auto-selected) | Notes | | -------------------- | ---------- | ----------------------------- | -------------------------------------------------------------- | | Node 20+ | ✅ | dist/index.node.{js,cjs} | Recommended. | | Node 18+ | ✅ | dist/index.node.{js,cjs} | crypto is global; works out of the box. | | Browsers (modern) | ⚠️ | dist/index.browser.js | Server-side only for checkCredential. Never ship API keys to the browser. | | Cloudflare Workers | ✅ | dist/index.worker.js | See examples/cloudflare-worker.ts. | | Vercel Edge | ✅ | dist/index.worker.js | Via the edge-light export condition. | | Deno | ✅ | dist/index.js | npm:@blursec/sdk. | | Bun | ✅ | dist/index.js | Native. |


Configuration

new Blursec({
  apiKey: process.env.BLURSEC_API_KEY!, // required

  // optional
  environment: "production" | "staging" | "local",
  baseUrl: "https://self-hosted.example/api",
  timeoutMs: 30_000,             // default request timeout (not credential check)
  defaultHeaders: { "X-Tenant": "acme" },
  fetch: customFetchImpl,        // for retries, telemetry, mocking

  // enterprise: structured audit logging (PII-scrubbed automatically)
  logger: createConsoleLogger(),
  logLevel: "info",              // "debug" | "info" | "warn" | "error"

  // enterprise: process-local circuit breaker
  circuitBreaker: {
    enabled: true,               // default
    failureThreshold: 5,         // open after 5 consecutive infra failures
    resetTimeoutMs: 30_000,      // probe again after 30s
  },
});

Full reference: docs/configuration.md.


Enterprise features

The SDK ships with the surface a SOC 2 / GDPR / PDPPL-compliant deployment needs out of the box:

1. Privacy-preserving telemetry

Every value emitted to the configured logger first passes through scrubPII, which masks emails, bearer tokens, JWTs, API-key-shaped strings, long hex blobs, SSN-shaped strings, and PAN-shaped digit blocks. Sensitive object keys (authorization, cookie, password, email, hash, userId, ...) are masked regardless of value content.

import { scrubPII } from "@blursec/sdk";

scrubPII({
  email: "[email protected]",
  headers: { Authorization: "Bearer eyJhbGciOi..." },
  note: "user 4242-4242-4242-4242",
});
// → { email: "[REDACTED]", headers: { Authorization: "[REDACTED]" }, note: "user [REDACTED]" }

2. Audit-grade structured logger

Plug any structured-logging stack into the SDK by implementing the two-method BlursecLogger interface. The SDK emits named events (client.initialised, credentials.check.success, credentials.check.failed_open, circuit.opened, auth.rotated, session.killed, session.flagged, ...) so consumers can filter on stable identifiers without string-matching free-form messages.

audit-level entries are never filtered by logLevel — they always reach the sink, so the SOC 2 audit trail stays complete.

import { Blursec, createConsoleLogger } from "@blursec/sdk";

const blursec = new Blursec({
  apiKey: process.env.BLURSEC_API_KEY!,
  logger: createConsoleLogger(),
  logLevel: "info",
});

3. Circuit breaker (fail-open / fail-closed)

When the Blursec API is unhealthy every credential check would otherwise burn the full timeoutMs — multiplying /login latency at exactly the moment login traffic is most sensitive. The breaker tracks consecutive infrastructure failures (timeouts, network errors, 5xx, malformed responses) and, once the threshold trips, short-circuits subsequent calls. Those calls fail open by default with failureReason: "circuit_open", so legitimate users are never blocked.

const result = await blursec.credentials.check(email, password);
if (result.failedOpen) {
  metrics.increment("blursec.failed_open", { reason: result.failureReason });
}

4. Type branding

Branded primitives (ApiKey, UserId, SessionToken, RequestId, HashHex) prevent accidental mix-ups at compile time — zero runtime cost. Opt in by minting values through the matching constructors:

import { apiKey, sessionToken } from "@blursec/sdk";

const k = apiKey(process.env.BLURSEC_API_KEY!);
const s = sessionToken(req.cookies.session);

await blursec.verifySession(s);  // ✅
await blursec.verifySession(k);  // ❌ Argument of type 'ApiKey' is not assignable to 'RequestLike | string | SessionToken'

Existing callers that pass plain string continue to work — branding is opt-in.


Reference


Examples


Public API

export { Blursec, BlursecClient } from "@blursec/sdk";

export {
  BlursecError, BlursecAPIError, BlursecValidationError, BlursecTimeoutError,
  BlursecCircuitOpenError,
  AuthResource, CredentialsResource, SessionsResource, WebhooksResource,
  CircuitBreaker,
  // hashing
  hashCredential, hashString, matchesHash, canonicalCredential,
  HASH_PREFIX_LENGTH,
  // session-token utilities
  canonicalSessionToken, detectTokenType, extractSessionToken,
  // webhook verification
  verifyWebhookSignature, signWebhookPayload,
  BLURSEC_WEBHOOK_HEADERS, DEFAULT_WEBHOOK_TOLERANCE_MS,
  // Enterprise: structured logging
  createConsoleLogger, createNoopLogger,
  // Enterprise: PII scrubbing
  scrubPII, scrubString, REDACTED,
  // Enterprise: type branding
  apiKey, userId, sessionToken, requestId, hashHex, unbrand,
} from "@blursec/sdk";

export type {
  BlursecClientConfig, BlursecEnvironment,
  CredentialRiskResult, CheckOptions, LeakMatch, LeakSeverity, RecommendedAction, RequestContext,
  SessionVerificationResult, VerifySessionOptions,
  WebhookHeaders, VerifyWebhookOptions,
  AuthPrincipal, RotatedApiKey,
  HashedCredential,
  RequestLike, ExtractSessionTokenOptions, TokenType,
  HttpMethod, RequestOptions, BlursecApiErrorPayload,
  // Enterprise types
  BlursecLogger, LogEntry, LogLevel, AuditEvent,
  CircuitBreakerConfig, CircuitState,
  ApiKey, UserId, SessionToken, RequestId, HashHex, Brand,
} from "@blursec/sdk";

Documentation & links


License

MIT