# Architecture Source: https://docs.igrisecurity.com/architecture How Igris components fit together — API server, frontend, database, cache, auth, and proxy flow. # Architecture Igris is a monorepo with two primary services, a shared database, and a Redis cache layer. ## System Overview ```mermaid theme={null} flowchart TD MCP["MCP Clients\n(Claude Code, OpenCode, Cursor)"] -->|"JSON-RPC"| API["Igris API (Hono, port 3100)\n/v1/mcp/:slug\n/llm/:slug/*\n/api/v1/*\n/api/auth/*"] LLMClient["LLM Clients\n(OpenAI-compatible)"] -->|"chat/embeddings"| API API -->|"Forward tool calls"| Upstream["Upstream MCP Server"] API -->|"Forward LLM requests"| LLMProvider["Upstream LLM Provider\n(60 providers)"] API -->|"Read/Write"| DB["Neon PostgreSQL (Drizzle)\naudit_events, connections, policies,\nsessions, users"] API -->|"Archive expired events"| S3["S3 (Standard-IA)\nGzip NDJSON archives\nper org per day"] DB -->|"Rows deleted after archive"| S3 API -->|"Cache & Pub/Sub"| Redis["Upstash Redis\nPolicy cache, rate limits,\nsession state, SSE pubsub"] Web["Next.js Web (port 3200)\nDashboard UI"] -->|"REST API"| API ``` ## Components ### API Server (Hono) The backend runs on [Hono](https://hono.dev) and listens on port **3100**. It handles: * **MCP Gateway** (`/v1/mcp/:slug`) — intercept MCP tool calls, evaluate policies, forward to upstream * **LLM Gateway** (`/llm/:slug/*`) — OpenAI-compatible API proxy for 60 LLM providers (see [LLM Gateway](#llm-gateway) below) * **REST API** (`/api/v1/*`) — CRUD for policies, servers, sessions, audit events, billing * **Auth** (`/api/auth/*`) — Better Auth endpoints for session management * **SSE** (`/api/v1/events`) — real-time event stream for dashboard updates ### Web Frontend (Next.js) The dashboard runs on [Next.js](https://nextjs.org) on port **3200** and provides: * Governance management (servers, policies, sessions) * Real-time observe dashboard with risk heat maps * Organization settings, member management, billing ### Database (Neon PostgreSQL) All persistent state lives in [Neon](https://neon.tech) PostgreSQL. The schema is managed by [Drizzle ORM](https://orm.drizzle.team) with auto-migrations on startup. Key tables: | Table | Purpose | | ---------------------------- | ----------------------------------------------------------------------------- | | `user`, `account`, `session` | Better Auth identity | | `organization`, `member` | Multi-tenancy | | `connections` | Connection configs (HTTP MCP / LLM gateway, encrypted credentials) | | `policies` | Governance rules per connection | | `agent_sessions` | Active gateway sessions | | `audit_events` | Gateway audit trail (mutable — rows are archived to S3 and deleted on expiry) | ### Cache (Upstash Redis) [Upstash Redis](https://upstash.com) handles: * **Policy cache** — hot policies cached with TTL to avoid DB lookups on every tool call * **Rate limiting** — sliding window counters for rate-limit policy rules * **SSE pub/sub** — event fan-out to connected dashboard clients * **Session state** — fast lookup for kill-switch status ### Authentication (Better Auth) [Better Auth](https://better-auth.com) provides: * Email/password and OAuth (GitHub, Google) login * Organization-scoped sessions with RBAC (operational) * API key generation for programmatic access (proxy) * Session tokens stored as `igris.session_token` cookies (or `__Secure-igris.session_token` in production) ## LLM Gateway The LLM Gateway is an OpenAI-compatible API proxy mounted at `/llm/:slug/*`. It routes requests through Igris connections so every LLM call is governed, audited, and cost-tracked alongside your MCP traffic. ### Route shape ``` /llm/{connection-slug}/v1/chat/completions → chat.completions (transformed) /llm/{connection-slug}/v1/embeddings → embeddings (transformed) /llm/{connection-slug}/** → passthrough to upstream ``` The `{connection-slug}` identifies the Igris connection that holds the upstream provider credential. No model-prefix routing is used in the URL — the provider is determined by the `provider` field on the connection record. ### Authentication Bearer API key only (`Authorization: Bearer ig_...`). Wildcard CORS is applied (`*`) so browser-based clients can call the gateway directly. Session cookies are not accepted on gateway routes. ### What the gateway does on each request 1. Authenticates the API key and resolves the connection (slug → provider + encrypted credential) 2. Evaluates policies (allow / deny / alert / redact) against the model and endpoint 3. Checks pre-forward token guards and content guards 4. Checks rate limits (requests / tokens / dollars, Redis sliding window) 5. Forwards the request to the upstream provider with credential injection 6. Streams the response to the client via a single-pass tee (client stream + accumulator) 7. Writes an audit event with token counts, cost, and model after the response completes 8. Updates the LLM anomaly detector asynchronously (cost spike, token burn, error rate, model shift, response length) ### Provider support 60 providers are registered, including OpenAI, Anthropic, Google Gemini, Groq, Mistral, DeepSeek, Cohere, and self-hosted models via the `openai-compatible` provider (requires `customBaseUrl`). ### Enablement The LLM Gateway is feature-flagged. Set `LLM_GATEWAY_ENABLED=false` to disable it; requests to `/llm/*` will return `404`. ## S3 Audit Archive Audit events are **mutable** — they are retained in Postgres up to the plan's retention limit, then archived to S3 Standard-IA and deleted from Postgres. ### Archive job A daily cron runs at UTC `ARCHIVE_CRON_HOUR_UTC` (default 03:00). For each organization it: 1. Finds days in `audit_events` older than `retentionDays + 1` buffer days 2. Serializes each day's events as gzip NDJSON 3. Writes the file to S3 with a deterministic key (`{orgId}/{YYYY-MM}/{YYYY-MM-DD}.ndjson.gz`) — idempotent overwrite 4. In a single Postgres transaction: inserts a manifest row into `audit_archives`, then deletes the archived rows from `audit_events` Rows that were restored from an archive (`restored_from_archive_id IS NOT NULL`) are excluded from future archiving. ### Key implications * `audit_events` does not grow unboundedly in Postgres * Restored rows are marked with `restored_from_archive_id` so they are not re-archived * The audit trail is complete but not immutable — rows are deleted from Postgres after archiving ## Proxy Flow When an MCP client calls a tool through the Igris proxy: 1. **Request arrives** at `/v1/mcp/:slug` with the tool name and arguments 2. **Auth check** — validate API key or session cookie 3. **Org resolution** — determine which organization owns this connection 4. **Policy evaluation** — load rules from cache (or DB on cache miss), evaluate first-match against the tool name with conditions 5. **Action execution**: * **allow** → forward to upstream, log the event * **deny** → return error to client, log the denial * **alert** → forward to upstream, log + emit anomaly event via SSE 6. **Anomaly check** — evaluate rate spike and destructive pattern detectors 7. **Audit write** — persist the event to `audit_events` with timing, result, and metadata 8. **SSE broadcast** — push real-time event to connected dashboard clients ## Deployment Igris deploys to **EC2** via GitHub Actions with a systemd service: * API server runs as a systemd unit on EC2 (Ubuntu) * Next.js frontend deployed to Vercel (or a separate EC2 instance) * Neon for managed PostgreSQL (serverless, auto-scaling) * Upstash for managed Redis (serverless, per-request pricing) * Migrations applied automatically at API startup via `drizzle-orm/postgres-js/migrator` (programmatic — not `drizzle-kit`). The API refuses to start if any migration fails. Igris is a fully managed cloud service — no self-hosted deployment is required. # Authentication Source: https://docs.igrisecurity.com/authentication Three auth methods: session cookies for the dashboard, API keys for programmatic access, and OAuth via GitHub or Google. # Authentication Igris supports several authentication methods depending on the context. ## Session Cookies (Dashboard) When you log in through the dashboard, Better Auth sets a session cookie. The cookie name depends on the environment: * **Production:** `__Secure-igris.session_token` (Secure flag set, HTTPS only) * **Development:** `igris.session_token` This cookie is automatically sent with all dashboard requests. For API calls from a browser context, include credentials: ```javascript theme={null} fetch("https://api.igrisecurity.com/api/v1/policies", { credentials: "include" // sends session cookie }); ``` Or pass the cookie explicitly: ```bash theme={null} curl https://api.igrisecurity.com/api/v1/policies \ -H "Cookie: __Secure-igris.session_token=eyJhbGciOi..." ``` ## OAuth (GitHub / Google) GitHub and Google OAuth are conditionally enabled based on server configuration. When the relevant environment variables are set (`GITHUB_CLIENT_ID`/`GITHUB_CLIENT_SECRET` or `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET`), the corresponding social login buttons appear on the sign-in page. OAuth sessions are issued as session cookies identical to email/password login — no additional API changes are needed. ## API Keys (Programmatic Access) For MCP proxy connections and CI/CD integrations, use API keys. ### Creating an API Key 1. Go to **Settings → API Keys** in the dashboard 2. Click **Create Key** 3. Give it a label (e.g., "Proxy - Production", "CI Pipeline") 4. Copy the key — it's only shown once API keys follow the format `ig_` followed by 48 lowercase hex characters (24 random bytes), for a total of 51 characters: ``` ig_a3f82c1d9e4b7f0612a5c8e3d1f29b047e6a8c2d5f1b4e7a ``` ### Using an API Key Pass the key in the `Authorization` header: ```bash theme={null} curl https://api.igrisecurity.com/api/v1/policies \ -H "Authorization: Bearer ig_a3f82c1d9e4b7f06..." ``` ### Key Scoping and Permissions API keys are **read-only by default**. `GET` and `HEAD` requests succeed with a valid key. Write operations (`POST`, `PUT`, `PATCH`, `DELETE`) on management API routes require a session cookie — requests made with an API key alone will receive `403 Forbidden` on those routes. Keys are scoped to the organization they were created in. ### Revoking Keys Revoke a key in **Settings → API Keys** by clicking **Revoke**. Revoked keys immediately stop working — any proxy connections using that key will receive `401 Unauthorized`. ## Ingest API Authentication The Ingest API (`/api/v1/ingest/*`) uses a **separate** BetterAuth API-key system. These keys are issued for log ingestion from external proxies (Portkey, Helicone, Cloudflare integrations) and are distinct from the management API keys described above. Pass them the same way — `Authorization: Bearer ` — but generate them from the Ingest settings, not the main API Keys page. ## MCP Gateway and LLM Gateway Authentication The MCP gateway (`/v1/mcp/:slug`) and LLM gateway (`/llm/:slug/*`) use **Bearer API key only**. Session cookies are not accepted on these routes. * **CORS:** Both routes allow wildcard origins (`*`) — designed for programmatic clients and browser-based SDKs calling from any origin. * **No session cookie fallback:** There is no session-cookie auth path on gateway routes; the Bearer key is the only accepted credential. ```bash theme={null} # MCP gateway curl https://api.igrisecurity.com/v1/mcp/my-server \ -H "Authorization: Bearer ig_a3f82c1d9e4b7f06..." # LLM gateway curl https://api.igrisecurity.com/llm/openai/v1/chat/completions \ -H "Authorization: Bearer ig_a3f82c1d9e4b7f06..." ``` ## Organization Context All API requests are scoped to an organization. The organization is determined by: 1. **Session cookies** — the user's active organization in their session 2. **API keys** — the organization the key was created in ## Error Responses | Status | Meaning | | ---------------------- | --------------------------------------------------- | | `401 Unauthorized` | Missing, invalid, or revoked credentials | | `403 Forbidden` | Valid credentials but insufficient role permissions | | `403 Forbidden` (plan) | Feature requires a higher plan tier | A `401` response body contains only an `error` field: ```json theme={null} { "error": "Invalid or expired API key" } ``` A plan-gated `403` response includes additional context: ```json theme={null} { "error": "Feature not available on your plan", "feature": "hipaa", "currentPlan": "starter", "requiredPlan": "scale" } ``` ## RBAC Roles Permissions are enforced based on the user's role in the organization: | Role | Policies | Sessions | Servers | API Keys | Compliance | Billing | Members | | ------------- | ---------- | ---------- | ---------- | ---------- | ---------- | ------- | ------- | | **Owner** | Full | Full | Full | Full | Full | Full | Full | | **Admin** | Full | Full | Full | Full | Full | — | Manage | | **Developer** | Read/Write | Read/Write | Read/Write | Read/Write | — | — | — | | **CISO** | Read/Write | Read/Write | Read | — | Read/Write | — | — | | **Auditor** | Read | Read | Read | — | Read | — | — | # Connections Source: https://docs.igrisecurity.com/connect/connections Store upstream MCP server credentials securely and route requests through Igris. # Connections Connections are encrypted credential vaults for upstream MCP servers. Instead of putting real API keys in your code, you create a connection in Igris that stores the credential securely and gives you a clean gateway URL. ## How Connections Work ``` Without connections: Your app → real upstream credential in code → MCP server With connections: Your app → Igris API key only → Igris Gateway → injects real credential → MCP server ``` **Benefits:** * Real credentials never leave Igris — encrypted at rest with AES-256-GCM * Rotate credentials instantly without changing any code * Revoke access by disabling the connection * Full audit trail of every request per connection ## Create a Connection In the dashboard: **Governance → Connections → New Connection** Or via API: ```bash theme={null} curl -X POST https://api.igrisecurity.com/api/v1/connections \ -H "Authorization: Bearer ig_..." \ -H "Content-Type: application/json" \ -d '{ "slug": "github-prod", "name": "GitHub Production", "upstreamUrl": "https://mcp.github.com", "authType": "bearer", "credential": "ghp_your_github_token" }' ``` The credential is encrypted and stored. It will never be returned in any API response. ### Auth Types Connections support eight authentication types for upstream credential injection: | Auth Type | Behavior | | --------- | -------------------------------------------------------------------------------------------------------- | | `bearer` | Injects credential as `Authorization: Bearer ` (default) | | `header` | Injects credential as a raw header value with a custom header name (requires `authHeaderName`) | | `none` | No credential injection — upstream doesn't require auth | | `oauth` | OAuth 2.0 client credentials flow — credential is the client secret, fetches access tokens automatically | | `mtls` | Mutual TLS — credential is the client private key (PEM); requires a client certificate | | `azure` | Azure Managed Identity / Entra workload identity token injection | | `aws` | AWS SigV4 signing — credential is `ACCESS_KEY_ID:SECRET_ACCESS_KEY` | | `gcp` | GCP service account token injection — credential is a service account JSON key | ### Custom Auth Headers Some MCP servers expect credentials in a non-standard header. Use `authType: "header"` with `authHeaderName`: ```bash theme={null} curl -X POST https://api.igrisecurity.com/api/v1/connections \ -H "Authorization: Bearer ig_..." \ -H "Content-Type: application/json" \ -d '{ "slug": "custom-api", "name": "Custom API", "upstreamUrl": "https://my-api.example.com/mcp", "authType": "header", "authHeaderName": "X-API-Key", "credential": "sk_my_secret_key" }' ``` This injects the credential as `X-API-Key: sk_my_secret_key` to the upstream server. ## Use a Connection ### With the SDK ```typescript theme={null} const config = igris.connectHttp("github-prod", { user: "alice@company.com", metadata: { role: "developer" }, }); // config.baseUrl = "https://api.igrisecurity.com/v1/mcp/github-prod" // config.apiKey = "ig_..." (set as Authorization: Bearer when calling the gateway) // config.headers = { "X-Igris-Trace-Id": "...", "X-Igris-User": "...", "X-Igris-Metadata": "..." } ``` ### Direct MCP Client Config ```json theme={null} { "mcpServers": { "github": { "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key" } } } } ``` ## Credential Injection When the gateway receives a request, it: 1. Strips your Igris API key (never forwarded upstream) 2. Decrypts the connection's credential 3. Injects it using the configured auth type: * **bearer**: `Authorization: Bearer ` * **header**: `: ` * **none**: no credential injected The upstream server only sees its own credential. Your users only see the Igris API key. ## Rotate Credentials Update a credential without changing any SDK configs: ```bash theme={null} curl -X POST https://api.igrisecurity.com/api/v1/connections/github-prod/rotate \ -H "Authorization: Bearer ig_..." \ -H "Content-Type: application/json" \ -d '{ "credential": "ghp_new_token_here" }' ``` All requests immediately use the new credential. Zero downtime. ## Slug Format Connection slugs must be: * Lowercase letters, numbers, underscores, hyphens * 3-64 characters * Start with a letter or number Examples: `github-prod`, `slack-mcp`, `analytics01` # LLM Gateway Source: https://docs.igrisecurity.com/connect/llm-gateway Route any LLM provider through Igris for audit, policies, and cost tracking in 5 minutes. # LLM Gateway Turn on governance for your LLM calls in 5 minutes. Works with OpenAI, Anthropic, Google Gemini, Groq, Mistral, Cohere, and 54 more providers out of the box. ## Quickstart ### 1. Get an Igris API key Sign up at [app.igrisecurity.com/signup](https://app.igrisecurity.com/signup), then create a new API key under **Settings → API Keys**. Your key starts with `ig_`. ### 2. Create a connection for your LLM provider Connections are encrypted credential vaults. They bind your upstream provider API key to an Igris slug you can reference from code without ever exposing the real key. 1. Go to **Connections** in the dashboard 2. Click **New Connection** 3. Select type **LLM** 4. Pick your provider — e.g. **OpenAI** 5. Paste your provider API key 6. Optionally restrict to specific models (e.g. `gpt-4o`, `gpt-4o-mini`) 7. Save and note the slug (e.g. `openai-prod`) ### 3. Make your first call The model field uses `@slug/model` syntax. Igris resolves the slug, injects credentials, enforces policies, and logs everything — before forwarding to the upstream provider. ```ts theme={null} import { Igris } from "@igris-security/sdk"; const igris = new Igris({ apiKey: "ig_..." }); const response = await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [{ role: "user", content: "Hello!" }], user: "alice@corp.com", }); console.log(response.choices[0].message.content); ``` ### 4. See it in the audit trail Back in the dashboard, open **LLM → Audit Trail**. You'll see your call with: * Provider + model used * Input and output token counts * Cost in USD (calculated server-side) * End-to-end latency * User identity passed via `user` field * Policy action (allowed / denied / alerted) Head to **LLM → Cost** for spend breakdowns over time. ## Streaming Pass `stream: true` to get an `AsyncIterable` of SSE chunks. The gateway uses minimal SSE-parse buffering to extract usage data — no extra round-trips are added. ```ts theme={null} const stream = await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [{ role: "user", content: "Write a haiku about governance" }], stream: true, }); for await (const chunk of stream) { process.stdout.write(chunk.choices[0]?.delta?.content ?? ""); } ``` Token usage is extracted from the final SSE chunk (or the terminal usage field for providers that support it) and recorded in the audit trail even for streaming responses. ## Passing metadata for policy conditions The `user` field and arbitrary metadata can be passed via extra request headers. The SDK's `connectLlm()` method is the cleanest way to attach per-request context: ```ts theme={null} const conn = igris.connectLlm("openai-prod", { user: "alice@corp.com", traceId: "trace-abc123", metadata: { role: "developer", team: "platform" }, }); // Use conn.baseUrl + conn.apiKey + conn.headers with any OpenAI-compatible client ``` ## Endpoint and protocol The gateway is OpenAI-compatible. All requests target: ``` https://api.igrisecurity.com/llm/:slug/* ``` For example, a chat completions call via raw HTTP: ```bash theme={null} curl https://api.igrisecurity.com/llm/openai-prod/v1/chat/completions \ -H "Authorization: Bearer ig_..." \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}' ``` ## Concepts ### Connections A **connection** is an encrypted credential vault that maps an Igris slug to an upstream provider API key. Your application code never holds the real provider key — it only knows the slug (e.g. `openai-prod`). The gateway resolves the slug at request time, injects the real credential, and forwards to the upstream provider. Connections can be scoped to a specific organization, restricted to a subset of models, enabled or disabled without a code change, and rotated by updating the vault entry in the dashboard. A single slug can be reused across as many callers as you like — policy rules target the slug. See [Connections](/connect/connections) for CRUD operations via the API. ### Providers A **provider** is a registered upstream LLM service — OpenAI, Anthropic, Groq, Mistral, and [56 more](/connect/providers) — 60 in total. Each provider registration captures: * **Slug** — the identifier used in connection creation and model routing (e.g. `openai`, `anthropic`) * **Base URL** — where the gateway forwards requests * **Auth style** — `bearer`, `x-api-key`, or `query-param` * **Supported endpoints** — which of `chat.completions`, `embeddings`, `images.generate`, `audio.transcriptions`, `audio.speech`, or `passthrough` this provider supports Providers with `baseUrl: null` (Ollama, HuggingFace, Triton, Modal, `openai-compatible`) require a `customBaseUrl` to be set on the connection for self-hosted deployments. See the full [Provider Catalog](/connect/providers). ### Policies A **policy** is an ordered list of `PolicyRule` objects attached to an organization or connection. Rules are evaluated in order — first match wins. Each rule has four main parts: #### Target What the rule matches. Three discriminated union variants: ```ts theme={null} // Match a specific LLM model or a glob { kind: "llm_model"; model: "gpt-4o" } { kind: "llm_model"; model: "gpt-4*" } // Match a specific endpoint { kind: "llm_endpoint"; endpoint: "chat.completions" } // Match an MCP tool (for MCP governance rules in the same policy) { kind: "mcp_tool"; tool: "delete_*" } ``` #### Action What happens when the rule matches: `"allow"`, `"deny"`, or `"alert"`. Deny returns HTTP 403. Alert records an alert event without blocking the request. #### Conditions Optional metadata conditions that must also match for the rule to fire. Keys are dotted paths into the request context (e.g. `metadata.role`, `user`). Operators: `eq`, `neq`, `in`, `nin`. ```json theme={null} { "conditions": { "metadata.role": { "in": ["intern", "contractor"] } } } ``` #### Limits, Guards, and Content Controls Rules can also carry: * **`limit`** — rate limit on requests, tokens, or dollars per minute/hour/day * **`tokenGuard`** — cap `max_tokens` or reject requests exceeding an input token estimate * **`contentGuard`** — runs detectors (PII, secrets, prompt injection, custom regex/keywords) over the prompt and optionally the response, with `action: "deny" | "redact" | "alert"` * **`logContent`** — whether to persist the full prompt and completion to `llm_call_bodies` ### Audit Trail Every request through the gateway produces an **audit event** with: * `type: "llm_call"` * Provider + model (resolved after gateway routing) * `inputTokens`, `outputTokens`, `cachedTokens` * `costCents` (integer, USD cents × 100 — sub-cent precision) * Latency in milliseconds * `userId`, `traceId`, `connectionSlug` * Policy action taken * `requestId` for correlation Query audit events from the SDK with `igris.auditEvents.list()` or browse them in the dashboard under **LLM → Audit Trail**. If `logContent: true` is set on a matching rule, the full prompt and completion are stored in `llm_call_bodies` and linked via the audit event ID. ### Cost Tracking Cost is computed server-side using a live pricing snapshot. The `costCents` column stores the value as an integer representing USD cents × 100 — so `150` means \$0.0150. The **LLM → Cost** dashboard aggregates spend by day, provider, model, connection, and user. Anomaly detection also monitors cost-per-minute as one of its five signal dimensions. ### Anomaly Detection The `LlmAnomalyDetector` runs five parallel signal trackers on every request: | Signal | Trigger | | --------------- | -------------------------------------------------------- | | Cost spike | Cost/minute exceeds baseline EWMA by configurable factor | | Token burn | Tokens/minute exceeds threshold | | Response length | Output tokens for a single response exceeds threshold | | Model shift | Observed model diverges from expected model baseline | | Error rate | HTTP 4xx/5xx rate exceeds threshold | When a signal fires, an alert event is written and optionally delivered via webhook. Alerts do not block requests by default — configure a `deny` action on the policy rule to make them blocking. ## Three Usage Styles ### 1. SDK native (recommended) Use `igris.chat.completions.create()` directly. The `@slug/model` model prefix tells the SDK which connection to route through. Zero external dependencies beyond the Igris SDK. ```ts theme={null} const igris = new Igris({ apiKey: "ig_..." }); await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [...] }); ``` ### 2. connectLlm escape hatch Use `igris.connectLlm(slug, options)` to get a `{ baseUrl, apiKey, headers }` object and wire it into any OpenAI-compatible SDK client. This is the zero-migration path when your app already uses the OpenAI Node SDK. ```ts theme={null} import OpenAI from "openai"; import { withIgris } from "@igris-security/sdk/adapters/openai"; const igris = new Igris({ apiKey: "ig_..." }); const openai = withIgris(new OpenAI({ apiKey: "placeholder" }), igris, "openai-prod"); // Now openai.chat.completions.create() routes through Igris ``` ### 3. Raw HTTP Any HTTP client that can set `Authorization: Bearer ` and target `https://api.igrisecurity.com/llm//v1/chat/completions` works directly. This is useful for language runtimes without an Igris SDK (Python, Go, Rust, etc.). ```bash theme={null} curl https://api.igrisecurity.com/llm/openai-prod/v1/chat/completions \ -H "Authorization: Bearer ig_..." \ -H "Content-Type: application/json" \ -d '{"model":"gpt-4o","messages":[{"role":"user","content":"Hello"}]}' ``` ## Request Modes: Passthrough vs Transformed Most providers use **transformed mode** — the gateway rewrites the request body and response to match the OpenAI chat completions schema, regardless of what the upstream expects. This means you can switch providers without changing your application code. **Passthrough mode** (`endpoint: "passthrough"`) forwards the raw request body directly to the provider without transformation. Use this for providers with non-standard APIs (image generation, 3D model generation, audio) where the OpenAI schema doesn't apply. ## Metadata Channels Request context (user identity, trace IDs, metadata for policy conditions) can be provided through three channels, resolved in priority order: 1. **Individual headers** — `X-Igris-Metadata-: ` (highest priority). One header per metadata field. Example: `X-Igris-Metadata-role: developer`. 2. **JSON blob header** — `x-igris-metadata: {"user":"alice","role":"developer"}` plus special sentinels `_user` and `_trace_id` for the core identity fields. 3. **Request body** — `body.user` (OpenAI convention). Lowest priority, for compatibility with clients that already set the OpenAI `user` field. The `connectLlm()` method handles encoding all three channels automatically when you pass `user`, `traceId`, and `metadata` options. ## What's next? All 60 supported providers with slugs, base URLs, and auth styles. Create and manage encrypted credential vaults for upstream providers. # MCP Client Config Source: https://docs.igrisecurity.com/connect/mcp-config Generate configs for Claude Desktop, Cursor, and any MCP client. # MCP Client Config The SDK's `connectHttp()` generates a URL + headers that work with any MCP client supporting HTTP transport. ## SDK Usage (Programmatic) For apps that create MCP connections dynamically: ```typescript theme={null} import { Igris } from "@igris-security/sdk"; const igris = new Igris({ apiKey: process.env.IGRIS_API_KEY! }); const config = igris.connectHttp("github-prod", { user: currentUser.email, metadata: { role: currentUser.role }, }); // config = { baseUrl, apiKey, headers } // Pass baseUrl + headers to your MCP client, and add the Authorization header // from config.apiKey: const client = new McpClient({ transport: new StreamableHttpTransport(config.baseUrl, { headers: { Authorization: `Bearer ${config.apiKey}`, ...config.headers, }, }), }); const tools = await client.listTools(); const result = await client.callTool("create_branch", { name: "feat/new" }); ``` For direct, programmatic tool calls without wiring up an external MCP client, use the [`igris.mcp` resource](/govern/tool-calls) — `initialize`, `listTools`, and `callTool`. ## Claude Desktop Add to your Claude Desktop config (`~/.claude/claude_desktop_config.json`): ```json theme={null} { "mcpServers": { "github": { "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key", "X-Igris-User": "alice@company.com", "X-Igris-Metadata": "{\"role\":\"developer\"}" } } } } ``` ## Cursor Add to `.cursor/mcp.json` in your project: ```json theme={null} { "mcpServers": { "github": { "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key", "X-Igris-User": "alice@company.com", "X-Igris-Metadata": "{\"role\":\"developer\"}" } } } } ``` ## Claude Code Add to `~/.claude.json` or `.mcp.json`: ```json theme={null} { "mcpServers": { "github": { "type": "url", "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key", "X-Igris-User": "alice@company.com", "X-Igris-Metadata": "{\"role\":\"developer\"}" } } } } ``` ## VS Code Add to `.vscode/mcp.json`: ```json theme={null} { "servers": { "github": { "type": "http", "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key", "X-Igris-User": "alice@company.com", "X-Igris-Metadata": "{\"role\":\"developer\"}" } } } } ``` `X-Igris-User` and `X-Igris-Metadata` are optional. They're useful when you want per-user policy decisions and per-user audit attribution. The `Authorization` header is required. ## Multiple Connections You can use multiple connections — each points to a different upstream MCP server: ```typescript theme={null} const github = igris.connectHttp("github-prod", { user: "alice" }); const slack = igris.connectHttp("slack-prod", { user: "alice" }); const notion = igris.connectHttp("notion-prod", { user: "alice" }); ``` Each gets its own gateway URL, policies, and audit trail. # Supported Providers Source: https://docs.igrisecurity.com/connect/providers The full list of LLM providers supported by the Igris gateway — 60 providers out of the box. # Supported Providers The Igris LLM Gateway supports **60 providers** out of the box. Each is accessible through the same unified API — no code changes needed to switch between them. Add a provider by creating a connection in the dashboard and selecting from the list below. The `slug` column is what you use in the connection type selector and in model strings (`@/`). ## OpenAI-compatible providers These providers expose an OpenAI-compatible `/v1/chat/completions` endpoint. The gateway passes requests through with minimal transformation — just credential injection and streaming normalization. | Slug | Name | Base URL | Auth | Endpoints | | ---------------- | --------------------- | -------------------------------------------------------- | ------ | --------------------------------------------------------------------------------- | | `openai` | OpenAI | `https://api.openai.com/v1` | bearer | chat.completions, embeddings, images.generate, audio.transcriptions, audio.speech | | `groq` | Groq | `https://api.groq.com/openai/v1` | bearer | chat.completions | | `mistral-ai` | Mistral AI | `https://api.mistral.ai/v1` | bearer | chat.completions, embeddings | | `deepseek` | DeepSeek | `https://api.deepseek.com/v1` | bearer | chat.completions | | `cerebras` | Cerebras | `https://api.cerebras.ai/v1` | bearer | chat.completions | | `sambanova` | SambaNova | `https://api.sambanova.ai/v1` | bearer | chat.completions | | `together-ai` | Together AI | `https://api.together.xyz/v1` | bearer | chat.completions, embeddings | | `fireworks-ai` | Fireworks AI | `https://api.fireworks.ai/inference/v1` | bearer | chat.completions, embeddings | | `perplexity-ai` | Perplexity | `https://api.perplexity.ai` | bearer | chat.completions | | `x-ai` | xAI | `https://api.x.ai/v1` | bearer | chat.completions | | `anyscale` | Anyscale | `https://api.endpoints.anyscale.com/v1` | bearer | chat.completions, embeddings | | `deepinfra` | DeepInfra | `https://api.deepinfra.com/v1/openai` | bearer | chat.completions, embeddings | | `openrouter` | OpenRouter | `https://openrouter.ai/api/v1` | bearer | chat.completions, embeddings | | `nebius` | Nebius AI Studio | `https://api.studio.nebius.ai/v1` | bearer | chat.completions, embeddings | | `lambda` | Lambda | `https://api.lambdalabs.com/v1` | bearer | chat.completions | | `dashscope` | Alibaba DashScope | `https://dashscope-intl.aliyuncs.com/compatible-mode/v1` | bearer | chat.completions, embeddings | | `hyperbolic` | Hyperbolic | `https://api.hyperbolic.xyz/v1` | bearer | chat.completions | | `moonshot` | Moonshot AI | `https://api.moonshot.cn/v1` | bearer | chat.completions | | `novita-ai` | Novita AI | `https://api.novita.ai/v3/openai` | bearer | chat.completions | | `upstage` | Upstage | `https://api.upstage.ai/v1/solar` | bearer | chat.completions, embeddings | | `siliconflow` | SiliconFlow | `https://api.siliconflow.cn/v1` | bearer | chat.completions, embeddings | | `lingyi` | 01.AI (Lingyi) | `https://api.lingyiwanwu.com/v1` | bearer | chat.completions | | `inference-net` | Inference.net | `https://api.inference.net/v1` | bearer | chat.completions | | `lepton` | Lepton AI | `https://api.lepton.run/api/v1` | bearer | chat.completions | | `kluster-ai` | Kluster AI | `https://api.kluster.ai/v1` | bearer | chat.completions | | `nscale` | Nscale | `https://inference.api.nscale.com/v1` | bearer | chat.completions | | `featherless-ai` | Featherless AI | `https://api.featherless.ai/v1` | bearer | chat.completions | | `bytez` | Bytez | `https://api.bytez.com/v1` | bearer | chat.completions | | `krutrim` | Krutrim | `https://cloud.olakrutrim.com/v1` | bearer | chat.completions | | `302ai` | 302.AI | `https://api.302.ai/v1` | bearer | chat.completions | | `cometapi` | CometAPI | `https://api.cometapi.com/v1` | bearer | chat.completions | | `matterai` | MatterAI | `https://api.matterai.so/v1` | bearer | chat.completions | | `nextbit` | Nextbit | `https://api.nextbit.io/v1` | bearer | chat.completions | | `z-ai` | Z.AI | `https://api.z.ai/api/paas/v4` | bearer | chat.completions | | `iointelligence` | IO Intelligence | `https://api.intelligence.io.solutions/api/v1` | bearer | chat.completions | | `aibadgr` | AIBadgr | `https://api.aibadgr.com/v1` | bearer | chat.completions | | `ovhcloud` | OVHcloud AI Endpoints | `https://oai.endpoints.kepler.ai.cloud.ovh.net/v1` | bearer | chat.completions | | `predibase` | Predibase | `https://serving.app.predibase.com/v1` | bearer | chat.completions | | `monsterapi` | MonsterAPI | `https://llm.monsterapi.ai/v1` | bearer | chat.completions | | `deepbricks` | Deepbricks | `https://api.deepbricks.ai/v1` | bearer | chat.completions | | `lemonfox-ai` | Lemonfox AI | `https://api.lemonfox.ai/v1` | bearer | chat.completions | ## Providers with divergent APIs These providers have non-OpenAI APIs. The gateway handles request/response transformation internally. | Slug | Name | Base URL | Auth | Endpoints | | ----------- | ------------- | ------------------------------------------- | ----------- | ---------------------------- | | `anthropic` | Anthropic | `https://api.anthropic.com/v1` | x-api-key | chat.completions | | `google` | Google Gemini | `https://generativelanguage.googleapis.com` | query-param | chat.completions, embeddings | | `cohere` | Cohere | `https://api.cohere.com/v1` | bearer | chat.completions, embeddings | | `ai21` | AI21 Labs | `https://api.ai21.com/studio/v1` | bearer | chat.completions | | `reka-ai` | Reka AI | `https://api.reka.ai/v1` | bearer | chat.completions | | `zhipu` | Zhipu AI | `https://open.bigmodel.cn/api/paas/v4` | bearer | chat.completions, embeddings | **Notes:** * **Anthropic** uses `x-api-key` header (not Bearer). The gateway injects this automatically. * **Google** authenticates via `?key=` query param, appended by the gateway. Use `igris.chat.completions.create({ model: "@google-prod/gemini-2.0-flash", ... })` or raw HTTP. The Google subpath adapter (`@igris-security/sdk/adapters/google`) returns connection config for manual wiring since the official SDK doesn't expose a unified `baseURL` override. * **Cohere** and **AI21** speak their own chat API formats. The gateway translates OpenAI chat completions requests to their native format transparently. ## Embeddings-only providers | Slug | Name | Base URL | Auth | Endpoints | | -------- | --------- | ------------------------------- | ------ | ---------- | | `voyage` | Voyage AI | `https://api.voyageai.com/v1` | bearer | embeddings | | `jina` | Jina AI | `https://api.jina.ai/v1` | bearer | embeddings | | `nomic` | Nomic | `https://api-atlas.nomic.ai/v1` | bearer | embeddings | ## Passthrough / image / 3D providers These providers use the `passthrough` endpoint — the gateway forwards the raw request body without transformation. Use them for image generation, 3D model generation, or any non-chat workload. | Slug | Name | Base URL | Auth | Endpoints | | -------------- | ------------ | ------------------------------------ | ------ | ---------------------------- | | `stability-ai` | Stability AI | `https://api.stability.ai` | bearer | images.generate, passthrough | | `segmind` | Segmind | `https://api.segmind.com/v1` | bearer | images.generate, passthrough | | `recraft-ai` | Recraft AI | `https://external.api.recraft.ai/v1` | bearer | images.generate, passthrough | | `tripo3d` | Tripo3D | `https://api.tripo3d.ai/v2` | bearer | passthrough | | `meshy` | Meshy | `https://api.meshy.ai/v2` | bearer | passthrough | ## Self-hosted providers These providers require you to supply a `customBaseUrl` on the connection. The gateway will reject requests if `customBaseUrl` is not set. | Slug | Name | Auth | Endpoints | | ------------------- | -------------------------- | ------ | ----------------------------------------- | | `ollama` | Ollama | bearer | chat.completions, embeddings | | `huggingface` | HuggingFace Inference | bearer | chat.completions, embeddings, passthrough | | `triton` | NVIDIA Triton | bearer | chat.completions, passthrough | | `modal` | Modal | bearer | chat.completions, passthrough | | `openai-compatible` | OpenAI-compatible (custom) | bearer | chat.completions, embeddings | For Ollama running locally: ```ts theme={null} // Connection dashboard: set customBaseUrl to http://localhost:11434 await igris.chat.completions.create({ model: "@ollama-local/llama3.2", messages: [{ role: "user", content: "Hello" }], }); ``` ## Adding a new provider Providers are code-only — adding one requires a PR to `packages/proxy/src/llm-providers/` with the provider implementation following the existing template. ## Requesting a provider Open a support request from the [Igris dashboard](https://app.igrisecurity.com) with the provider name, API docs URL, and auth style. # Anomaly Detection Source: https://docs.igrisecurity.com/govern/anomaly Automatic detection of rate spikes and destructive tool call patterns, with per-type cooldowns and SSE broadcast. # Anomaly Detection Igris includes built-in anomaly detection that monitors tool call patterns and alerts when suspicious activity is detected. Anomaly events are written to the audit trail and broadcast via SSE for real-time dashboard updates. Source: `packages/proxy/src/anomaly.ts`, `apps/api/src/index.ts`. ## Detection Methods ### Rate Spike Detection Monitors the rate of tool calls per session and fires when the count exceeds a flat threshold within a sliding time window. **How it works:** 1. Igris maintains a sliding window of tool call timestamps per session 2. On each tool call, expired timestamps are evicted from the window 3. If the number of calls still in the window exceeds the threshold, a rate spike anomaly is triggered **Default configuration** (verified at `apps/api/src/index.ts` line 279): * Threshold: **more than 50 calls within any 60-second window** ### Destructive Pattern Detection Watches for tool calls that match known destructive name patterns, regardless of the policy action applied to that call. Monitored patterns (verified at `apps/api/src/index.ts` line 282): * `delete_*` — deletion operations * `drop_*` — schema/table drops * `rm_*` — remove-style operations * `remove_*` — removal operations * `destroy_*` — resource destruction A destructive pattern anomaly fires after **5 matching calls** accumulate within a session. Even if a policy allows these calls, the anomaly detector flags them for visibility. Anomaly detection and policies are independent systems. A tool call can be allowed by policy but still trigger an anomaly alert. This gives you defense in depth. ## Cooldowns After an anomaly alert fires, a per-type cooldown prevents alert fatigue (verified at `packages/proxy/src/anomaly.ts` lines 24–25): | Anomaly type | Cooldown | | ------------------- | ----------------------------------- | | Rate spike | **5 minutes** after an alert fires | | Destructive pattern | **10 minutes** after an alert fires | During cooldown, the same anomaly type for the same session will not fire again. This prevents a burst of destructive calls from generating dozens of separate alerts. ## Anomaly Events When an anomaly is detected, Igris: 1. **Writes an audit event** with type `anomaly` to the `audit_events` table, including: * `alertType` in the `details` field: `"rate_spike"` or `"destructive_pattern"` * Connection slug * Tool name that triggered it * Call count and human-readable message 2. **Broadcasts via SSE** to all connected dashboard clients for the organization ## Viewing Anomalies * **Observe dashboard** — risk heat map highlights connections with recent anomalies * **Audit Events** — filter by `type: anomaly` to see all anomaly events ## Relationship to Policies | System | Purpose | | ----------------- | ------------------------------------------------------------------------ | | Policies | Decide whether a tool call is allowed, denied, alerted, or redacted | | Anomaly detection | Monitors patterns across **all** tool calls regardless of policy outcome | A tool call allowed by policy can still trigger an anomaly alert. Policies enforce rules; anomaly detection catches unexpected patterns. ## Related * [Policies](/govern/policies) — rule-based allow/deny/alert/redact governance * [Sessions](/govern/sessions) — session lifecycle and kill switch * [Tool Calls](/govern/tool-calls) — how tool calls are logged and audited # Guards Source: https://docs.igrisecurity.com/govern/guards Token guards cap LLM request size before forwarding; content guards scan request and response content with Igris detectors. # Guards Guards are optional fields on a policy rule that add an extra enforcement layer on top of the rule's primary action. They run at request time — before the request is forwarded to the upstream server. Two kinds of guards are available: | Guard | Field | Applies to | | ------------- | -------------- | ---------------------------------------------------- | | Token guard | `tokenGuard` | LLM rules (`llm_model`, `llm_endpoint` targets) | | Content guard | `contentGuard` | LLM rules only (`llm_model`, `llm_endpoint` targets) | Guards are evaluated after target matching and condition checking. If a token guard fires, the request is denied regardless of the rule's `action`. Content guards are evaluated for LLM requests only; see the interaction table below for how they interact with `redact` rule actions. ## Token Guards Token guards let you cap the size of LLM requests before they reach the upstream provider. They apply to rules whose `target.kind` is `llm_model` or `llm_endpoint`. Source: `packages/proxy/src/policy-engine.ts` — `checkTokenGuard`, `apps/api/src/lib/schemas/policies.ts`. ### Fields ```ts theme={null} tokenGuard?: { maxInputTokens?: number; // deny if estimated input tokens exceed this value maxRequestMaxTokens?: number; // deny if the request's max_tokens field exceeds this value } ``` | Field | Description | | --------------------- | --------------------------------------------------------------------------------- | | `maxInputTokens` | Deny if the estimated prompt token count (chars ÷ 4 heuristic) exceeds this value | | `maxRequestMaxTokens` | Deny if the request's `max_tokens` parameter exceeds this value | When a token guard fires, the request is denied before it reaches the upstream provider with a structured error explaining which limit was exceeded. ### Example ```json theme={null} { "target": { "kind": "llm_model", "model": "gpt-4o" }, "action": "allow", "tokenGuard": { "maxInputTokens": 8000, "maxRequestMaxTokens": 2048 } } ``` This rule allows GPT-4o calls but blocks any request whose estimated input exceeds 8 000 tokens, or whose `max_tokens` exceeds 2 048. `maxInputTokens` uses a character-count heuristic (`chars / 4`) at request time. It is not an exact token count. Use it to catch runaway prompts, not for precise billing. ## Content Guards Content guards run Igris detectors on request content (and optionally on the upstream response) before forwarding. They can block or redact matched content. Source: `packages/proxy/src/policy-engine.ts` — `checkContentGuard` / `redactRequestBody` / `inspectResponseContent`, `apps/api/src/lib/schemas/policies.ts`. ### Fields ```ts theme={null} contentGuard?: { detectors: string[]; // detector IDs, pack names, or "custom:" action: "deny" | "redact" | "alert"; inspectResponse?: boolean; // also scan the upstream response content } ``` | Field | Description | | ----------------- | --------------------------------------------------------------------------------------------------------------------------------------------- | | `detectors` | List of detector IDs (e.g. `"us-ssn"`), pack names (e.g. `"pack:pii-default"`), or custom detector slugs (`"custom:internal-id"`) | | `action` | `"deny"` blocks the request when a detector matches; `"redact"` masks matched content and forwards; `"alert"` logs a match but does not block | | `inspectResponse` | When `true`, also run detectors on the upstream response content. Non-streaming responses only. | ### Example: Redact PII before forwarding ```json theme={null} { "target": { "kind": "llm_endpoint", "endpoint": "chat.completions" }, "action": "redact", "contentGuard": { "detectors": ["pack:pii-default", "pack:secrets-default"], "action": "redact", "inspectResponse": false } } ``` Matched text is replaced with `[REDACTED:]` in-place. The upstream call still proceeds with the redacted content. Note: when the rule action is `redact`, the content-guard deny-check is skipped by the evaluator — redaction is applied directly to the request body before forwarding. ### Example: Deny if secrets detected in LLM prompt ```json theme={null} { "target": { "kind": "llm_endpoint", "endpoint": "chat.completions" }, "action": "allow", "contentGuard": { "detectors": ["pack:secrets-default", "custom:internal-id"], "action": "deny", "inspectResponse": true } } ``` ### Built-in packs | Pack | Detector members | | ---------------------- | ------------------------------------------------------------------------------------------------------------------ | | `pack:pii-default` | `us-ssn`, `credit-card`, `email`, `us-phone`, `ipv4`, `iban`, `uk-nin` | | `pack:secrets-default` | `aws-access-key`, `aws-secret-key`, `gcp-service-account`, `private-key-pem`, `slack-token`, `github-token`, `jwt` | ### Custom detectors Manage custom detectors via the API at `https://api.igrisecurity.com/api/v1/detectors/custom`. Three kinds are supported: `regex`, `keywords`, and `luhn`. Reference a saved custom detector in any `detectors` array using the `custom:` prefix. ## Combining Guards with Actions Guards and actions interact as follows: | Rule action | Token guard fires | Content guard fires | | ----------- | ---------------------------------- | ---------------------------------------------------------------------------------------------------------- | | `allow` | Request denied | Denied or redacted depending on `contentGuard.action` | | `deny` | Request denied (guard fires first) | Request denied | | `alert` | Request denied | Denied or redacted depending on `contentGuard.action` | | `redact` | Request denied | Content-guard deny-check is skipped; redaction is applied separately to the request body before forwarding | For maximum protection, pair a `deny` rule action with a `contentGuard.action` of `"deny"`. The content guard provides a second enforcement layer if the primary rule action is changed. ## Related * [Policies](/govern/policies) — how rules are structured, evaluated, and rate-limited * [Tool Calls](/govern/tool-calls) — how MCP tool calls flow through the proxy * [Identity & Metadata](/reference/sdk#identity--metadata) — how user and metadata context reach the policy engine # Policies Source: https://docs.igrisecurity.com/govern/policies Define governance rules to allow, deny, alert, or redact MCP tool calls and LLM requests using glob patterns, conditions, and rate limits. # Policies Policies are the core of Igris governance. Each policy contains an ordered list of rules evaluated against incoming tool calls and LLM requests. The first matching rule determines the action. ## Concepts A **policy** is attached to one or more connections and contains: * **Name** — human-readable label * **Connection** — which connection(s) this policy applies to * **Rules** — ordered list of target/action/condition tuples * **Enabled** — whether the policy is active A **rule** specifies: * **Target** — what to match (MCP tool name, LLM model, or LLM endpoint) * **Action** — what to do when the target matches: `allow`, `deny`, `alert`, or `redact` * **Conditions** (optional) — attribute-based gates on user identity or metadata * **Rate limit** (optional) — cap calls, tokens, or dollars per time period * **Token guard** (optional, LLM rules) — cap input tokens or `max_tokens` per request * **Content guard** (optional) — run detectors on request or response content ## Rule Targets The `target.kind` field is the discriminator. Three variants are supported: ```ts theme={null} type PolicyRuleTarget = // Match an MCP tool call by name or glob | { kind: "mcp_tool"; tool: string } // Match a specific LLM model name or glob (e.g. "gpt-4*", "claude-*") | { kind: "llm_model"; model: string } // Match a specific LLM endpoint type | { kind: "llm_endpoint"; endpoint: | "chat.completions" | "embeddings" | "images.generate" | "audio.transcriptions" | "audio.speech" | "passthrough" } ``` ### MCP tool glob matching MCP tool targets use glob-style patterns with `*` as a wildcard: | Pattern | Matches | | ------------- | -------------------------------------------- | | `*` | Everything (catch-all) | | `delete_*` | `delete_user`, `delete_record`, `delete_all` | | `*_sensitive` | `read_sensitive`, `export_sensitive` | | `db_*` | `db_query`, `db_insert`, `db_delete` | | `read_user` | Exact match: only `read_user` | ### LLM model glob matching The `model` field also supports `*` as a wildcard: ```json theme={null} { "kind": "llm_model", "model": "gpt-4*" } // matches gpt-4o, gpt-4-turbo, gpt-4o-mini { "kind": "llm_model", "model": "*" } // catch-all — matches any model { "kind": "llm_model", "model": "claude-3-5-*" } // matches claude-3-5-sonnet, claude-3-5-haiku ``` ## Rule Evaluation: First Match Wins Rules are evaluated **top to bottom**. The first rule whose target matches (and whose conditions pass) determines the action. Remaining rules are skipped. ```json theme={null} { "rules": [ { "target": { "kind": "mcp_tool", "tool": "delete_*" }, "action": "deny" }, { "target": { "kind": "mcp_tool", "tool": "drop_*" }, "action": "deny" }, { "target": { "kind": "mcp_tool", "tool": "write_*" }, "action": "alert" }, { "target": { "kind": "mcp_tool", "tool": "*" }, "action": "allow" } ] } ``` In this example: * `delete_users` → **denied** (matches rule 1) * `drop_table` → **denied** (matches rule 2) * `write_record` → **allowed + alert sent** (matches rule 3) * `read_data` → **allowed** (matches catch-all rule 4) If no rule matches, the tool call is **denied by default**. Always include a catch-all `*` rule at the end if you want a default-allow posture. ## Actions ### Allow The tool call is forwarded to the upstream server. An audit event is logged. ### Deny The tool call is blocked. The client receives an error response. An audit event is logged with the denial reason. ### Alert The tool call is forwarded (same as allow), but an additional anomaly event is emitted via SSE and logged. Use this for tool calls you want to monitor without blocking. ### Redact The tool call arguments are inspected by the configured content-guard detectors. Any matched sensitive content (PII, secrets, etc.) is masked in-place before the request is forwarded. The upstream call still proceeds — `redact` is not a blocking action. An audit event records what was redacted. ## Conditions (Attribute-Based Access Control) Rules can include conditions that match against user identity and request metadata. A rule with conditions only applies when all conditions are met. ### Example: Block destructive ops for interns ```json theme={null} { "rules": [ { "target": { "kind": "mcp_tool", "tool": "delete_*" }, "action": "deny", "conditions": { "metadata.role": "intern" } }, { "target": { "kind": "mcp_tool", "tool": "drop_*" }, "action": "deny", "conditions": { "metadata.role": "intern" } }, { "target": { "kind": "mcp_tool", "tool": "*" }, "action": "allow" } ] } ``` This blocks `delete_*` and `drop_*` calls **only** when `metadata.role` is `"intern"`. Admins and developers can still use these tools. ### Condition Fields | Field | Source | Example | | ---------------- | ------------------------------ | --------------------------------- | | `user` | `X-Igris-User` header | `"user": "alice@company.com"` | | `traceId` | `X-Igris-Trace-Id` header | `"traceId": "trace-abc"` | | `metadata.*` | `X-Igris-Metadata` header | `"metadata.role": "admin"` | | `connectionSlug` | The connection slug being used | `"connectionSlug": "openai-prod"` | ### Condition Operators | Operator | Example | Meaning | | ------------------ | --------------------------------------------- | ------------------ | | String (shorthand) | `"metadata.role": "intern"` | Equals "intern" | | `eq` | `"metadata.role": { "eq": "intern" }` | Equals "intern" | | `neq` | `"metadata.role": { "neq": "admin" }` | Not equals "admin" | | `in` | `"metadata.role": { "in": ["dev", "admin"] }` | Value in list | | `nin` | `"metadata.role": { "nin": ["intern"] }` | Value not in list | ### Missing Fields If a condition references a field that isn't present in the request, **the rule is skipped** — it does not deny or allow. Evaluation continues to the next rule. Pass user identity and metadata via the SDK's `connectHttp()` options. See [Identity & Metadata](/reference/sdk#identity--metadata) for details. ## Rate Limits Add a `limit` to any rule to restrict usage frequency. You can cap by requests, tokens, or dollars over a `minute`, `hour`, or `day` period: ```json theme={null} { "target": { "kind": "mcp_tool", "tool": "query_*" }, "action": "allow", "limit": { "requests": 100, "per": "minute" } } ``` All three dimensions are optional — set any combination: | Field | Type | Description | | ---------- | --------------------------------- | ----------------------------------- | | `requests` | number | Maximum tool calls in the period | | `tokens` | number | Maximum tokens consumed (LLM rules) | | `dollars` | number | Maximum cost in USD (LLM rules) | | `per` | `"minute"` \| `"hour"` \| `"day"` | The rolling window duration | When any configured limit is exceeded, the tool call is denied regardless of the rule's action. Rate counters are tracked per organization in Redis using sliding windows. Example: cap GPT-4o calls for contractors at 10 requests/hour: ```json theme={null} { "target": { "kind": "llm_model", "model": "gpt-4o" }, "action": "allow", "conditions": { "metadata.role": "contractor" }, "limit": { "requests": 10, "per": "hour" } } ``` ## Token Guards and Content Guards Token guards cap the size of LLM requests before they are forwarded. Content guards run detectors on request and response content. Both are documented on the [Guards](/govern/guards) page. ## Creating Policies 1. Go to **Govern → Policies** in the dashboard 2. Click **Create Policy** 3. Select the target connection 4. Add rules in priority order (with optional conditions) 5. Save ## Policy Caching Active policies are cached in Redis with a TTL. When you update a policy, the cache is invalidated immediately. On cache miss, policies are loaded from the database and re-cached. This ensures low-latency policy evaluation on the proxy path while keeping changes responsive. ## Best Practices 1. **Start restrictive** — begin with deny-all, then add specific allows as needed. 2. **Use alert for new tools** — when a new MCP tool appears, set it to alert before deciding on allow/deny. 3. **Group by risk** — create separate policies for different risk levels (e.g., read-only vs. write operations). 4. **Use rate limits on expensive operations** — protect against runaway agents with rate limits on write and delete operations. 5. **Review audit events** — regularly check denied and alerted calls to refine your policies. ## Related * [Guards](/govern/guards) — token guards and content guards * [Sessions](/govern/sessions) — session lifecycle and kill switch * [Anomaly Detection](/govern/anomaly) — automatic rate spike and destructive pattern detection * [Identity & Metadata](/reference/sdk#identity--metadata) — passing user and metadata context # Sessions Source: https://docs.igrisecurity.com/govern/sessions Agent sessions track tool call activity per organization and connection, with a kill switch for immediate control. # Sessions Agent sessions are automatically created when an MCP client begins making tool calls through the Igris gateway. They provide a grouping mechanism for related tool calls and a kill switch for immediate control. Source: `packages/proxy/src/session-manager.ts`, `apps/api/src/routes/sessions.ts`. ## How Sessions Work When the proxy receives a tool call, Igris checks for an active session for that organization + connection combination. If none exists, one is created automatically. Each session tracks: * **Organization** — which org owns this session * **Connection** — which connection the calls are routed through * **Status** — `active`, `suspended`, or `completed` * **Tool call count** — total number of calls in this session * **Started at** — when the session was created Sessions expire after **30 minutes of inactivity**. Last-access time is tracked internally for TTL purposes only; there is no `updatedAt` field on the session object. ## Session Deduplication Igris prevents duplicate sessions for the same org + connection combination. If a session already exists and is active, new tool calls join that session rather than creating a new one. This keeps the audit trail clean and the kill switch effective. ## Kill Switch The kill switch lets you instantly suspend a session. When suspended: * All incoming tool calls for that session return a JSON-RPC error: `"Agent session suspended by kill switch"` * The upstream MCP server receives no further requests * A `session_suspended` **webhook** is dispatched to any registered webhook endpoints Suspension dispatches a webhook but does **not** log an audit event and does **not** emit an SSE event. Notification is via webhook only. ### Suspending via API ```bash theme={null} POST https://api.igrisecurity.com/api/v1/sessions/{id}/suspend Authorization: Bearer Content-Type: application/json { "reason": "Suspected credential exfiltration" } ``` Response: ```json theme={null} { "suspended": true } ``` ### Resuming via API ```bash theme={null} POST https://api.igrisecurity.com/api/v1/sessions/{id}/resume Authorization: Bearer ``` Response: ```json theme={null} { "resumed": true } ``` ### Listing Sessions ```bash theme={null} GET https://api.igrisecurity.com/api/v1/sessions?status=active Authorization: Bearer ``` Filter by `status=active`, `status=suspended`, or `status=completed`. Multiple statuses can be comma-separated. ## Use Cases * **Incident response** — immediately halt a misbehaving agent without restarting infrastructure * **Maintenance windows** — suspend sessions during database maintenance * **Investigation** — pause activity while reviewing anomaly alerts * **Cost control** — stop a runaway agent racking up API costs ## Related * [Policies](/govern/policies) — rules that govern what tool calls are allowed in a session * [Anomaly Detection](/govern/anomaly) — automatic alerts on rate spikes and destructive patterns * [Tool Calls](/govern/tool-calls) — how `igris.mcp` calls flow through the proxy and are audit-logged # Tool Calls Source: https://docs.igrisecurity.com/govern/tool-calls Call MCP tools programmatically with the igris.mcp resource — initialize, listTools, and callTool over the gateway. # Tool Calls When you'd rather not wire up your own MCP client, the SDK exposes `igris.mcp` — a small JSON-RPC client that speaks to the Igris gateway directly. Every call is policy-evaluated, identity-tagged, and audit-logged exactly the same way external MCP clients are. ## When to use this | Use `igris.mcp.*` when… | Use `igris.connectHttp()` when… | | --------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------- | | You're calling tools from server-side code (jobs, agents, schedulers) | You're configuring an external MCP client (Claude Desktop, Cursor, VS Code, your own MCP-aware app) | | You want zero extra dependencies on top of the SDK | You already use `@modelcontextprotocol/sdk` or another HTTP MCP client and want to keep that wiring | | You want one return type (string content) instead of full JSON-RPC plumbing | You need raw access to MCP capabilities, prompts, resources, etc. | Both paths route through the same gateway, hit the same policies, and produce the same audit events. ## API ```typescript theme={null} igris.mcp.initialize(slug, options?): Promise<{ protocolVersion, serverInfo }> igris.mcp.listTools(slug, options?): Promise igris.mcp.callTool(slug, toolName, args, options?): Promise ``` All three accept the same `McpConfigOptions`: ```typescript theme={null} interface McpConfigOptions { user?: string; // sent as X-Igris-User traceId?: string; // sent as X-Igris-Trace-Id; auto-generated if omitted metadata?: Record; // sent as X-Igris-Metadata: } ``` ## Quick Start ```typescript theme={null} import { Igris } from "@igris-security/sdk"; const igris = new Igris({ apiKey: process.env.IGRIS_API_KEY! }); // Optional handshake — useful for sanity checking that the connection is wired up. const init = await igris.mcp.initialize("github-prod", { user: "alice@company.com", }); console.log(init.serverInfo); // { name: "github-mcp", version: "1.2.0" } // Discover available tools. const tools = await igris.mcp.listTools("github-prod", { user: "alice@company.com", }); for (const tool of tools) { console.log(`${tool.name} — ${tool.description}`); } // Invoke a tool. The return value is the upstream tool's text content joined. const output = await igris.mcp.callTool( "github-prod", "create_issue", { repo: "acme/igris", title: "Migration plan for v2", body: "Initial draft.", }, { user: "alice@company.com", traceId: "trace-issue-triage-789", metadata: { feature: "issue-triage", env: "production" }, }, ); console.log(output); ``` Every call hits `https://api.igrisecurity.com/v1/mcp/`, carries `Authorization: Bearer `, and is policy-evaluated server-side before being forwarded to the upstream MCP server with its real credential injected. ## Threading multiple calls under one trace If a single user request triggers several tool calls, share the trace id so Lens can show the whole chain as one operation: ```typescript theme={null} const traceId = "trace-" + crypto.randomUUID(); const opts = { user: "alice@company.com", traceId }; await igris.mcp.callTool("github-prod", "list_issues", { repo: "acme/igris" }, opts); await igris.mcp.callTool("slack-prod", "post_message", { channel: "#triage", text: "Top 3 issues this week ...", }, opts); ``` Both rows in the audit trail will share `traceId = "trace-..."`, which lets you filter Lens to the full sequence. ## Error handling `igris.mcp` methods (`igris.mcp.initialize`, `igris.mcp.listTools`, `igris.mcp.callTool`) throw plain `Error` instances — **not** the typed `IgrisError` subclasses. The error message includes the HTTP status and response body: ``` MCP gateway error (403): {"error":"policy denied"} MCP error: tool not found ``` The typed error classes (`IgrisPolicyDeniedError`, `IgrisRateLimitError`, `IgrisAuthError`) apply only to `igris.chat.completions`, `igris.embeddings`, and `igris.connections` — resources that use `fetchWithRetry` internally. `igris.mcp` uses a plain `fetch` call. Handle `igris.mcp` errors with a standard try/catch: ```typescript theme={null} try { const output = await igris.mcp.callTool("github-prod", "delete_repo", { repo: "acme/legacy" }); } catch (e) { if (e instanceof Error) { if (e.message.includes("403")) { // policy denied or auth failure } else if (e.message.includes("429")) { // rate limited — implement your own backoff and retry } else { // upstream or gateway error } } throw e; } ``` `igris.mcp` does **not** auto-retry. Unlike the LLM gateway resources, `igris.mcp` makes a single `fetch` call with no retry logic. If you need resilience against transient errors or rate limits, implement your own retry loop with exponential backoff. ## What gets logged Every `initialize`, `listTools`, and `callTool` produces an audit event with: * The actor (`user`, derived from `X-Igris-User`) * The connection slug * The tool name and arguments (for `callTool`) * The policy decision (`allow` / `deny` / `alert`) * Any matched rule and conditions * The trace id (`X-Igris-Trace-Id`) * All metadata fields (`X-Igris-Metadata`) * The timing and outcome All of this surfaces in Lens and is queryable via the audit-events API. ## Related * [Connections](/connect/connections) — how to register the upstream MCP server and store its credential * [Identity & Metadata](/reference/sdk#identity--metadata) — what the gateway does with `user`, `traceId`, and `metadata` * [Policies](/govern/policies) — how to gate `tools/call` by tool name, user, or metadata * [MCP Client Config](/connect/mcp-config) — the alternative path: drive an external MCP client # Introduction Source: https://docs.igrisecurity.com/introduction Igris is an AI security and governance platform — govern every MCP tool call, inspect every prompt, and observe every AI interaction. # What is Igris? Igris is an AI security and governance platform that sits at every layer of your AI stack. It governs the MCP tool calls your agents make, inspects the prompts and responses flowing through your LLMs, and gives security and compliance teams a unified view of every AI interaction — all from a single dashboard. AI teams today face a fragmented landscape: agents calling dozens of MCP tools and LLM providers with no consistent policy, no firewall, and no audit trail. Igris closes that gap with three integrated products and one security posture. ## The Platform Igris is three integrated products covering every layer of your AI security stack. The MCP governance proxy. Sits between your agents and MCP servers to enforce deny-by-default policies, inject credentials, authorize tool calls, control sessions, and detect anomalies in real time. The LLM firewall. Inspects every prompt and response across 60 providers — blocking prompt injection, redacting PII and secrets, enforcing content policy, and stopping data leakage. The observability layer. A unified dashboard for logs, traces, anomaly alerts, and audit trails across every Igris product. Both Sentinel and Guard are reachable through a single TypeScript SDK and a gateway endpoint, so you add governance to an MCP tool call or an LLM request with one function call. ## Who is Igris for? * **AI/ML Engineering Teams** shipping agents that use MCP tool calls in production and need guardrails. * **CISOs and Security Officers** who need audit evidence without manual spreadsheet work. * **Platform Teams** managing multiple AI providers and needing a single pane of glass for governance. * **Startups** that want governance evidence auto-populated from real system data instead of fabricated. ## Key Principles 1. **Provider-agnostic** — Igris works across leading AI providers including OpenAI, Anthropic, Gemini, and self-hosted models via the 60-provider LLM gateway. It works at the tool-call and log level. 2. **Zero-trust by default** — Policies are deny-first. Unmatched tool calls are blocked unless explicitly allowed. 3. **Evidence from reality** — Audit data is populated from actual events, not templates filled with placeholder data. 4. **Real-time** — SSE-powered dashboard updates. Anomaly detection alerts within seconds. ## Next Steps Get up and running in under 5 minutes. Understand how Igris components fit together. # Audit Trail Source: https://docs.igrisecurity.com/monitor/audit-trail Searchable, filterable log of every LLM call, MCP tool call, and connection event — with archive restore for older data. # Audit Trail The Audit Trail screen at [app.igrisecurity.com/audit](https://app.igrisecurity.com/audit) provides a paginated, filterable table of every event recorded by the proxy and LLM gateway. Use it for incident investigations, compliance reviews, and ad-hoc queries. Audit events are **mutable**. The archive job deletes rows from the database after your plan's retention window and writes them to S3. You can restore archived events back into the live table — see [Archive and restore](#archive-and-restore) below. *** ## Event types | Type | Description | | ------------ | ---------------------------------------------------- | | `llm.call` | A request forwarded by the LLM gateway to a provider | | `mcp.tool` | A tool call evaluated by the MCP proxy | | `connection` | A connection-level lifecycle event | *** ## Filters The toolbar exposes six independent filters that can be combined freely: | Filter | Values | | --------------- | ----------------------------------------------------------------------------------------- | | **Type** | All / `llm.call` / `mcp.tool` / `connection` | | **Action** | Policy outcome: `allow`, `deny`, `alert`, `redact` | | **Connection** | Dropdown of all registered connection slugs (or `(none)` for events without a connection) | | **Date range** | Calendar picker for `from` / `to` timestamps | | **Text search** | Free-text match across event ID, model, tool name, connection slug, and user ID | | **Sort** | Column (timestamp / type / action / detail) and direction (asc / desc) | Results update with a 250 ms debounce. Press **Clear** to reset all filters at once. *** ## Table columns | Column | Content | | -------------- | ----------------------------------------------- | | **Time** | Event timestamp | | **Type** | `llm.call`, `mcp.tool`, or `connection` | | **Detail** | Model name (LLM calls) or tool name (MCP calls) | | **Actor** | User or service that triggered the event | | **Connection** | Connection slug | | **Action** | Policy outcome | Click any row to open the **event detail drawer** with the full event payload. *** ## Pagination Page size is configurable (default 25 events per page). Navigation controls sit at the bottom of the table. The toolbar also shows a count: "Showing N of total events". *** ## CSV export Click **Export** in the toolbar to download the **current page** of results as a CSV file (`audit-trail.csv`). The export includes: Time, Type, Detail, Actor, Connection, Action. The export covers only the rows currently loaded in the table — not the entire result set. Apply filters first to scope what you need, then export. *** ## Archive and restore Igris archives audit events to S3 when they exceed your plan's retention window. The archive runs daily and deletes the rows from Postgres after writing them, so they no longer appear in the live table. ### Archive banner When you apply a date range filter that overlaps an archived period, an amber banner appears above the table: > **Some logs in this range are archived.** \[date range] — **Request restore** The banner shows the start and end of the first archived range, plus a count of additional ranges if more than one overlaps. ### Requesting a restore Click **Request restore** to open the restore dialog. It shows: * The date range to be restored * Estimated row count and data size * The exact fee for this restore job and how many free restores your org has remaining this month Restore jobs may incur a per-job fee that depends on your plan. Some plans include a number of free restores each calendar month; once those are used, each additional job is charged at your plan's rate. Higher tiers include more free restores and may have different per-job fees. Enterprise plans include unlimited free restores. The restore dialog always reflects your current plan's fee and remaining free allowance before you confirm — you will never be charged an unexpected amount. For current pricing details, open the **Plans & Billing** tab in [Settings](https://app.igrisecurity.com/settings). After submitting, a restore job is queued. The job writes events back to your org's live table. Once complete, the events are visible in the audit trail again and the archive banner disappears for that range. Archive restore requires the **Growth** plan or above. The button is disabled on Starter. *** ## API The audit trail is backed by `GET /api/v1/audit-events`. See the Reference section for full parameter documentation. # Dashboard Source: https://docs.igrisecurity.com/monitor/dashboard Real-time overview of MCP and LLM activity, security posture, and recent incidents across all connections. # Dashboard The Igris dashboard gives you a live snapshot of activity across every connection your organization has registered. Open it at [app.igrisecurity.com](https://app.igrisecurity.com). ## Time range A segmented control at the top right sets the reporting window for every panel on the page: | Label | Window | | ------- | --------------------- | | **24h** | Last 24 hours | | **7d** | Last 7 days (default) | | **30d** | Last 30 days | Use the refresh button to re-fetch data without changing the window. *** ## Platform status banner A status hero sits at the top of the page. When the selected period contains policy denials or anomalies the banner shows **"Incidents detected"** with a count of each. Otherwise it reads **"All systems operational"**. A live clock refreshes every second; the stated SLA is **99.99 %**. *** ## KPI strip Six metric cards span the full width of the page. Each card shows the current-period total, a sparkline, and a percentage change vs. the previous equivalent window where available. | KPI | What it counts | Source field | | ------------------- | ------------------------------------------------------- | ------------------------------------- | | **MCP Tool Calls** | Total proxy-evaluated tool calls (allow + alert + deny) | `totals.toolCalls` | | **LLM Requests** | Requests forwarded by the LLM gateway | `totals.llmCalls` | | **Threats Blocked** | Injection attempts stopped by content guards | `totals.injectionsBlocked` | | **Policy Denials** | Tool calls blocked by a policy rule | `totals.denied` | | **PII Redacted** | Redaction actions applied to payloads | `totals.piiRedacted` | | **LLM Cost** | Cumulative spend across all LLM connections | `totals.costCents` (displayed in USD) | MCP Tool Calls and Policy Denials each show a delta indicator (arrow up / arrow down) compared to the previous period. The other four metrics show a flat indicator. *** ## Activity timeline A stacked bar chart below the KPI strip plots **Allow / Alert / Deny** counts over time. The x-axis buckets are derived from the selected period (hourly for 24 h, daily for 7 d and 30 d). A link in the chart header navigates to the [Observe](/monitor/dashboard#observe-screen) screen for a deeper view. Legend colours: * **Allow** — green * **Alert** — amber * **Deny** — red *** ## Security posture A panel to the right of the activity timeline summarises your governance coverage: | Row | Colour when non-zero | | ------------------ | -------------------- | | Active policies | neutral | | Open denials (24h) | amber | | Anomalies flagged | amber | | Suspended sessions | red | | Active sessions | neutral | A **coverage score** (0–100) is computed from your policy count, connection count, injection rate, session health, and anomaly count. The score label is **Strong** (≥ 80), **Moderate** (≥ 50), or **Setup needed** (\< 50). *** ## Top connections Lists connections by total request volume for the selected period. When no connections have been registered yet, the panel shows a prompt to create one. *** ## Top denied tools Shows up to five tool names ordered by policy-denial count. Links to the [Audit Trail](/monitor/audit-trail) screen. *** ## Recent incidents Shows up to four denied tool calls from the last 24 hours tagged **Critical**. Links to the [Incidents](/monitor/incidents) screen for the full list. *** ## Observe screen Accessible from **app.igrisecurity.com/observe** or via the **"Open Observe →"** link on the activity timeline. The Observe screen gives a CISO-oriented view over the same time window (day / week / month). ### KPI row | KPI | What it counts | | ------------------- | -------------------------------------------- | | **Total Requests** | LLM calls + MCP tool calls combined | | **Threats Blocked** | Injections blocked + policy denials combined | | **PII Redacted** | Redaction actions | | **Total Cost** | LLM spend in USD | ### Executive summary A prose paragraph generated server-side describing key findings for the selected period. Rendered below the KPI row. ### Risk heat map A grid of AI systems (rows) by risk categories (columns): **PII Exposure**, **Injection Attempts**, **Policy Violations**, **Anomalies**. Each cell is colour-coded: * **Red (High)** — elevated count and/or high-severity finding * **Amber (Medium)** — moderate activity * **Green (Low)** — minimal activity * **None** — no data ### Threat distribution A breakdown of all threat types as proportional progress bars: * Policy Denials * PII Detections * Injections Blocked * Anomalies # Incidents Source: https://docs.igrisecurity.com/monitor/incidents Triage and track security incidents — policy denials, anomalies, and session events — in a unified queue. # Incidents The Incidents screen at [app.igrisecurity.com/incidents](https://app.igrisecurity.com/incidents) surfaces security events that require review. Denials, anomaly detections, and session-level events all flow into a single queue so your team has one place to triage. Anomaly detections appear here — there is no separate Anomaly dashboard screen. The **Anomalies** tab on this page is where you find and action them. *** ## Incident categories | Category | What it represents | | --------- | -------------------------------------------------------------- | | `denial` | A tool call or LLM request blocked by a policy rule | | `anomaly` | A statistical or pattern-based anomaly flagged by the detector | | `session` | A session-level event (e.g. kill-switch suspension) | *** ## Tabs Four tabs filter the incident list: | Tab | Shows | | ------------- | --------------------------------------------------------------- | | **All** | Every incident regardless of category, with a total count badge | | **Denials** | `denial` category only | | **Anomalies** | `anomaly` category only | | **Sessions** | `session` category only | Each tab label shows the count of incidents matching that category in the current result set. *** ## Filters and search The toolbar below the tabs provides additional scoping: | Filter | Options | | --------------- | --------------------------------------------------------------- | | **Severity** | `critical` / `high` / `medium` / `low` / `info` | | **Status** | `open` / `investigating` / `resolved` | | **Text search** | Matches against title, summary, actor name, and connection slug | | **Group by** | `severity` (default) | Press **Clear** to reset all filters. *** ## Incident list Incidents are sorted by status (open → investigating → resolved), then by severity (critical → high → medium → low → info), then by most-recently-seen. Each row shows: * Severity badge * Title and summary * Category * Connection slug * Actor name * First seen / last seen timestamps * Current status Click a row to open the **incident detail drawer**. *** ## Incident detail drawer The drawer opens from the right and shows: * **Incident metadata** — title, summary, severity, category, status, first/last seen, connection, actor * **Policy** — the policy and rule that triggered the incident (if applicable) * **Impact counters** — blocked events, allowed events, and redacted events linked to this incident * **12-hour pulse** — hourly bar chart of linked events over the past 12 hours * **Linked audit events** — a paginated list of the raw `mcp.tool` or `llm.call` events that were grouped into this incident Deep-linking is supported: opening `https://app.igrisecurity.com/incidents?id=` loads the drawer directly for that incident. *** ## Triage actions Three status transitions are available from the drawer: | Action | Sets status to | Notes | | ---------------------- | --------------- | ------------------------------------------------------- | | **Mark investigating** | `investigating` | Signals the incident is being reviewed | | **Resolve** | `resolved` | Requires a resolution note. Sets `resolvedAt` timestamp | | **Reopen** | `open` | Clears the resolution note and `resolvedAt` | These actions map to `PATCH /api/v1/incidents/:id` with `{ status, resolution_note }`. *** ## Severity Severities are assigned automatically by the incident clustering logic. They can be overridden via the API by patching `severity`. Once a severity is manually set it is locked and will not be overwritten by future clustering. *** ## API | Method | Path | Purpose | | ------- | ------------------------------ | ---------------------------------------------------------------- | | `GET` | `/api/v1/incidents` | List incidents (filter by `category`, `severity`, `status`, `q`) | | `GET` | `/api/v1/incidents/:id` | Incident detail with impact counters and pulse | | `GET` | `/api/v1/incidents/:id/events` | Linked audit events for an incident | | `PATCH` | `/api/v1/incidents/:id` | Update status, severity, or resolution note | | `GET` | `/api/v1/incidents/count` | Count by status (`open`, `investigating`, `resolved`) | # LLM Cost Source: https://docs.igrisecurity.com/monitor/llm-cost Spend analytics for every LLM request routed through the Igris gateway — broken down by model, connection, and time. # LLM Cost The LLM Cost screen at [app.igrisecurity.com/llm-cost](https://app.igrisecurity.com/llm-cost) shows spending analytics for all requests processed by the Igris LLM gateway. Cost data is derived from the `costCents`, `tokensIn`, `tokensOut`, `model`, and `provider` fields recorded on each `llm_call` audit event. *** ## Time range A segmented control in the page header selects the reporting window: | Option | Window | | ------- | --------------------- | | **24h** | Last 24 hours | | **7d** | Last 7 days (default) | | **30d** | Last 30 days | | **90d** | Last 90 days | All panels update when the range changes. *** ## Summary KPIs Two rows of KPI cells appear at the top of the page. **Primary metrics:** | KPI | Description | | -------------------- | ----------------------------------------------------------------------- | | **Total Spend** | Sum of `costCents` across all LLM calls in the period, displayed in USD | | **Avg Cost/Call** | `totalCents ÷ totalCalls` | | **Total Calls** | Count of `llm_call` audit events in the period | | **Active Providers** | Number of distinct providers with at least one call | **Token metrics:** | KPI | Description | | ------------------- | ----------------------------------------------- | | **Input Tokens** | Sum of `tokensIn` | | **Output Tokens** | Sum of `tokensOut` | | **Avg Tokens/Call** | `(totalTokensIn + totalTokensOut) ÷ totalCalls` | *** ## Spend over time A stacked bar chart plots daily spend in USD, with each provider as a separate series. The x-axis is date-bucketed by `date_trunc('day', timestamp)`. Hover a bar to see the dollar breakdown per provider for that day. This chart only renders when there is at least one data point in the selected range. *** ## Top models by spend A horizontal bar chart listing the top 10 models ranked by total `costCents`. Each bar shows the model identifier and formatted spend. The widest bar represents 100 %; all others are proportional. *** ## Top connections by spend A horizontal bar chart listing the top 10 connection slugs ranked by total `costCents`. Useful for identifying which integrations are driving the most spend. *** ## Avg cost per call by connection A horizontal bar chart ranking connections by their **average** cost per call (rather than total spend). This surfaces connections that use expensive models infrequently — connections that "Top Connections by Spend" can miss when they are outranked by high-volume, low-cost connections. The label for each bar includes the connection slug and its total call count. *** ## Token usage by model A horizontal bar chart ranking models by combined token consumption (`tokensIn + tokensOut`). Helps identify which models are the largest token consumers regardless of unit price. *** ## API | Method | Path | Description | | ------ | -------------------------------------- | ---------------------------------------------------------------- | | `GET` | `/api/v1/llm-cost/summary?range=7d` | Aggregated totals, top models, top connections, avg-cost ranking | | `GET` | `/api/v1/llm-cost/timeseries?range=7d` | Daily spend per provider for the stacked bar chart | **`range`** accepts `24h`, `7d`, `30d`, or `90d`. Defaults to `7d`. ### Summary response shape ```json theme={null} { "totalCents": 1234, "totalCalls": 980, "totalTokensIn": 450000, "totalTokensOut": 120000, "topModels": [ { "model": "claude-3-5-sonnet-20241022", "cents": 800, "calls": 600, "tokensIn": 300000, "tokensOut": 80000 } ], "topConnections": [ { "slug": "prod-gateway", "cents": 950, "calls": 750 } ], "connectionsByAvgCost": [ { "slug": "premium-agent", "avgCents": 42.5, "calls": 12 } ], "range": "7d" } ``` ### Timeseries response shape ```json theme={null} [ { "date": "2026-06-20T00:00:00.000Z", "provider": "anthropic", "cents": 120, "calls": 90 }, { "date": "2026-06-20T00:00:00.000Z", "provider": "openai", "cents": 45, "calls": 30 } ] ``` # Real-Time Events Source: https://docs.igrisecurity.com/monitor/real-time-events SSE endpoint for real-time dashboard updates with org-scoped tool call and anomaly events. # Real-Time Events Igris provides a Server-Sent Events (SSE) endpoint that streams real-time events to connected clients. The dashboard uses this for live updates, and you can connect your own clients for monitoring and alerting. ## SSE Endpoint ``` GET /api/v1/events ``` **Authentication:** Session cookie or API key required. **Scoping:** Events are scoped to the authenticated user's active organization. You only receive events for servers belonging to your org. ## Connecting ### Browser (EventSource) ```javascript theme={null} const eventSource = new EventSource( "https://api.igrisecurity.com/api/v1/events", { withCredentials: true } // sends session cookie ); eventSource.addEventListener("tool_call", (event) => { const data = JSON.parse(event.data); console.log("Tool call:", data.toolName, data.action); }); eventSource.addEventListener("anomaly", (event) => { const data = JSON.parse(event.data); console.log("Anomaly detected:", data.type, data.severity); }); ``` ### curl ```bash theme={null} curl -N -H "Authorization: Bearer ig_abc123..." \ https://api.igrisecurity.com/api/v1/events ``` ## Event Types ### `connected` Emitted immediately after the SSE connection is established, confirming the stream is live. ```json theme={null} { "orgId": "org_abc123" } ``` ### `heartbeat` Sent every 30 seconds to keep the connection alive. ```json theme={null} { "ts": 1719446400000 } ``` ### `tool_call` Emitted when a tool call is processed by the proxy. ```json theme={null} { "toolName": "read_users", "serverId": "db-production", "action": "allow" } ``` ### `anomaly` Emitted when anomaly detection triggers an alert. ```json theme={null} { "type": "rate_spike", "toolName": "query_records", "serverId": "db-production", "severity": "high" } ``` ### `llm_call` Emitted when a request is processed by the LLM gateway. ### `llm_anomaly` Emitted when the LLM anomaly detector fires on a gateway request. Session suspension does not emit an SSE event. When a session is suspended via the kill switch, a `session_suspended` **webhook** is dispatched to any registered webhook endpoints. There is no `session_update` SSE event. ## Infrastructure SSE events are delivered directly to in-process org listeners — the `emitEvent` function looks up registered callbacks for the org and calls them synchronously. Igris also publishes to an Upstash Redis channel (`igris:events`) as a fire-and-forget side-channel; however, the API does not maintain a Redis subscriber loop, so cross-instance fan-out relies on in-process delivery. Clients connected to the same API instance as the event source receive events reliably. # Quickstart Source: https://docs.igrisecurity.com/quickstart Go from zero to governed MCP tool calls in under 5 minutes. # Quickstart This guide walks you through signing up, creating a connection, writing your first policy, and making a governed MCP tool call. ## 1. Sign Up Navigate to [app.igrisecurity.com/signup](https://app.igrisecurity.com/signup) and create an account using email/password or OAuth (GitHub, Google). After signup you'll be prompted to create your first organization. ## 2. Create a Connection Connections are encrypted credential vaults for your upstream MCP servers. Go to **Governance → Connections** and click **New Connection**. Provide: * **Name** — a human-readable label (e.g., "GitHub Production") * **Slug** — a URL-safe identifier (e.g., `github-prod`) * **Upstream URL** — where the real MCP server lives (e.g., `https://mcp.github.com`) * **Credential** — the upstream API key or token (encrypted at rest) Once created, Igris gives you a gateway URL: ``` https://api.igrisecurity.com/v1/mcp/github-prod ``` ## 3. Install the SDK ```bash theme={null} npm install @igris-security/sdk ``` ## 4. Connect to MCP ```typescript theme={null} import { Igris } from "@igris-security/sdk"; const igris = new Igris({ apiKey: "ig_your_api_key", // from Settings → API Keys }); const config = igris.connectHttp("github-prod", { user: "alice@company.com", metadata: { role: "developer" }, }); // Use with any MCP client // Spread config.headers alongside Authorization — it carries session and metadata headers // that Igris requires. Both are needed; neither alone is sufficient. const client = new McpClient({ transport: new StreamableHttpTransport(config.baseUrl, { headers: { Authorization: `Bearer ${config.apiKey}`, ...config.headers, }, }), }); ``` Or for direct MCP client usage (Claude Desktop, Cursor): ```json theme={null} { "mcpServers": { "github": { "url": "https://api.igrisecurity.com/v1/mcp/github-prod", "headers": { "Authorization": "Bearer ig_your_api_key" } } } } ``` Every tool call now flows through Igris before reaching the upstream server. ## 5. Create Your First Policy Go to **Governance → Policies** and click **Create Policy**. Example: block destructive operations for interns. ```json theme={null} { "connectionSlugs": ["github-prod"], "name": "Block destructive ops for interns", "rules": [ { "tool": "delete_*", "action": "deny", "conditions": { "metadata.role": "intern" } }, { "tool": "drop_*", "action": "deny", "conditions": { "metadata.role": "intern" } }, { "tool": "*", "action": "allow" } ] } ``` `connectionSlugs` is an array — a single policy can apply to multiple connections. Rules are evaluated **first match wins**. The catch-all `*` at the end allows everything not explicitly denied. Conditions on `metadata.role` only apply when the SDK passes that metadata. ## 6. Trigger a Tool Call Use your MCP client as normal. The gateway will: * Authenticate your API key * Inject the upstream credential (from the connection) * Evaluate policies (including metadata conditions) * Log everything to the audit trail **Quota exhaustion:** when your organization's monthly log quota is reached, tool calls continue to succeed but audit events are silently dropped. Monitor your usage in **Settings → Usage** to avoid gaps in your audit trail. ## 7. View the Audit Trail Go to **Monitoring → Audit Trail** to see every tool call, including: * User (the API key owner; `X-Igris-User` is stored in request metadata, not the User field) * Trace ID (for correlating multi-step requests) * Tool name + arguments * Policy action (allowed / denied / alerted) * Connection used * Latency Filter by user, trace ID, or connection to drill down. ## What's Next? Route LLM calls through Igris for audit, policies, and cost tracking. Manage encrypted credentials, rotate tokens, and control access. Metadata-based rules, deny-by-default, and condition operators. Set up rate spike and destructive pattern alerts. # API Reference Source: https://docs.igrisecurity.com/reference/api REST API reference for the Igris management API. # REST API Reference ## Base URL ``` https://api.igrisecurity.com ``` All management API endpoints are under `/api/v1/`. ## Authentication All requests require a Bearer API key: ```http theme={null} Authorization: Bearer ig_... ``` Generate keys in **Settings → API Keys** in the Igris Dashboard. On `401`, the response is: ```json theme={null} { "error": "Unauthorized" } ``` *** ## Connections Encrypted credential vaults for MCP and LLM providers. | Method | Path | Description | | -------- | ---------------------------------- | ----------------------------- | | `GET` | `/api/v1/connections` | List all connections | | `POST` | `/api/v1/connections` | Create a connection | | `GET` | `/api/v1/connections/:slug` | Get a connection | | `PATCH` | `/api/v1/connections/:slug` | Update a connection | | `DELETE` | `/api/v1/connections/:slug` | Delete a connection | | `POST` | `/api/v1/connections/test` | Test a connection's upstream | | `POST` | `/api/v1/connections/:slug/rotate` | Rotate connection credentials | **Connection shape:** ```json theme={null} { "id": "uuid", "slug": "github-prod", "name": "GitHub Production", "serverType": "http", "upstreamUrl": "https://api.github.com/mcp", "authType": "bearer", "enabled": true, "createdAt": "2026-01-01T00:00:00Z" } ``` `serverType` is `"http"`, `"stdio"`, or `"llm"`. LLM connections include `llmProvider`, `allowedModels`, `logFullContent`, and `bodyRetentionDays`. *** ## Policies Governance rules evaluated at the gateway for every tool call or LLM request. | Method | Path | Description | | -------- | ---------------------- | ----------------- | | `GET` | `/api/v1/policies` | List all policies | | `POST` | `/api/v1/policies` | Create a policy | | `GET` | `/api/v1/policies/:id` | Get a policy | | `PATCH` | `/api/v1/policies/:id` | Update a policy | | `DELETE` | `/api/v1/policies/:id` | Delete a policy | **Policy shape:** ```json theme={null} { "id": "uuid", "name": "Block delete tools", "connectionSlug": "github-prod", "rules": [ { "target": { "kind": "mcp_tool", "tool": "delete_*" }, "action": "deny" } ], "enabled": true, "createdAt": "2026-01-01T00:00:00Z" } ``` `connectionSlug` is `null` for global policies (applied to all connections). `action` is `"allow"`, `"deny"`, or `"alert"`. Rules also support `conditions`, `limit`, `tokenGuard`, and `contentGuard` fields — see the [Policies guide](/govern/policies). *** ## Sessions MCP agent sessions tracked by the governance gateway. | Method | Path | Description | | ------ | ------------------------------ | -------------------------- | | `GET` | `/api/v1/sessions` | List sessions | | `GET` | `/api/v1/sessions/:id` | Get a session | | `POST` | `/api/v1/sessions/:id/suspend` | Suspend a session | | `POST` | `/api/v1/sessions/:id/resume` | Resume a suspended session | **Session shape:** ```json theme={null} { "id": "uuid", "connectionSlug": "github-prod", "status": "active", "userId": "alice@company.com", "startedAt": "2026-01-01T00:00:00Z", "lastSeenAt": "2026-01-01T01:00:00Z" } ``` `status` is `"active"`, `"suspended"`, or `"ended"`. *** ## Audit Events Complete log of all tool calls, LLM calls, anomaly signals, and policy decisions. Events are retained in Postgres, then archived to S3 and restorable on demand. | Method | Path | Description | | ------ | ----------------------------------- | ------------------------------------------ | | `GET` | `/api/v1/audit-events` | List audit events (paginated) | | `GET` | `/api/v1/audit-events/:id` | Get a single audit event | | `POST` | `/api/v1/audit-events/restore` | Request restore of archived events from S3 | | `GET` | `/api/v1/audit-events/restore-jobs` | List S3 restore jobs | **Audit event shape:** ```json theme={null} { "id": "uuid", "type": "mcp_tool_call", "source": "igris", "toolName": "create_branch", "policyAction": "allow", "connectionSlug": "github-prod", "userId": "alice@company.com", "traceId": "ig_trace_abc123", "severity": null, "timestamp": "2026-01-01T00:00:00Z" } ``` `type` is `"mcp_tool_call"`, `"llm_call"`, or `"anomaly"`. LLM call events include `provider`, `model`, `tokensIn`, `tokensOut`, `costCents`, `latencyMs`, and `llmEndpoint`. Query params: `limit`, `offset`, `type`, `connectionSlug`, `severity`, `from`, `to`. *** ## Incidents Security incidents auto-opened by anomaly detection or alert-action policies. | Method | Path | Description | | ------- | ------------------------------ | ---------------------------------- | | `GET` | `/api/v1/incidents` | List incidents (paginated) | | `GET` | `/api/v1/incidents/count` | Open incident count | | `GET` | `/api/v1/incidents/:id` | Get an incident | | `GET` | `/api/v1/incidents/:id/events` | Get audit events for an incident | | `PATCH` | `/api/v1/incidents/:id` | Update (resolve, assign, add note) | **Incident shape:** ```json theme={null} { "id": "uuid", "title": "Anomaly: rate spike on github-prod", "status": "open", "severity": "high", "connectionSlug": "github-prod", "createdAt": "2026-01-01T00:00:00Z", "resolvedAt": null } ``` `status` is `"open"`, `"investigating"`, or `"resolved"`. Idle incidents auto-resolve after 7 days. *** ## LLM Cost Cost and token usage for LLM gateway traffic. | Method | Path | Description | | ------ | ------------------------------------- | -------------------------------- | | `GET` | `/api/v1/observe/llm-cost/summary` | Aggregate cost and token summary | | `GET` | `/api/v1/observe/llm-cost/timeseries` | Cost time-series data | **Summary shape:** ```json theme={null} { "totalCostCents": 12345, "totalInputTokens": 1000000, "totalOutputTokens": 500000, "byConnection": [...], "byProvider": [...], "byModel": [...] } ``` Query params: `from`, `to`, `connectionSlug`, `provider`. *** ## Observe CISO dashboard metrics for MCP traffic. | Method | Path | Description | | ------ | -------------------------- | -------------------------- | | `GET` | `/api/v1/observe/summary` | Dashboard summary metrics | | `GET` | `/api/v1/observe/risk-map` | Per-connection risk scores | | `GET` | `/api/v1/observe/stats` | Traffic and policy stats | *** ## Webhooks Outbound HTTP webhooks for real-time event delivery. | Method | Path | Description | | -------- | --------------------------- | ----------------- | | `GET` | `/api/v1/webhooks` | List webhooks | | `POST` | `/api/v1/webhooks` | Create a webhook | | `PATCH` | `/api/v1/webhooks/:id` | Update a webhook | | `DELETE` | `/api/v1/webhooks/:id` | Delete a webhook | | `POST` | `/api/v1/webhooks/:id/test` | Send a test event | **Webhook shape:** ```json theme={null} { "id": "uuid", "url": "https://your-service.com/hooks/igris", "events": ["policy_deny", "anomaly"], "enabled": true, "createdAt": "2026-01-01T00:00:00Z" } ``` Event types: `policy_deny`, `anomaly`. The webhook payload includes `type`, `teamId`, `serverId`, `sessionId`, `toolName`, `reason`, and `timestamp`. Deliveries are signed with HMAC-SHA256; verify the `X-Igris-Signature` header. *** ## API Keys Manage programmatic access keys for the API and gateway. | Method | Path | Description | | -------- | ------------------------- | ---------------------- | | `GET` | `/api/v1/api-keys` | List API keys | | `POST` | `/api/v1/api-keys` | Create an API key | | `DELETE` | `/api/v1/api-keys/:id` | Revoke an API key | | `GET` | `/api/v1/api-keys/verify` | Verify a key (CLI use) | API keys are shown in full only once on creation. The list endpoint returns metadata only (no raw key values). *** ## Events (SSE) Real-time event stream over Server-Sent Events. | Method | Path | Description | | ------ | ---------------- | --------------- | | `GET` | `/api/v1/events` | Open SSE stream | ```http theme={null} GET /api/v1/events Authorization: Bearer ig_... Accept: text/event-stream ``` **Event types pushed on the stream:** | Event | When | | ------------- | ---------------------------------- | | `tool_call` | MCP tool call processed by gateway | | `anomaly` | Anomaly signal detected | | `llm_call` | LLM request processed | | `llm_anomaly` | LLM anomaly signal detected | Each event is a JSON object on the `data:` line. The stream is org-scoped — you only receive events for your organization. Keep the connection alive; the server sends a `ping` comment every 30 seconds. *** ## Pagination List endpoints use offset pagination: ```http theme={null} GET /api/v1/audit-events?limit=50&offset=0 ``` Response envelope: ```json theme={null} { "data": [...], "total": 1234, "limit": 50, "offset": 0 } ``` Default `limit` is 50; maximum is 200. # SDK Reference Source: https://docs.igrisecurity.com/reference/sdk Complete reference for @igris-security/sdk — MCP governance and LLM gateway client. # Igris SDK The Igris SDK (`@igris-security/sdk`) is a TypeScript client for the Igris governance gateway. It generates MCP client configurations, calls MCP tools programmatically, and provides a native OpenAI-compatible interface for the LLM gateway. ## Installation ```bash theme={null} npm install @igris-security/sdk ``` ## Initialize ```typescript theme={null} import { Igris } from "@igris-security/sdk"; const igris = new Igris({ apiKey: "ig_...", // Your Igris API key }); ``` The `baseUrl` option can also be set via the `IGRIS_BASE_URL` environment variable. Never commit your API key to source control. Use environment variables: ```typescript theme={null} const igris = new Igris({ apiKey: process.env.IGRIS_API_KEY! }); ``` ## Get Your API Key 1. Log in to the [Igris Dashboard](https://app.igrisecurity.com) 2. Go to **Settings → API Keys** 3. Click **Generate API Key** 4. Copy the key — it's shown only once *** ## MCP Connections ### `igris.connectHttp(slug, options?)` Generate an HTTP connection config for any external MCP client. ```typescript theme={null} const config = igris.connectHttp("github-prod", { user: "alice@company.com", traceId: "req_abc123", metadata: { role: "developer", team: "engineering" }, }); // config.baseUrl → "https://api.igrisecurity.com/v1/mcp/github-prod" // config.apiKey → "ig_..." // config.headers → { "X-Igris-Trace-Id": "ig_trace_...", "X-Igris-User": "alice@company.com", ... } ``` Pass the result to any MCP client: ```typescript theme={null} const response = await fetch(config.baseUrl, { method: "POST", headers: { "Authorization": `Bearer ${config.apiKey}`, "Content-Type": "application/json", ...config.headers, }, body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/list", params: {} }), }); ``` **Return type:** ```typescript theme={null} interface HttpConnection { baseUrl: string; apiKey: string; headers: Record; } ``` *** ## Identity & Metadata Pass user identity and freeform metadata with every MCP or LLM request. Igris uses this for policy evaluation, audit trails, and trace correlation. ### How It Works The SDK translates identity options into HTTP headers: | SDK Parameter | HTTP Header | Purpose | | ------------- | ------------------ | ---------------------------------------- | | `user` | `X-Igris-User` | End-user identity (opaque string) | | `traceId` | `X-Igris-Trace-Id` | Cross-call correlation | | `metadata` | `X-Igris-Metadata` | JSON key-value pairs for policy matching | All fields are **optional**. If omitted, the gateway still works — just without identity-based policies or user-level audit. ### Trace IDs Trace IDs group multiple tool calls from a single user request: ```typescript theme={null} const traceId = "req_abc123"; const githubConfig = igris.connectHttp("github-prod", { traceId, user: "alice" }); const slackConfig = igris.connectHttp("slack-prod", { traceId, user: "alice" }); ``` **Auto-generation:** If you don't pass a `traceId`, the SDK generates one automatically (`ig_trace_` + random hex). Each `connectHttp()` call gets its own trace ID. ### Metadata for Policies Metadata enables attribute-based access control: ```typescript theme={null} metadata: { role: "developer", team: "engineering", environment: "production", customerId: "cust_456", } ``` Then write policies that match on these fields: ```json theme={null} { "rules": [ { "tool": "delete_*", "action": "deny", "conditions": { "metadata.role": "intern" } }, { "tool": "*", "action": "allow" } ] } ``` **Condition operators:** | Operator | Example | Meaning | | ------------------ | --------------------------------------------- | ------------------ | | String (shorthand) | `"metadata.role": "intern"` | Equals "intern" | | `eq` | `"metadata.role": { "eq": "intern" }` | Equals "intern" | | `neq` | `"metadata.role": { "neq": "admin" }` | Not equals "admin" | | `in` | `"metadata.role": { "in": ["dev", "admin"] }` | Value in list | | `nin` | `"metadata.role": { "nin": ["intern"] }` | Value not in list | If a policy condition references a field that is absent from the request, the rule is skipped — not denied. **Metadata limits:** | Constraint | Limit | | ------------ | ---------------------------------- | | Max size | 4 KB | | Max nesting | 3 levels | | Key format | No dots in key names | | Invalid JSON | Header ignored (treated as absent) | **User-based policies are advisory, not security boundaries.** Any holder of an API key can set any `X-Igris-User` value. The API key is the real trust anchor. *** ## LLM Gateway ### `igris.chat.completions.create(request)` Send a chat completion through the Igris LLM gateway. The `model` field must use `@slug/model` syntax. **Non-streaming:** ```typescript theme={null} const response = await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [ { role: "system", content: "You are a helpful assistant." }, { role: "user", content: "What is the capital of France?" }, ], max_tokens: 200, temperature: 0.7, }); console.log(response.choices[0].message.content); console.log(response.usage.total_tokens); ``` **Streaming:** ```typescript theme={null} const stream = await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [{ role: "user", content: "Write a short story" }], stream: true, }); for await (const chunk of stream) { const delta = chunk.choices[0]?.delta?.content; if (delta) process.stdout.write(delta); } ``` ### `igris.embeddings.create(request)` ```typescript theme={null} const response = await igris.embeddings.create({ model: "@openai-prod/text-embedding-3-small", input: ["Hello world", "Goodbye world"], }); console.log(response.data[0].embedding.length); // e.g. 1536 ``` ### `igris.llmProviders.list()` Fetch all registered LLM provider slugs and metadata: ```typescript theme={null} const providers = await igris.llmProviders.list(); for (const p of providers) { console.log(`${p.slug} → ${p.name} (${p.authStyle})`); } ``` ### `igris.connectLlm(slug, options?)` Build an `LlmConnection` for use with any OpenAI-compatible SDK client: ```typescript theme={null} const conn = igris.connectLlm("openai-prod", { user: "alice@corp.com", traceId: "trace-abc123", metadata: { role: "developer", team: "platform" }, }); // conn.baseUrl → "https://api.igrisecurity.com/llm/openai-prod/v1" // conn.apiKey → your Igris API key // conn.headers → X-Igris-User, X-Igris-Trace-Id, X-Igris-Metadata-* headers ``` *** ## LLM Types ### `ChatMessage` ```typescript theme={null} interface ChatMessage { role: "system" | "user" | "assistant" | "tool"; content: string | Array<{ type: string; text?: string; [key: string]: unknown }>; name?: string; tool_call_id?: string; tool_calls?: unknown[]; reasoning_content?: string; // extended thinking output (e.g. Claude reasoning) } ``` ### `ChatCompletionUsage` ```typescript theme={null} interface ChatCompletionUsage { prompt_tokens: number; completion_tokens: number; total_tokens: number; prompt_tokens_details?: { cached_tokens?: number }; completion_tokens_details?: { reasoning_tokens?: number }; } ``` ### `ChatCompletionResponse` ```typescript theme={null} interface ChatCompletionResponse { id: string; object: "chat.completion"; created: number; model: string; choices: ChatCompletionChoice[]; usage: ChatCompletionUsage; } ``` ### `ChatCompletionChunk` (streaming) ```typescript theme={null} interface ChatCompletionChunk { id: string; object: "chat.completion.chunk"; created: number; model: string; choices: Array<{ index: number; delta: Partial; finish_reason: string | null; }>; usage?: ChatCompletionUsage; // present on the final chunk } ``` *** ## Subpath Adapters Zero-migration integration with existing SDK clients. All adapters are exported from `@igris-security/sdk/adapters/{openai,anthropic,google}`. ### OpenAI adapter ```typescript theme={null} import OpenAI from "openai"; import { Igris } from "@igris-security/sdk"; import { withIgris } from "@igris-security/sdk/adapters/openai"; const igris = new Igris({ apiKey: "ig_..." }); const openai = withIgris( new OpenAI({ apiKey: "placeholder" }), igris, "openai-prod", { user: "alice@corp.com" }, ); // Works exactly like the native OpenAI client await openai.chat.completions.create({ model: "gpt-4o", messages: [...] }); ``` ### Anthropic adapter ```typescript theme={null} import Anthropic from "@anthropic-ai/sdk"; import { withIgris } from "@igris-security/sdk/adapters/anthropic"; const anthropic = withIgris(new Anthropic({ apiKey: "placeholder" }), igris, "anthropic-prod"); await anthropic.messages.create({ model: "claude-3-5-sonnet-20241022", max_tokens: 1024, messages: [{ role: "user", content: "Hello!" }], }); ``` ### Google adapter (stub) Google's SDK does not expose a unified `baseURL` override. The Google adapter returns the `LlmConnection` config for manual use. ```typescript theme={null} import { withIgris } from "@igris-security/sdk/adapters/google"; const conn = withIgris(null, igris, "google-prod"); // conn.baseUrl / conn.apiKey / conn.headers — apply manually ``` *** ## Typed Errors All SDK methods throw typed errors on non-2xx responses: ```typescript theme={null} import { IgrisError, IgrisAuthError, IgrisPolicyDeniedError, IgrisRateLimitError, IgrisConflictError, IgrisValidationError, } from "@igris-security/sdk"; try { await igris.chat.completions.create({ model: "@openai-prod/gpt-4o", messages: [...] }); } catch (err) { if (err instanceof IgrisPolicyDeniedError) { console.error("Blocked by policy:", err.message); // HTTP 403 } else if (err instanceof IgrisRateLimitError) { console.error("Rate limited:", err.message); // HTTP 429 } else if (err instanceof IgrisAuthError) { console.error("Auth error:", err.message); // HTTP 401 } else if (err instanceof IgrisError) { console.error("Gateway error:", err.message, err.response); } } ``` **Error class hierarchy:** | Class | HTTP Status | | ------------------------ | ----------- | | `IgrisAuthError` | 401 | | `IgrisPolicyDeniedError` | 403 | | `IgrisConflictError` | 409 | | `IgrisValidationError` | 422 | | `IgrisRateLimitError` | 429 | | `IgrisError` | any other | *** ## Environment Variables | Variable | Default | Description | | ---------------- | ------------------------------ | ------------------------- | | `IGRIS_BASE_URL` | `https://api.igrisecurity.com` | Override gateway base URL | Call MCP tools programmatically with `igris.mcp`. Create and manage encrypted credential vaults. Generate configs for Claude Desktop, Cursor, and more. REST API endpoints for all Igris resources.