> ## Documentation Index
> Fetch the complete documentation index at: https://docs.blursec.dev/llms.txt
> Use this file to discover all available pages before exploring further.

# Credentials

# `blursec.credentials`

Pre-auth prefix-blinded checks against the Blursec stealer-log database. This is the core surface of the SDK.

```ts theme={null}
import { Blursec } from "@blursec/sdk";

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

const risk = await blursec.credentials.check(email, password);
```

***

## Methods

### `check(email, password, options?) → Promise<CredentialRiskResult>`

Layer 1 — email + password pair.

The SDK locally:

1. Builds the canonical string `lowercase(email) + ":" + password`.
2. SHA-256 hashes it via `crypto.subtle.digest`.
3. Sends **only the first 10 hex chars** of the digest to `POST /v1/credentials/check`.
4. Compares the remaining 54 chars against returned `hashSuffix` values.
5. Returns a `CredentialRiskResult`.

The raw password never appears on the network.

```ts theme={null}
const risk = await blursec.credentials.check("alice@example.com", "P@ssw0rd!");

if (risk.leaked) {
  console.warn("Leaked credential:", risk.severity, risk.recommendedAction);
}
```

### `checkToken(sessionToken, options?) → Promise<CredentialRiskResult>`

Layer 2 — session cookie / bearer token. Same k-anonymity model, but hashes the token alone.

Use on **every authenticated request** if you suspect a session might have been hijacked (or routinely, e.g. on sensitive endpoints).

```ts theme={null}
const risk = await blursec.credentials.checkToken(req.cookies.session);
if (risk.leaked && risk.severity === "critical") {
  await yourSessionStore.revokeAllFor(req.userId);
  return res.status(401).end();
}
```

> For request-aware token extraction (JWT vs opaque auto-detection, cookie/header
> fallbacks) prefer [`blursec.sessions.verify(req)`](/docs/resources/sessions) — it
> wraps `checkToken` and returns `tokenType` + `compromised`.

### `checkHash(fullSha256HexDigest, options?) → Promise<CredentialRiskResult>`

Low-level escape hatch. Pass a 64-char SHA-256 hex digest you computed yourself (e.g. inside a WebAssembly worker, or when integrating with a non-standard transport). The SDK still only sends the prefix.

***

## `CheckOptions`

All three methods accept the same options bag:

```ts theme={null}
interface CheckOptions {
  context?: RequestContext;       // Layer 3 — device & connection signals
  failOpen?: boolean;             // default: true
  timeoutMs?: number;             // default: 1500
  signal?: AbortSignal;           // cancellation, composes with timeout
  severityActions?: Partial<Record<LeakSeverity, RecommendedAction>>;
  dryRun?: boolean;               // default: false — observe-only mode
}
```

### `context` — Layer 3 risk scoring

Optional metadata about the request. When provided, Blursec factors device & connection signals into the response severity (e.g. a leaked credential coming from a known-good IP scores lower than one coming from a Tor exit node).

```ts theme={null}
const risk = await blursec.credentials.check(email, password, {
  context: {
    ip: req.ip,
    userAgent: req.headers["user-agent"],
    deviceFingerprint: req.fingerprint,
    country: req.geo?.country,
    userId: existingUserId, // if known
  },
});
```

### `failOpen`

When `true` (default), the SDK swallows timeouts, network errors, and 5xx responses, returning `{ leaked: false, failedOpen: true }`. This guarantees a Blursec outage cannot lock out legitimate users.

When `false`, the SDK throws — see [errors.md](/docs/errors).

### `timeoutMs`

Per-call timeout. Defaults to `1500 ms` (generous headroom above the typical sub-50 ms response time). Combined with `failOpen: true`, this ensures the worst-case latency added to your login flow is bounded.

```ts theme={null}
// Very strict latency budget
const risk = await blursec.credentials.check(email, password, {
  timeoutMs: 200,
});
```

### `signal`

Standard `AbortSignal`. Composes with the SDK's internal timeout — whichever fires first wins.

### `severityActions`

Override the default `severity → recommendedAction` mapping for one call:

```ts theme={null}
const risk = await blursec.credentials.check(email, password, {
  severityActions: {
    low: "force_reset",   // be stricter
    high: "kill_sessions",
  },
});
```

### `dryRun`

Observe-only mode. When `true`, the SDK still queries the API and reports the
real `leaked` / `severity` / `firstSeen`, but coerces `recommendedAction` to
`"allow"` so the integration never blocks traffic. The action it *would* have
taken is surfaced on `enforcedAction`. Recommended for the first 1–2 weeks of
any rollout.

```ts theme={null}
const risk = await blursec.credentials.check(email, password, { dryRun: true });
// risk.recommendedAction === "allow"  (always, in dry-run)
// risk.enforcedAction    === "block"  (what it *would* have been)
metrics.increment("blursec.shadow", { action: risk.enforcedAction });
```

***

## `CredentialRiskResult`

```ts theme={null}
interface CredentialRiskResult {
  leaked: boolean;
  severity: "low" | "medium" | "high" | "critical" | null;
  recommendedAction:
    | "allow" | "monitor" | "force_reset" | "kill_sessions" | "block";
  firstSeen: string | null;   // ISO 8601
  sources: number;            // # of distinct stealer-log sources
  checkedAt: string;          // ISO 8601, when the SDK ran the check
  durationMs: number;         // end-to-end SDK time
  failedOpen: boolean;        // true when SDK returned a safe default
  failureReason?:             // only set when failedOpen is true
    | "timeout" | "network" | "server_error"
    | "circuit_open" | "malformed_response";
  dryRun?: boolean;           // true when called with { dryRun: true }
  enforcedAction?:            // action that *would* apply outside dry-run
    RecommendedAction;
}
```

### Default severity → action mapping

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

When multiple matches come back for the same prefix, the SDK picks the **highest** severity, sums `sources`, and picks the **earliest** `firstSeen`.

***

## The convenience shortcut

`blursec.checkCredential(email, password, options?)` is an exact alias for `blursec.credentials.check(email, password, options?)`. Use whichever reads better:

```ts theme={null}
// equivalent
await blursec.checkCredential(email, password);
await blursec.credentials.check(email, password);
```

***

## Manual hashing

If you want to perform the prefix-blinded lookup yourself (e.g. you have a custom transport):

```ts theme={null}
import { hashCredential, matchesHash, HASH_PREFIX_LENGTH } from "@blursec/sdk";

const hashed = await hashCredential(email, password);
// → { full, prefix (10 hex chars = 40 bits), suffix (54 hex chars) }

const matches = await fetchFromBlursec(hashed.prefix); // your transport
const leaked = matches.some((m) => matchesHash(hashed.full, m.hashSuffix));
```

> **Threat model.** A 40-bit prefix is one-way (SHA-256 is preimage-resistant) and cannot be reversed back to the credential. However, a malicious or compromised Blursec backend could in principle log prefixes and correlate them across requests to identify specific credentials in its own database. The SDK's confidentiality story trusts the Blursec backend not to do this. If your threat model does not allow that trust, run the database on-prem.

See [examples/k-anonymity.ts](https://github.com/blur-sec/blursec-sdk/blob/main/examples/k-anonymity.ts) for a runnable walkthrough.
