JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 29
  • Score
    100M100P100Q73172F
  • License Apache-2.0

AI agent runtime security: context-preserving tokenization, risk scoring, and policy enforcement

Package Exports

  • @priventai/core
  • @priventai/core/contracts
  • @priventai/core/package.json
  • @priventai/core/vault/memory

Readme

@priventai/core

Early Access. Privent is currently in private rollout. API keys are issued through our access process. Request access →

Runtime security for AI agents. Tokenize PII, secrets, and sensitive data before they reach LLMs; restore the originals only at trusted egress points.

"Customer: alice@acme.com, Card: 4111-1111-1111-1111"
                          ↓ tokenize
"Customer: [EMAIL_001], Card: [CREDIT_CARD_001]"
                          ↓ LLM call
"Reply to [EMAIL_001] about [CREDIT_CARD_001]"
                          ↓ detokenize at trusted sink
"Reply to alice@acme.com about 4111-1111-1111-1111"

The LLM never sees raw secrets. Tokens are reversible, session-scoped, and deterministic within a session.


Features

  • Hybrid detection — regex patterns for EMAIL, PHONE, CREDIT_CARD, IBAN, SSN, API_KEY, JWT, AWS_KEY, IP, URL out of the box; pluggable ML extractor.
  • Session-scoped vault — same value maps to the same token within a session; cross-session tokens are randomized to block correlation attacks.
  • Deep detokenization — walks nested objects, arrays, and strings; cycle-safe.
  • Typed error taxonomyPriventError base class plus 9 subclasses, each with a retryable flag and JSON serialization that scrubs sensitive fields.
  • Resilient HTTP client — exponential backoff with jitter, idempotency keys, AbortController-based timeouts.
  • Fail-open by default — if Privent Cloud is unreachable, falls back to local regex detection rather than blocking traffic.
  • Dual ESM + CJS build with full TypeScript types.

Installation

npm install @priventai/core
# or
pnpm add @priventai/core
# or
yarn add @priventai/core

Requires Node.js 20+.


Quick Start

import { PriventClient } from '@priventai/core';

const client = new PriventClient({
  apiKey: process.env.PRIVENT_API_KEY, // request via https://www.privent.ai/request-access — without it, runs in regex-only mode
});

await client.withSession(async ({ vault }) => {
  const { tokenizedText } = await client.tokenizer.tokenize(
    'Customer: alice@acme.com, Card: 4111111111111111',
    vault,
    { kinds: ['EMAIL', 'CREDIT_CARD'] },
  );
  // tokenizedText === "Customer: [EMAIL_001], Card: [CREDIT_CARD_001]"

  const llmResponse = await myLLM.invoke(tokenizedText);

  const restored = await client.tokenizer.detokenize(llmResponse, vault);
  return restored;
});
// vault.destroy() runs automatically in a finally block

API

PriventClient

Main entry point. Wires together a vault, tokenizer, risk scorer, policy engine, and audit logger.

const client = new PriventClient({
  apiKey?: string;                 // defaults to PRIVENT_API_KEY env var
  baseUrl?: string;                // default: https://api.privent.ai
  vaultFactory?: VaultFactory;     // default: in-memory
  tokenizer?: Tokenizer;           // default: HybridTokenizer (regex)
  riskScorer?: RiskScorer;
  policyEngine?: PolicyEngine;
  auditLogger?: AuditLogger;
  maxRetries?: number;             // default: 2
  timeout?: number;                // default: 30_000 ms
  failPolicy?: 'open' | 'closed';  // default: 'open'
});

client.withSession(fn)

Opens a session, gives fn a vault and IDs, and destroys the vault when fn resolves or throws.

const result = await client.withSession(async ({ vault, sessionId, traceId }) => {
  // vault: TokenVault — store/retrieve tokens
  // sessionId: per-session UUID
  // traceId: correlation ID for audit logs
  return doWork(vault);
});

Tokenizer

const { tokenizedText, entities } = await client.tokenizer.tokenize(text, vault, {
  kinds: ['EMAIL', 'PHONE'],
  allowList: ['noreply@system.com'],   // never tokenize
  denyList: ['internal-project-x'],    // always tokenize
  customPatterns: [
    { kind: 'PROJECT_CODE', regex: /PROJ-\d{4}/g, confidence: 0.95 },
  ],
});

Token format: [KIND_NNN] — e.g. [EMAIL_001], [CREDIT_CARD_003]

Built-in entity types:

Kind Confidence Notes
EMAIL 0.95 RFC-pragmatic match
PHONE 0.80 International + national formats
CREDIT_CARD 0.98 Validated with Luhn checksum
IBAN 0.97 Country-aware length check
SSN 0.90 US format
API_KEY 0.88 Common provider prefixes (sk-, ghp_, xoxb-, …)
JWT 0.98 Three-segment base64url structure
AWS_KEY 0.99 AKIA… access key IDs
IP_ADDRESS 0.85 IPv4
URL 0.90 http/https

detokenizeDeep(value, vault)

Walks any value (string, object, array, Map, Set) and replaces tokens with their originals.

import { detokenizeDeep } from '@priventai/core';

const restored = await detokenizeDeep(
  { email: '[EMAIL_001]', items: ['[PHONE_001]', 'plain text'] },
  vault,
);

Cycle-safe (uses WeakSet), depth-limited to 64, skips binary buffers, fast-paths strings without [.

Vault

import { InMemoryTokenVault } from '@priventai/core/vault/memory';

const vault = new InMemoryTokenVault('session-1');
await vault.store({ token: '[EMAIL_001]', value: 'a@b.com', kind: 'EMAIL', ... });
const entry = await vault.retrieve('[EMAIL_001]');
const same = await vault.findByValue('a@b.com', 'EMAIL'); // determinism
await vault.destroy();

Normalization for determinism: EMAIL → lowercase + trim; PHONE, CREDIT_CARD, IBAN → digits only; others → trim.

Errors

import {
  PriventError,
  PriventConfigError,
  PriventAuthError,
  PriventRateLimitError,
  PriventAPIError,
  PriventNetworkError,
  PriventTimeoutError,
  PriventValidationError,
  PriventVaultFullError,
  PriventVaultDestroyedError,
} from '@priventai/core';

try {
  await client.tokenizer.tokenize(text, vault);
} catch (err) {
  if (err instanceof PriventError) {
    console.log(err.code, err.retryable, err.toJSON());
  }
}

toJSON() scrubs sensitive fields so errors are safe to log.


Audit Events

The AuditEvent.type union covers eight kinds: session_open | tokenize | detokenize | risk_check | llm_call | policy_decision | egress | error. 'llm_call' is emitted by the external @priventai/n8n-hook package; the in-tree n8n-nodes-privent package emits the first four (plus error on failure paths).

Wire contract: every event is serialized to the v1 schema (POST /v1/audit/events) at flush time. The TypeScript AuditEvent interface is camelCase (traceId, sessionId, workflowId, nodeId, numeric timestamp, optional framework: 'manual'); the wire payload is snake_case (trace_id, session_id, workflow_id, node_id, ISO8601 timestamp). A UUID event_id is generated per event for backend idempotency, and framework: 'manual' is silently coerced to 'sdk' on the wire (the value is deprecated).

AuditEvent.metadata is a free-form Record<string, unknown> on the wire — adapters populate snake_case keys (agent_name, workflow_name, execution_id, node_name) using the NodeAuditContext typing aid, plus event-type-specific extras (sink_id / sink_url_host / sink_trusted on detokenize, risk_score / risk_level / categories on risk_check, prompt_tokens / completion_tokens / latency_ms / provider / model on llm_call, etc.).

The v1 wire schema also accepts optional top-level scalars and config blocks that the backend denormalizes into indexed columns and auto-discovered resource tables, so dashboards can read them without JSON-extract at query time:

Top-level field Purpose
latency_ms Per-event latency (int, ms). Surfaced as the node's P50/P95/P99 in the inspector.
error_type Short error classifier (≤64 chars) used for the error-breakdown panel.
node_name Mirror of metadata.node_name; promoted for indexed filtering.
http_status Response code for sink/egress events; powers the 4xx/5xx KPI cells.
webhook_config { url, method?, auth_scheme?, content_type? } — backend upserts a Webhook row by (org, url, method).
sink_config { sink_key, host, trusted?, tls_version?, cert_fingerprint?, added_by_user_id? } — upserts a Sink row by (org, sink_key).
vault_config { name, region?, retention_days?, detection_mode?, detection_version? } — upserts a Vault row by (org, name).
cron_config { cron_expression, timezone? } — upserts a CronSchedule row by (org, cron_expression, workflow_id).

All seven fields are optional. Existing callers that don't set them continue to work — the backend treats every event without the new fields exactly as it did before. The AuditEvent interface exposes the same fields in camelCase (latencyMs, errorType, nodeName, httpStatus, webhookConfig, sinkConfig, vaultConfig, cronConfig); serializeForWire handles the snake_case translation.

Critical events flush immediately rather than waiting for the buffer interval: any error event, and policy_decision events whose metadata.decision === 'BLOCK'.

Sample wire payload for a sink egress event using both metadata and the new top-level shape:

{
  "event_id": "8a3f…",
  "type": "egress",
  "trace_id": "…",
  "session_id": "…",
  "timestamp": "2026-05-23T12:00:00.000Z",
  "framework": "n8n",
  "workflow_id": "abc123",
  "node_id": "<node uuid>",
  "node_name": "Notify Slack",
  "latency_ms": 412,
  "http_status": 200,
  "sink_config": {
    "sink_key": "https://hooks.slack.com/services/T0/B0/x",
    "host": "hooks.slack.com",
    "trusted": true,
    "tls_version": "TLS1.3",
    "cert_fingerprint": "sha256/AAA…"
  },
  "metadata": {
    "agent_name": "support-bot",
    "workflow_name": "Inbound Triage",
    "execution_id": "exec-42",
    "node_name": "Notify Slack",
    "framework": "n8n"
  }
}

Security Properties

  • No persistence by default. The in-memory vault holds entries for the session lifetime only.
  • Cross-session randomization. Token IDs are not derived from input value — observing tokens across sessions reveals nothing.
  • Bounded vaults. Default cap of 10,000 entries per session prevents unbounded growth.
  • Sensitive fields scrubbed in error JSON. API keys, tokens, and entity values are stripped before serialization.
  • Idempotency keys on every Cloud request to make retries safe.

Documentation


License

Apache-2.0 © Privent AI

Questions? Contact us at hello@privent.ai.