# Security & Compliance — `@kaltura/intelligent-agents`

Defaults are secure. Where a strict setting would hurt usability, the SDK keeps a usable default and gives you an option to tighten it, noted in this file.

- No runtime dependencies, no install scripts. The SDK is sourced directly from git (`src/`), not a package registry, so there's zero transitive supply-chain surface.
- All cryptography is delegated to the platform. The SDK never rolls its own crypto: TLS and DTLS-SRTP (the encryption protocol securing WebRTC audio and video) come from the host (Node `tls`/`crypto`, browser WebCrypto/TLS). Run Node/OpenSSL in FIPS mode and a FIPS-validated OS/browser to operate in a FIPS-validated configuration (NIST SC-13).
- Secrets never reach the client. The Admin Secret lives only server-side, in `Management`. The browser `KalturaAvatarSession` takes only a short-lived, entitlement-ON user-session token.

Framework crosswalks map the SDK control-by-control to HIPAA, HITRUST, the OWASP LLM/Agentic Top 10, and avatar/deepfake/voice-clone law (EU AI Act Art. 50, NO FAKES, CA SB 1001, BIPA, C2PA). See the [framework crosswalks](#framework-crosswalks) section below, which also includes a copy-paste secure production baseline config.

## Reporting a vulnerability

Email `security@kaltura.com` with details and a PoC if available. Please do not open a public issue for an undisclosed vulnerability. We acknowledge within a few business days and coordinate disclosure.

## Table of contents

- [AI-application controls](#ai-application-controls-owasp-llmagentic-hipaa-technical-safeguards)
- [KS guidance for agents](#ks-kaltura-session-guidance-for-agents-ac-3--ac-6--ia-2)
- [Session type](#session-type)
- [Token lifecycle](#token-lifecycle-rfc-9700-oauth-20-security-bcp-nist-ac-family)
- [Audit logging](#audit-logging-nist-au-2--au-3--au-12-owasp-logging-soc-2-cc7)
- [Transport security](#transport-security-nist-sc-8-owasp-wsstls)
- [Browser hardening](#browser-hardening-owasp-asvs--websocket-cs)
- [Isolation & multi-tenancy](#isolation--multi-tenancy-nist-sc-4--ac-64)
- [Supply-chain integrity](#supply-chain-integrity-slsa--openssf--eo-14028)
- [Shared-responsibility control matrix](#shared-responsibility-control-matrix-nist-800-53)
- [FIPS mode](#fips-mode-how-to)
- [Data residency](#data-residency-sc-7)
- [Data handling and deletion](#data-handling-and-deletion)
- [Framework crosswalks](#framework-crosswalks)
- [HIPAA](#hipaa-45-cfr-part-164)
- [HITRUST CSF](#hitrust-csf-incl-the-ai-security-assessment)
- [OWASP Top 10 for LLM Applications](#owasp-top-10-for-llm-applications-2025)
- [OWASP Agentic](#owasp-agentic-agentic-security-initiative--top-10-for-agentic-apps)
- [Avatar / digital-human / deepfake / voice-clone](#avatar--digital-human--deepfake--voice-clone)
- [Secure production baseline](#secure-production-baseline-cm-6)

## AI-application controls (OWASP LLM/Agentic; HIPAA technical safeguards)

Guardrails for the AI/agent layer. Most are opt-in: pass the callback or option. Framework mapping: [framework crosswalks](#framework-crosswalks).

<div data-nova-target="guardrail-list" data-nova-label="Guardrail list: output handling, input guardrail, agentic gate, consumption valve, HIPAA safeguards, deepfake disclosure">

- **Output handling (LLM05).** Opt-in: `safeUrl`, `safeText`, `renderSafeLink` (DOM-built, scheme-checked, never `innerHTML`). Call these yourself when rendering avatar text. On by default: inbound clamping of captions/segments.
- **Input guardrail (LLM01).** Opt-in: `onBeforeSend(text, ctx)` may transform or block a turn. It's a no-op until you pass it.
- **Agentic gate (LLM06 / ASI 01-02).** Opt-in: `onAgentAction(action)` and the declarative `agentActions` policy. Always available: the read-only `capabilities` surface and the `stop()` kill switch.
- **Consumption valve (LLM10).** Opt-in: `maxTurnsPerMinute` (unlimited until you set it).
- **HIPAA technical safeguards.** On by default: `idleTimeoutMs` auto-logoff (164.312(a)(2)(iii)), 900000 ms (15 minutes); pass `0` to disable. Opt-in: the opaque `subjectId` unique-user-id (164.312(a)(2)(i)). Content-free turn audit events (164.312(b)) fire automatically once you wire `onAuditEvent`.
- **Avatar/deepfake.** On by default: disclosure-before-speech with `synthetic`/`provenance` data, fired before the avatar's first words on every connect. `getDisclosure()` is queryable any time. Opt-in: `requireDisclosureAck` (also a biometric-consent gate) and an optional `consentRef` on voice/visual cloning.

</div>

## KS (Kaltura Session) guidance for agents (AC-3 / AC-6 / IA-2)

A KS carries the privileges that decide what it can do. Full privilege reference: [Kaltura API Authentication and Security](https://github.com/kaltura/developer-platform-docs/blob/master/documentation/VPaaS-API-Getting-Started/Kaltura_API_Authentication_and_Security.md).

| Token | Mint with | Privilege | Entitlement | Typical use |
|-------|-----------|-----------|-------------|--------------|
| **admin** | `sessions.createAdminToken({ userId })` | `disableentitlement` | OFF | Management-plane calls (provisioning, config), server-side |
| **conversation / agent** | `sessions.createConversationToken({ configId, userId })` / `createAgentToken({ agentId, userId })` | `setrole:PLAYBACK_BASE_ROLE`, `geniegpcid:<configId>`, and `agentid:<id>` (agent tokens, or conversation tokens minted with `agentId`) | ON | The token your server hands a live avatar/chat session. A user session (KS type 0) by default. See [Session type](#session-type) |
| **widget** | `sessions.createWidgetToken({widgetId})` | server-derived | ON | Public, secret-free anonymous embed, safe to mint straight from the browser. See [Session type](#session-type) |

For anything that reaches a browser, mint `conversation`/`agent` tokens (with a per-user `userId`) or `widget` tokens. Keep `admin` tokens server-side. `createConversationToken`/`createAgentToken` reject `extraPrivileges` that disable entitlement (`entitlement_violation`). Which of the two to use: [Conversation token or agent token?](https://kaltura.github.io/intelligent-agents-sdk/reference/api/authentication/index.md#conversation-token-or-agent-token).

Admin-type tokens (`createAdminToken()`, or `sessionType: 'admin'` on a conversation/agent token) are for your server. Your server decides at mint time what reaches a browser. The browser SDK can't check it: a real KS's privileges are encrypted, so `inspectKs()` reports `disableEntitlement: null`.

## Session type

`createConversationToken()` and `createAgentToken()` mint a **user session** (KS type 0) by default: `setrole:PLAYBACK_BASE_ROLE`, entitlement ON. `sessionType: 'admin'` mints an admin-level session (KS type 2, no forced role), limited only by the `role` and privileges you set. `createAdminToken()` is always type 2 with `disableentitlement`, server-side only. Every type 2 mint needs a `userId`, which becomes the owner of anything the token creates. Minting options: [Authentication](https://kaltura.github.io/intelligent-agents-sdk/reference/api/authentication/index.md).

What each token can reach:

| Token | Threads | OVP admin APIs |
|---|---|---|
| **Admin type (type 2)**: `createAdminToken()`, or `sessionType: 'admin'` | Every user's threads | Yes, within the `role` and privileges you set |
| **Conversation/agent with `userId`** (user session) | Own threads: read, continue, list, delete | No |
| **Conversation/agent without `userId`** | One identity for all holders. Read, list and delete return 403 | No |
| **Widget** | Same as without `userId` | No |

- **Always pass a per-user `userId`** when you mint a conversation or agent token. Any stable opaque id works. The user needs no registration.
- **Widget tokens can't separate users.** Keep each `threadId` on your server, stored per user. To separate users in a browser, use the [per-visitor path](https://kaltura.github.io/intelligent-agents-sdk/reference/api/deploy/index.md#per-visitor-browser-path).
- **A `threadId` owned by another user is refused.** The server answers HTTP 200 with an `error` segment. The SDK throws `thread_access_denied`: [Refusals that arrive with HTTP 200](https://kaltura.github.io/intelligent-agents-sdk/reference/api/operate/index.md#refusals-that-arrive-with-http-200).
- Which methods take which token: [Threads](https://kaltura.github.io/intelligent-agents-sdk/reference/api/operate/index.md#threads).

`subjectId` is for audit only. It is not an access boundary. Use `userId` on the token for that.

## Token lifecycle (RFC 9700 OAuth 2.0 Security BCP; NIST AC family)

- Short-lived by default: browser-bound tokens (`conversation`/`agent`) default to 30 minutes, admin to 1 hour. Short TTL is the primary revocation lever for a stateless KS (RFC 9700 §6.1). Override per call with `ttlSeconds`, up to 86400 s (24 h) for browser-bound kinds and 7 days for admin; longer throws `ttl_too_long`. To refresh, your server mints a fresh token and the browser calls `session.setToken(freshKs)`, with no reconnect.
- Least privilege / binding (RFC 9700 §2.3, §4.10): tighten a token with the structured `restrictions` option instead of hand-crafting privilege strings. `{ role, actionsLimit, ipRestrict, uriRestrict, sessionGroupId }` compiles to the matching Kaltura privileges (`setrole`/`actionslimit`/`iprestrict`/`urirestrict`/`sessionid`). `role` needs `sessionType: 'admin'`. All restrictions are optional; none is set by default. Values and limits: [Authentication](https://kaltura.github.io/intelligent-agents-sdk/reference/api/authentication/index.md).
- Active revocation (RFC 9700 §5.2.1.1; SOC 2 CC6.2/CC6.3): `sessions.revoke(tokenOrKs)` ends a leaked token within seconds (Kaltura `session/end`). Tokens that share a `restrictions.sessionGroupId` are revoked together: revoking any member ends the whole family. Returns a receipt: `{ revokedAt, partnerId, _meta }`.
- Vault/KMS (NIST IA-5): pass `getAdminSecret: () => fetchFromVault()` to fetch the secret per-mint instead of holding it; it is never stored as an enumerable field.

## Audit logging (NIST AU-2 / AU-3 / AU-12; OWASP Logging; SOC 2 CC7)

The SDK is an event emitter, not a logging framework. Pass `onAuditEvent` to `Management` and/or `KalturaAvatarSession` to receive discrete, already-redacted, structured `AuditEvent` objects, and route them into your SIEM. This is opt-in: no event fires until you pass this hook.

```js
new Management({ partnerId, adminSecret, onAuditEvent: (e) => siem.write(e) });
```

### Event catalog

| Event | Fires when |
|---|---|
| `token.mint` | A KS is minted (admin, conversation, agent, or widget) |
| `token.revoke` | `sessions.revoke()` ends a token |
| `token.refresh` | `setToken()` rotates a live session's token |
| `guard.reject` | A call is rejected for carrying the wrong token kind (e.g. an admin token where a conversation token was required) |
| `auth.fail` | A request returns HTTP 401 or 403. A failed `conversations.stream` request fires it for any error status |
| `session.connect` | `KalturaAvatarSession` finishes connecting |
| `session.disconnect` | `KalturaAvatarSession` disconnects |
| `session.timeout` | The idle timeout fires and auto-disconnects the session |
| `guardrail.block` | `onBeforeSend` or `onAgentAction` blocks a turn or action |
| `rate.limit` | `maxTurnsPerMinute` rejects a turn |
| `turn.user_captured` | The user's speech or text turn is captured |
| `turn.avatar_spoke` | The avatar starts speaking a turn |
| `turn.converse` | `KalturaChatSession.sendText()` completes or fails |
| `tool.invoke` | A client-side tool call is invoked (from user text or an agent action) |
| `tool.ack` | The app ACKs a client-side tool call via `respondToTool()` |
| `tool.spiral_detected` | The soft tool-call-spiral threshold trips for the current turn |
| `tool.spiral_hard_recovery` | The hard tool-call-spiral limit trips and the session force-reconnects |
| `agent.action.allow` | `onAgentAction` allows an agent-initiated action |
| `agent.action.deny` | `onAgentAction` denies an agent-initiated action |
| `clone.consent` | A `consentRef` is recorded on a voice/visual clone upload |
| `whep.release` | The WHEP (WebRTC-HTTP Egress Protocol) video resource fails to release cleanly on disconnect |
| `session.complete` | The session-completion signal (`POST /thread/session_completed`) fires on disconnect, tab-close, backgrounding, or bfcache freeze. It reuses the session's own conversation KS and mints no new credential. `action` carries the trigger reason (`disconnect`, `pagehide`, `hidden_grace`, `suppressed:peers=N`, …). See [README.md § Ending a conversation cleanly](https://github.com/kaltura/intelligent-agents-sdk/blob/main/README.md#ending-a-conversation-cleanly-session_completed-signal) |

### Event fields

Every event carries this AU-3 content shape:

| Field | Type | Description |
|---|---|---|
| `ts` | string | ISO-8601 UTC timestamp |
| `type` | string | One of the event names in the catalog above |
| `severity` | string | `info`, `warning`, or `error` |
| `outcome` | string | `success` or `fail` |
| `requestId` | string | Correlation id, reused from the triggering call |
| `actor.partnerId` | string | Kaltura partner id |
| `actor.subjectId` | string | Opaque operator-supplied user id, if set. Audit only, not an access boundary |
| `actor.kind` | string | Token kind (`admin` / `conversation` / `agent` / `widget`) |
| `actor.sessionType` | string | `user` or `admin`. Not set for widget tokens |
| `actor.entitlementEnforced` | boolean | Whether entitlement was ON for this actor |
| `action` | string | The specific action taken |
| `scope` | string | The privilege string in effect, one-lined |
| `target` | string | Resource acted on, if any |
| `reason` | string | Failure reason, if any |
| `source` | string | Which SDK entry point emitted the event |
| `expiresAt` | number | Unix epoch seconds. Set on `token.mint` only |
| `_meta` | object | Provenance receipt |

### Guarantees

- The raw KS is never included in an event. Only its kind and scope are.
- Free-text fields are stripped of CR/LF (CWE-117 log-injection guard).
- A throwing SIEM sink can never break a mint or a live turn. Event emission is crash-safe.
- This audit stream is distinct from the chatty debug `logger` and is never gated behind a debug level.

## Transport security (NIST SC-8; OWASP WSS/TLS)

- `KalturaAvatarSession` rejects non-TLS `conversationManagerUrl`/`srsBaseUrl` (`insecure_transport`). The same rule covers every `Management` base URL and the sessions' `genieUrl`.
- A base URL that is empty, does not parse, uses a scheme other than `https`/`http`, or carries a username or password throws `bad_request` at construction.
- Loopback and private hosts (`localhost`/`127.0.0.1`, RFC 1918 ranges, link-local, and their IPv6 equivalents) are allowed for dev, with a loud one-time warning.
- Cleartext to a public host requires an explicit `allowInsecureTransport:true` (dev/test only, never production).
- Pass server-minted ephemeral TURN credentials (`turnCredentials` from appInit, RFC 7635). Without them the SDK uses its default TURN credentials and warns.

## Browser hardening (OWASP ASVS / WebSocket CS)

- Memory-only token: the SDK keeps the token in a non-enumerable instance field and drops it on `disconnect()`. Do not put a conversation token in `localStorage`/`sessionStorage` (XSS-exfiltratable). Pass it directly and re-mint from your server on reload.
- No token in URLs: tokens travel only in the socket `auth` field or the `Authorization` header, never a query string (OWASP API2:2023).
- Prototype-pollution guard: `setDynamicPrompt` data is scrubbed of `__proto__`/`constructor`/`prototype` before it touches the wire.
- CSP: the SDK uses no `eval`/`new Function`. A working policy sets:
  - `connect-src` to your live-session control-plane host (`conversationManagerUrl`), the WHEP video-egress host (`srsBaseUrl`), and your TURN host
  - `media-src blob:`
  - `script-src` to your injected socket.io origin, pinned with SRI (see the README's "Injecting socket.io securely")

  Also set `frame-ancestors` on the embedding page. A mic-capable widget needs anti-clickjacking headers.
- AI disclosure (EU AI Act Art. 50): a `disclosure` event fires before the avatar's first words. `requireDisclosureAck` holds the avatar greeting until `acknowledgeDisclosure()`. Note: ASR (Automatic Speech Recognition, the mic-to-text channel) connects before disclosure, so obtain consent before `connect()` in IL/TX/WA.

## Isolation & multi-tenancy (NIST SC-4 / AC-6(4))

No SDK module holds credential or tenant state at module scope. The admin secret, KS, and partnerId live only as (non-enumerable) instance fields. A single process can safely run N `Management` and M `KalturaAvatarSession` instances for different tenants, each with fully independent tokens, transports, and teardown. Nothing is shared or global.

## Supply-chain integrity (SLSA / OpenSSF / EO 14028)

- Zero runtime dependencies, no install lifecycle scripts.
- No registry publish step. The SDK is consumed straight from its git tags (`src/`, imported by path or served via jsDelivr's GitHub CDN). There is no npm package.

## Shared-responsibility control matrix (NIST 800-53)

The SDK generates and protects the records and enforces the client-side controls below; the operator owns storage-side controls a client library cannot provide (retention, tamper-evidence, non-repudiation).

| Control | Family | SDK provides | Operator responsible |
|---------|--------|--------------|----------------------|
| AC-3, AC-6, IA-2 | Access / least privilege | Two-token invariant; user-session tokens by default ([Session type](#session-type)); structured `restrictions` | Role/entitlement config in Kaltura |
| AC-12 | Session termination | Short TTLs; `revoke()`; `disconnect()` drops token + transports | Session-timeout policy |
| AU-2, AU-3, AU-12 | Audit generation/content | Structured, redacted, correlated `AuditEvent`s via `onAuditEvent` | Wire the hook to a SIEM |
| AU-4, AU-9, AU-10, AU-11 | Audit storage/integrity/retention | None | Tamper-evident storage, non-repudiation, retention |
| SC-8 | Transmission confidentiality | https/wss enforced; cleartext rejected | TLS termination, cert management |
| SC-13 | Validated cryptography | Delegates to platform TLS/WebCrypto | Run Node/OS/browser in FIPS mode |
| SC-4 | Info in shared resources | Per-instance isolation; non-enumerable secrets | Process/tenant separation |
| SI-10 | Input validation | Inbound payload validation; prototype-pollution scrub | None |
| IR-6, SI-2 | Incident/flaw response | Security contact; coordinated disclosure | US-CERT/agency reporting timelines |
| GDPR Art. 17 | Right to erasure | `threads.delete()` (admin token, or a per-user token for its own threads; [deletes are soft](https://kaltura.github.io/intelligent-agents-sdk/reference/api/operate/index.md#threads)) and `knowledge.deleteRecord()` | Emails already sent by a `lifecycle` rule's `sendInsightEmail` action are outside `threads.delete()`'s reach. Treat its `recipients` list as a data-processing decision. |

## FIPS mode (how-to)

```bash
# Node with an OpenSSL FIPS provider:
node --enable-fips your-server.js
# or via OpenSSL 3 FIPS provider config (OPENSSL_CONF / fipsmodule.cnf)
```

In the browser, FIPS validation is a property of the OS/browser crypto module. Deploy on a FIPS-validated platform; the SDK adds no non-validated crypto.

## Data residency (SC-7)

The SDK is a thin client. It contacts only the Kaltura endpoints you configure (`agenticUrl`/`genieUrl`/`ovpUrl`/`messagingUrl`/`conversationManagerUrl`/`srsBaseUrl`/`turnServerUrl`). There is no telemetry or hidden beacon. The optional `./experience/analytics` module sends KAVA application events to `analytics.kaltura.com`, and only when your code calls it. Point every URL at your in-boundary (e.g. US-Gov) hosts to keep all data within your authorization boundary.

Session hosts come from `appInit`, not from your config. `appInit` returns the `conversationManagerUrl`, `srsBaseUrl` and `turnServerUrl` for your partner's region. Pass them to `KalturaAvatarSession` unchanged (`new KalturaAvatarSession({ ...appInit, token: appInit.ks })`). Do not hard-code them. If you omit `conversationManagerUrl`, the session falls back to the US host.

The `region` option covers the rest. It fills the four `Management` base URLs (`agenticUrl`, `genieUrl`, `ovpUrl`, `messagingUrl`) and the session `genieUrl` from one [`REGIONS`](https://kaltura.github.io/intelligent-agents-sdk/reference/api/authentication/index.md#regions-and-base-urls) entry. `Management` never falls back to another region: a service the region lacks throws `region_unavailable` instead of reaching a US host. `region` does not change the analytics host.

This residency guarantee covers the SDK's own configured endpoints only. A `lifecycle` rule's `sendInsightEmail` action sends thread-derived content to the `recipients` you list, and the URL-pinning above does not apply to it. Your code decides which recipients and regions are allowed. A deployment that must protect PHI/PII boundaries vets the `recipients` list and reviews residency before it uses this action.

## Data handling and deletion

The SDK stores no conversation data itself. The one exception is Presenter session memory, which stays off unless you pass a `storage` object. How long Kaltura keeps data is a property of your account: ask your Kaltura Account Manager.

Every delete needs an admin token (a user-session token can also delete its own threads) and `{ confirmPermanent: true }`.

| Data | Read or list | Delete |
|---|---|---|
| Threads | `threads.list`, `get`, `transcript` | `threads.delete(ids, ks, confirm)`. Deletes are soft: [behavior and result shape](https://kaltura.github.io/intelligent-agents-sdk/reference/api/operate/index.md#threads) |
| Messages | `messages.list`, `get`, `report` | No method. The SDK cannot delete a single message |
| Knowledge records | `knowledge.list`, `getRecord` | `knowledge.deleteRecord(id, ks, confirm)`. It refuses while an intellect still lists the record, unless you pass `force: true`. `knowledge.removeSource` removes one source from a record |
| Agents, avatars, intellects | `agents.list`, `avatars.list`, `intellects.list` | `agents.delete`, `avatars.delete`, `intellects.delete`. None cascades: deleting an agent keeps its avatar and intellect |
| Tools, skills, secrets, catalog items, lifecycle rules, insight settings, email templates | `list` or `get` on each | `delete` on each |

To erase everything for one end user, list their threads with `filter.userIdEquals`, then pass the ids to `threads.delete`. The SDK has no single call for this and no way to see when a soft delete is purged.

## Framework crosswalks

A client SDK can implement technical controls and generate the records, but it cannot sign a contract, retain logs, or authenticate the human user. Each table marks **SDK** (the library provides it) vs **Operator** (your duty, fed by the SDK's hooks/events).

### HIPAA (45 CFR Part 164)

> Gating item: Kaltura offers a Business Associate Agreement, covering Kaltura and its avatar/ASR/TTS/brain subprocessors, as required before any PHI flows. Contact your Kaltura Account Manager or Customer Success Manager to execute one.

| Safeguard (CFR) | SDK provides | Operator responsible |
|---|---|---|
| **164.312(e)(1)/(e)(2) Transmission security** | https/wss enforced (`insecure_transport`); WebRTC media is DTLS-SRTP; crypto delegated to platform TLS | TLS termination, cert management |
| **164.312(b) Audit controls** | `onAuditEvent` (token + auth + guard lifecycle) and content-free PHI-exchange turn events (`turn.user_captured`, `turn.avatar_spoke`, `session.timeout`), never content | Wire to SIEM; review (164.308(a)(1)(ii)(D)) |
| **164.312(a)(2)(iii) Automatic logoff** | `idleTimeoutMs` (default ON, 900000 ms) → `disconnect()` + `idleWarning` + `session.timeout` audit | Choose the timeout per care setting |
| **164.312(a)(2)(i) Unique user identification** | Optional opaque `subjectId` threaded onto every AuditEvent (audit only, not an access boundary) | Supply an opaque id (never the patient's name/PHI) |
| **164.312(a)(1) Access control** | Two-token invariant; entitlement-ON user-session tokens that reach only the `userId`'s own threads; least-privilege `restrictions` | Role/entitlement config in Kaltura |
| **164.312(d) Person/entity authentication** | Authenticates the session (KS), minted server-side under your control | Proof the patient before minting the conversation token; bind the token with a per-user opaque `userId` |
| **164.502(b) Minimum necessary** | Redaction chokepoint; SDK persists no transcripts/captions/screenshots | Don't persist captions/screenshots beyond minimum necessary; apply retention |
| **164.402 Breach / safe harbor** | PHI encrypted in transit + no token/PHI at rest → supports the encryption safe harbor for the SDK-controlled path | At-rest encryption; breach detection + 164.404/164.410 notification |
| **164.316(b)(2) Retention** | Emits the records | Tamper-evident storage + 6-year retention |

PHI note: a patient may speak PHI to the avatar, so captions, transcripts, screenshots, and request-variable context (`setDynamicPrompt`'s `page_context`) can carry PHI. The SDK surfaces these to your app but persists none of them. Treat them as PHI in your app.

### HITRUST CSF (incl. the AI Security Assessment)

The SDK is an AI Application Provider component you can largely inherit in a HITRUST assessment. Kaltura's platform posture is the upstream inheritance source.

| HITRUST AI requirement | SDK provides | Operator / inherited |
|---|---|---|
| Encrypt traffic to/from the model | https/wss enforced; DTLS-SRTP | Platform/TLS |
| Restrict access to interact with the model | Two-token invariant; entitlement ON; per-user user sessions; `revoke()` | Identity proofing |
| Log AI inputs/outputs (AI.PI.a) | Security + turn audit via `onAuditEvent` (content-free by default) | SIEM storage/retention |
| Model rate limiting / DoS | Client-side `maxTurnsPerMinute` valve | Authoritative server-side quota (inherited) |
| **Humans can intervene (AI.NI.a, non-inheritable)** | `stop()` / `disconnect()`, `revoke()`, barge-in (`interrupt()`), `requireDisclosureAck`, `onAgentAction` veto | Wire at least one to a visible UI control |
| Output filtering / prompt-injection | `safeUrl`/`safeText`/`renderSafeLink` (output); `onBeforeSend` hook (input) | Model-side guardrails (inherited); red-team the intellect (the agent's configured prompts, tools, and knowledge linkage) |
| AI supply chain | Zero deps, no install scripts, no registry publish step | Due-diligence review |
| AI transparency to end-user | Disclosure-before-speech + `getDisclosure()` | Render it accessibly |
| Audit retention / tamper-evidence | Emits records | 6-year-or-longer tamper-evident storage |

> Prompt-injection note: the prototype-pollution scrub on `setDynamicPrompt`/inbound is object-injection defense, not prompt-injection defense. `redact()` is log-scoped, not an output content filter. Don't pass unsanitized end-user text into `setDynamicPrompt` or any request variable. Request variables stay on the thread, so see [Dynamic Data Injection](https://kaltura.github.io/intelligent-agents-sdk/guides/dynamic-data-injection/index.md) for what that means for a poisoned value. Instruction/data separation, source allow-listing, and model guardrails are operator/platform duties.

### OWASP Top 10 for LLM Applications (2025)

| Item | Status |
|---|---|
| **LLM01 Prompt Injection** | `onBeforeSend(text, ctx)` input-filter hook (block/transform). Model-side detection is operator/platform. |
| **LLM02 Sensitive Info Disclosure** | Redaction of secrets in logs/audit. `onBeforeSend` lets you mask outbound PII. Never put secrets in any request variable, including the `page_context` payload `setDynamicPrompt` sends. |
| **LLM03 Supply Chain** | Zero runtime deps, no install scripts, no registry publish step, SRI on the injected socket.io. |
| **LLM05 Improper Output Handling** | `safeUrl` (scheme allow-list), `safeText`, `renderSafeLink` (DOM-built, never `innerHTML`), inbound clamping of captions/segments. Treat avatar text/GenUI (the SDK's on-screen widget layer: flashcards, forms, images rendered from agent output) as untrusted. |
| **LLM06 Excessive Agency** | `onAgentAction` gate + declarative `agentActions` policy + `capabilities` surface + `requireDisclosureAck` HITL. |
| **LLM07 System-Prompt Leakage** | Documented: never embed secrets/authz rules in the provisioned prompt or any request variable (`setDynamicPrompt` included). |
| **LLM10 Unbounded Consumption** | Client `maxTurnsPerMinute` valve (`rate_limited`); server quota is authoritative. |
| LLM04 / LLM08 / LLM09 | Operator: data/model poisoning, RAG/embedding ACLs, and misinformation/overreliance UX are out of a client SDK's control. |

Bounded-parser contract: nav/action commands parsed out of avatar text must be bounded allow-lists, never `eval`/dispatch of arbitrary strings. `parseSlideNumber` (integer-bounded, range-checked) is the template.

### OWASP Agentic (Agentic Security Initiative / Top 10 for Agentic Apps)

The avatar is an agent (navigates, renders GenUI, captures leads, searches knowledge). The SDK is the client boundary where agent-initiated actions surface.

| Threat | Control |
|---|---|
| **ASI 01 Goal Hijack** / **ASI 02 Tool Misuse** | `onAgentAction(action)` chokepoint. Every agent-initiated action (`navigate`/`render-genui`/`structured-data-form`/…) passes through it before taking effect. Veto via false/throw. **Operator (server-side `api`/`code`/`csv` tools):** these fire server-to-server, outside the SDK's reach. Independently authorize each call against the caller's real session/permissions. Never treat model/system-prompt tool scoping, or a client-suppliable `request_vars` value, as an authorization claim. See [Tools and Secrets § Tools](https://kaltura.github.io/intelligent-agents-sdk/reference/api/build/tools-and-secrets/index.md#tools-api--csv--code--client--var). |
| **ASI 03 Identity & Privilege Abuse** | Scoped user-session token (per-user `userId`), entitlement ON, short TTL, revocable; least-privilege `restrictions`; `agentActions` policy (e.g. `navigate:'off'`). |
| **ASI 06 Memory & Context Poisoning** | `Presenter` session memory is bounded and operator-cleared via `clearMemory()`. Persisted memory is replayed context. The operator owns the storage choice. |
| **ASI 08 Cascading Failures** | Reconnect-window bound, media-recovery escalation, agent-stall watchdog, client rate valve. |
| **ASI 09 Human-Agent Trust** | Disclosure-before-speech + `getDisclosure()`; `requireDisclosureAck`. |
| **T8 Repudiation** | `agent.action.allow`/`agent.action.deny` audit events. |
| ASI 05 RCE | The SDK never `eval`s agent output; nav uses a bounded parser. |

### Avatar / digital-human / deepfake / voice-clone

| Obligation | Citation | Status |
|---|---|---|
| AI-interaction disclosure | EU AI Act Art. 50(1); CA SB 1001 (BOT Act); Utah AI Policy Act; Colorado AI Act | **SDK:** disclosure fires before first avatar speech + `getDisclosure()` queryable any time. **Operator:** render it accessibly (ARIA live region, not color-only; WCAG 2.1 SC 4.1.3). |
| Synthetic-media output marking | EU AI Act Art. 50(2); Recital 133; C2PA 2.x | **SDK:** `disclosure.synthetic:true` + `provenance{generatedBy,voice,sessionId}`. **Operator/Platform:** durable machine-readable marking (C2PA manifest) of the live media stream is server-side; stamp client-captured screenshots/recordings with an "AI-generated" assertion. |
| Deepfake disclosure (deployer) | EU AI Act Art. 50(4); Recital 134 | Operator (deployer) discloses manipulated content; SDK's disclosure supports it. |
| Bot identification | CA SB 1001 §17940 | SDK disclosure + persistent `getDisclosure()`. |
| Biometric notice + consent | BIPA 740 ILCS 14/15; TX CUBI §503.001; WA RCW 19.375 | The mic uplink may capture a "voiceprint." **SDK:** `requireDisclosureAck` can gate the mic until acknowledged. **Operator:** capture written-equivalent consent in IL/TX/WA before mic start. The avatar face/voice handling is operator/platform. |
| Voice/likeness clone consent | NO FAKES Act; TN ELVIS Act; CA AB 1836/2602; FTC impersonation rule | **SDK:** Management voice/visual provisioning accepts optional `consentRef` stored in `data._consent` on the returned catalog item. A `clone.consent` audit event is emitted. **Operator:** obtain and retain the source individual's consent. |
| Emotion / biometric categorisation notice | EU AI Act Art. 50(3) | Operator, if such features are enabled. |

### Secure production baseline (CM-6)

Copy-paste hardened configuration for a regulated deployment:

```js
// Server: Management
const mgmt = new Management({
  partnerId, getAdminSecret: () => vault.fetch('kaltura-admin'),  // or adminSecret
  onAuditEvent: (e) => siem.write(e),
});
const token = await mgmt.sessions.createConversationToken({
  configId, ttlSeconds: 1800,                 // short-lived (RFC 9700)
  userId: opaqueUserId,                       // per-user identity: reaches only this user's threads
  restrictions: { sessionGroupId: caseId },   // revocable family
});

// Browser: Experience
new KalturaAvatarSession({
  token, conversationManagerUrl, srsBaseUrl, turnServerUrl,      // all https/wss
  turnCredentials,                            // ephemeral (RFC 7635), not the SDK default
  allowInsecureTransport: false,              // never true in production
  requireDisclosureAck: true,                 // EU AI Act / biometric jurisdictions
  idleTimeoutMs: 900000,                      // HIPAA auto-logoff
  subjectId: opaqueUserId,                    // audit only: HIPAA unique-user-id (never PHI)
  maxTurnsPerMinute: 30,                      // LLM10 valve
  onBeforeSend: (t) => myGuardrail(t),        // LLM01 input filter
  onAgentAction: (a) => myPolicy(a),          // LLM06 / Agentic action gate
  onAuditEvent: (e) => siem.write(e),
});
```

Plus: pin the injected socket.io with SRI; set a strict CSP (`connect-src` your control-plane, WHEP and TURN hosts; `media-src blob:`; no inline script/`unsafe-eval`) and `frame-ancestors` on the embedding page; render the disclosure accessibly.

