# Authentication Source: https://silmaril.dev/docs/api-reference/authentication Authenticate Firewall API requests with your deployment API key. Send your provisioned API key in the `x-api-key` header and JSON in the request body. Location: protected application service ```bash theme={"theme":"github-light-default"} curl --request POST "$SILMARIL_API_URL" \ --header "x-api-key: $SILMARIL_API_KEY" \ --header "Content-Type: application/json" \ --data '{"text":"Summarize this release note.","hook":"user_input"}' ``` `SILMARIL_API_URL` is your complete classify URL. It already includes `/classify` and any deployment path prefix. Get both environment values through [deployment access](/docs/start/access). ## Keep the key in your service Make API calls from the service that owns model inference, tool execution, or orchestration. Store the key in that service's environment or secret manager. Keep it out of browser bundles and source control. ## Authentication errors The direct Firewall runtime returns `401` when the API key is missing, `403` when the key is invalid or the request's traffic class or forced surface is unauthorized, and `503` when authentication policy is unavailable. Hosted gateways can return `403` for either a missing or invalid key, so these status distinctions are not universal across every deployment path. Check that the key belongs to the deployment you are calling. Authentication error bodies can differ between hosted gateways and self-hosted runtimes. they do not contain a classification verdict. # Classify a batch Source: https://silmaril.dev/docs/api-reference/batch api-reference/generated/batch.json POST /classify Return a Firewall verdict for each independent event in input order. Send independent events in `texts`. For conversation-aware checks, use ordered [single-event requests](/docs/api-reference/classify) with the same `metadata.conversationId`. Batch classification neither reads nor updates conversation history. Use your provisioned `SILMARIL_API_URL` as the full request URL. Inspect `prediction` and optional `governance.action` for every item in `predictions` before consuming it. Location: protected application service ```bash cURL theme={"theme":"github-light-default"} curl --request POST "$SILMARIL_API_URL" \ --header "x-api-key: $SILMARIL_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "texts": [ "Summarize this release note.", "The release adds support for Python 3.12." ], "hooks": ["user_input", "tool_response"], "tool_names": [null, "read_file"], "metadata": [ {"conversationId": "conv-a"}, {"conversationId": "conv-b"} ] }' ``` Location: protected application service ```python Python theme={"theme":"github-light-default"} import json import os from urllib.request import Request, urlopen request = Request( os.environ["SILMARIL_API_URL"], data=json.dumps({ "texts": [ "Summarize this release note.", "The release adds support for Python 3.12.", ], "hooks": ["user_input", "tool_response"], "tool_names": [None, "read_file"], "metadata": [ {"conversationId": "conv-a"}, {"conversationId": "conv-b"}, ], }).encode("utf-8"), headers={ "x-api-key": os.environ["SILMARIL_API_KEY"], "Content-Type": "application/json", }, method="POST", ) with urlopen(request, timeout=10) as response: result = json.load(response) print(result) ``` Location: protected application service ```javascript JavaScript theme={"theme":"github-light-default"} const response = await fetch(process.env.SILMARIL_API_URL, { method: "POST", headers: { "x-api-key": process.env.SILMARIL_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ texts: [ "Summarize this release note.", "The release adds support for Python 3.12.", ], hooks: ["user_input", "tool_response"], tool_names: [null, "read_file"], metadata: [ { conversationId: "conv-a" }, { conversationId: "conv-b" }, ], }), signal: AbortSignal.timeout(10_000), }); if (!response.ok) { throw new Error(`Silmaril API error: ${response.status}`); } const result = await response.json(); console.log(result); ``` Location: Firewall response ```json theme={"theme":"github-light-default"} { "predictions": [ {"prediction": "BENIGN", "score": 0.01, "threshold": 0.5}, {"prediction": "BENIGN", "score": 0.02, "threshold": 0.5} ] } ``` ## Handle the verdict HTTP `200` returns a classification. In Block mode, stop an event when `prediction` is `MALICIOUS` or `governance.action` is `block`. In Warn or Shadow mode, record the would-block decision and continue. Handle request failures with your application's [error policy](/docs/operate/troubleshooting). See [modes and enforcement](/docs/guides/enforcement) for the full decision model. ## Optional review depth The optional top-level `review` field accepts `standard` or `deep`. See [review depth](/docs/guides/review-depth) for requirements, selection behavior, and acknowledgement rules. Review selection applies to the entire batch and is acknowledged on the batch envelope. # Classify an event Source: https://silmaril.dev/docs/api-reference/classify api-reference/generated/classify.json POST /classify Return a Firewall verdict for one complete event. Send one complete event in `text`. For multiple independent events, use [batch classification](/docs/api-reference/batch). Use your provisioned `SILMARIL_API_URL` as the full request URL. Inspect `prediction` and optional `governance.action` before consuming the event. Location: protected application service ```bash cURL theme={"theme":"github-light-default"} curl --request POST "$SILMARIL_API_URL" \ --header "x-api-key: $SILMARIL_API_KEY" \ --header "Content-Type: application/json" \ --data '{ "text": "Summarize this release note.", "hook": "user_input", "metadata": {"conversationId": "conv-123"} }' ``` Location: protected application service ```python Python theme={"theme":"github-light-default"} import json import os from urllib.request import Request, urlopen request = Request( os.environ["SILMARIL_API_URL"], data=json.dumps({ "text": "Summarize this release note.", "hook": "user_input", "metadata": {"conversationId": "conv-123"}, }).encode("utf-8"), headers={ "x-api-key": os.environ["SILMARIL_API_KEY"], "Content-Type": "application/json", }, method="POST", ) with urlopen(request, timeout=10) as response: result = json.load(response) print(result) ``` Location: protected application service ```javascript JavaScript theme={"theme":"github-light-default"} const response = await fetch(process.env.SILMARIL_API_URL, { method: "POST", headers: { "x-api-key": process.env.SILMARIL_API_KEY, "Content-Type": "application/json", }, body: JSON.stringify({ text: "Summarize this release note.", hook: "user_input", metadata: { conversationId: "conv-123" }, }), signal: AbortSignal.timeout(10_000), }); if (!response.ok) { throw new Error(`Silmaril API error: ${response.status}`); } const result = await response.json(); console.log(result); ``` Location: Firewall response ```json theme={"theme":"github-light-default"} { "prediction": "BENIGN", "score": 0.01, "threshold": 0.5, "primary_outcome": "benign" } ``` ## Handle the verdict HTTP `200` returns a classification. In Block mode, stop an event when `prediction` is `MALICIOUS` or `governance.action` is `block`. In Warn or Shadow mode, record the would-block decision and continue. Handle request failures with your application's [error policy](/docs/operate/troubleshooting). See [modes and enforcement](/docs/guides/enforcement) for the full decision model. ## Optional review depth The optional top-level `review` field accepts `standard` or `deep`. See [review depth](/docs/guides/review-depth) for requirements, selection behavior, and acknowledgement rules. # API Reference Source: https://silmaril.dev/docs/api-reference/overview Classify user input, tool calls, tool responses, and model output over HTTP. The Firewall API returns a verdict for each event your application checks. Use it directly over HTTP or through a [Silmaril SDK](/docs/start/quickstart). ## Get access Silmaril provisions an API key and a classify URL for your deployment. [Book a call](https://cal.com/silmaril/30min) to arrange access, then set `SILMARIL_API_KEY` and `SILMARIL_API_URL` in your service environment. `SILMARIL_API_URL` is the complete classify URL, including any deployment path prefix and `/classify`. Use the supplied value as-is. Hosted and self-hosted deployments use the same request format. ## Classify events Send JSON with the `x-api-key` header to `POST /classify`. | Request | Body | Response | | - | - | - | | [Single event](/docs/api-reference/classify) | `text` with optional hook, tool name, and metadata | One verdict | | [Batch](/docs/api-reference/batch) | `texts` with optional arrays of hooks, tool names, and metadata | A `predictions` array in input order | Batch items are independent. For conversation-aware checks, send ordered single-event requests with the same `metadata.conversationId`, waiting for each verdict before sending the next event. ## Handle the verdict A successful HTTP response contains a verdict even when it is malicious. Check `prediction` and the optional `governance.action` before consuming the event. In Block mode, stop a malicious or governance-blocked event. In Warn or Shadow mode, record the would-block decision and continue according to your application policy. `prediction` is the authoritative `BENIGN` or `MALICIOUS` verdict. The score and optional outcome fields add detail. Use [firewall outcomes](/docs/sdk/outcomes) to route malicious results. A non-2xx response or network failure means no verdict was returned. Your application decides whether that boundary fails open or closed. ## AI gateways LiteLLM uses a separate guardrail URL supplied with your deployment. Configure it through the [LiteLLM integration guide](/docs/integrations/litellm). ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Independent enforcement Source: https://silmaril.dev/docs/concepts/independent-enforcement Separate the agent's proposal, the security decision, and the action that follows. A coding agent fixing a build needs to read error reports and follow useful clues. Some of that material may also contain instructions aimed at the agent itself. If the agent accepts a misleading instruction, asking that same agent to decide whether it was misled gives the instruction another chance to influence the answer. Silmaril's principle of **Independence** calls for a security decision made separately from the agent's reasoning. ## Three separate responsibilities The agent proposes an action. A defense evaluates the proposal against the trusted task and applicable policy. The integration controls whether execution proceeds. ```mermaid theme={"theme":"github-light-default"} %%{init: {"theme": "base", "themeVariables": {"background": "#ffffff", "primaryColor": "#ffffff", "primaryTextColor": "#17171c", "primaryBorderColor": "#dedee0", "lineColor": "#666672", "secondaryColor": "#ffffff", "tertiaryColor": "#ffffff", "fontFamily": "Inter, sans-serif"}}}%% flowchart TD A[Agent proposes an action] --> B[Defense evaluates it] T[Trusted task and policy] --> B B --> C[Integration allows or stops execution] classDef decision fill:#ffffff,stroke:#168A7A,color:#17171c,stroke-width:2px class B decision ``` These responsibilities may be connected through an SDK, a gateway, or a supported agent's hooks. What matters is that a proposal cannot approve itself and untrusted content cannot rewrite the rules used to assess it. ## Follow the build repair Imagine the agent finds a diagnostic report that recommends installing a package from an unfamiliar source. It proposes running the command in the project's build environment. The defense needs to consider that proposal alongside the original repair request, the package source, the environment, and the relevant preceding actions. The report's instruction to “ignore security checks” is part of the material being inspected. It is not an instruction for the defense to obey. The outcome may still be to allow a legitimate dependency installation. Independence is about who evaluates the action and which instructions have authority. It does not require rejecting every unfamiliar action. ## Put the decision before execution A security assessment only affects an action when something uses its result. If an application requests a classification and then executes regardless of the response, that request has not established an enforcement boundary. The integration must apply the decision on the path where the action occurs. An action taken through a different, unconnected path falls outside that coverage. For coding agents, available lifecycle events and controls vary by agent. The [agent plugins guide](/docs/integrations/agent-plugins) describes the supported integration points. This is an architectural principle, not a claim that every integration can intercept every action or cannot be bypassed. Verify the boundaries your application actually connects and the behavior it uses when a check cannot complete. [Intent and outcomes](/docs/concepts/intent-and-outcomes) gives the independent check its context. [Learning your application](/docs/concepts/learning-your-application) keeps that context useful as the workflow evolves. Read [The Vital Trifecta](https://silmaril.dev/blog/vital-trifecta) for how these principles fit together. Apply this principle with [modes and enforcement](/docs/guides/enforcement). # Context and intent Source: https://silmaril.dev/docs/concepts/intent-and-outcomes Understand an action through the task, the user, the data, and what happened before it. A coding agent is fixing a failing build. Reading an error log, editing a configuration file, and installing a missing dependency could all help. Uploading that same log to a public issue could expose a credential. The difference is not simply which tool the agent uses. It is whether the action serves the task, stays within the user's authority, and handles the data appropriately. Silmaril calls this contextual understanding **Omniscience**. The name describes a design goal. A defense needs enough relevant context to judge what an action means in the application where it happens. ## Start with the intended task “Fix the build” gives the agent a goal. It does not give every instruction the agent encounters equal authority. Suppose an error report recommends a package and includes an installation command. That recommendation may be useful, mistaken, or deliberately misleading. The report is information to assess. It cannot expand the user's authorization just by telling the agent what to do next. Installing a dependency can be a normal part of the repair. The important questions include where the package comes from, what it will change, and whether that change fits the task. ## Four pieces of context **The task** explains the intended result. Repairing a test configuration is different from changing the production service. **The user** helps establish authority. A developer's permission to work in one repository does not necessarily extend to another team's secrets or infrastructure. **The data** determines what could be exposed. A build log with compiler errors and a build log containing an access token need different handling, even when the upload command is identical. **The preceding actions** explain how the agent reached this point. A package recommendation copied from an untrusted report deserves a different assessment from a dependency already declared by the project. Together, these details connect a proposed action to its likely consequences. They help reveal when an apparently helpful step has drifted away from the intended work. ## Connect context to a decision This principle depends on what an integration can observe. A classification request, gateway, and agent plugin can expose different information and cover different parts of a workflow. Check the relevant integration guide rather than assuming that the defense sees the entire application. [Independent enforcement](/docs/concepts/independent-enforcement) explains where this decision belongs. [Learning your application](/docs/concepts/learning-your-application) explains how its context stays relevant as workflows change. For the broader argument behind all three principles, read [The Vital Trifecta](https://silmaril.dev/blog/vital-trifecta). For request fields and execution order, see [events and conversations](/docs/guides/events). # Learning your application Source: https://silmaril.dev/docs/concepts/learning-your-application A self-improving defense that learns your workflows and adapts as they change. A coding agent's build repair workflow changes over time. The team adopts a new package registry, moves builds into a different environment, or replaces a tool. An action that once looked unusual may become routine. A familiar action may gain access to more sensitive data. **Adaptability** is the ability of a defense to learn from these changes and improve its decisions over time. Silmaril's goal is a self-improving system that learns your application, detects where its understanding falls short, and tests improvements as the application evolves. ## Learn while the application runs Suppose the team adopts a private package registry. The agent now needs to fetch a dependency from it to repair the build. A defense working from an older picture of the application might stop a legitimate installation. An adaptive defense should recognize that its understanding needs to change. It can use the task, the package source, the surrounding actions, and reliable outcome signals to distinguish this new workflow from a malicious package recommendation in an error report. The goal is to make that learning continuous, without requiring someone to write a new rule for every workflow change. Human feedback can help resolve ambiguity and provide useful examples. It is one learning signal within a broader improvement process. ## Improve through an autonomous loop The architectural goal is a loop that observes activity, identifies mistakes or unfamiliar patterns, develops candidate improvements, and evaluates them against both legitimate work and attacks. In the build repair, an improvement should recognize the team's new registry while still detecting a lookalike registry introduced by an untrusted report. Learning that one installation was legitimate should not make every installation acceptable. Evaluation gives the loop a way to measure progress. A candidate should reduce unnecessary interruptions while preserving detection of harmful behavior. Changes that meet the deployment's evaluation and rollout criteria can advance, with subsequent decisions providing evidence for the next iteration. A successful build alone is not enough evidence. The agent could complete the repair after exposing a credential or installing a malicious package. Improvement depends on learning whether the actions served the authorized task, alongside whether the task finished. ## Keep learning connected to enforcement The degree of automation depends on the product and deployment, including the available signals, tuning mechanisms, and rollout controls. These principles do not imply that every SDK call retrains a model or immediately changes enforcement. [Intent and outcomes](/docs/concepts/intent-and-outcomes) supplies the context for learning. [Independent enforcement](/docs/concepts/independent-enforcement) keeps the resulting decisions outside the agent's own reasoning. Together, they form [The Vital Trifecta](https://silmaril.dev/blog/vital-trifecta). Use the [finding review workflow](/docs/operate/findings) to provide evidence about a legitimate or harmful action. # Data handling Source: https://silmaril.dev/docs/deployment/data-handling Understand the event data sent to Firewall and plan evidence access for your deployment. ## Classification data A classification request sends event text and the context supplied by your integration. This can include a hook, tool name, conversation ID, request identity, and governance resource context. Tool responses and generated output can contain sensitive application data. Send the context needed to evaluate the protected boundary. Review event construction before production use and remove credentials that the check does not need. Inspect the [API schema](/docs/api-reference/classify) for supported fields. ## Credentials Keep service keys in the protected service's environment or secret manager. Direct macOS installs save a typed key in the user's login Keychain. Managed profiles supply the connection values and are readable by MDM administrators. Restrict access to completed profiles. Rotate managed URL and key values together. Local app removal leaves server-side credential revocation to your deployment administrator. ## Evidence and access Dashboard findings and captured conversation evidence can contain event content. Restrict evidence access to authorized reviewers and check capture availability when investigating a decision. [Firewall MCP](/docs/integrations/firewall-mcp) provides tenant-scoped evidence tools with separate permissions for restricted detail. ## Deployment planning Confirm the following with Silmaril during setup. Record the agreed values in your deployment plan. | Area | Confirm | | - | - | | Processing | Runtime location and any external model dependencies | | Capture | Which event content and metadata are retained | | Retention | Retention period and deletion process for your agreement | | Access | Operator roles and access to full payloads and traces | | Support | Approved channels for sharing diagnostic evidence | Use the [privacy policy](https://silmaril.dev/privacy) and your service agreement for applicable terms. See [self-hosted deployments](/docs/deployment/self-hosting) for responsibility planning. # Self-hosted deployments Source: https://silmaril.dev/docs/deployment/self-hosting Plan a Silmaril deployment in your cloud account with clear operational ownership. Silmaril works with your team to deploy Firewall near the workload it protects. [Arrange deployment access](/docs/start/access) to agree on the provider, region, networking, and runtime configuration. ## Deployment shape Your application or gateway sends events to the Firewall runtime in your cloud account. The runtime returns a verdict that the connected integration applies. Agree on evidence storage, dashboard access, and external model dependencies during setup. Silmaril supplies the images, ports, startup configuration, and release plan for the agreed deployment. Hosted deployment is also available. ## Supported providers | Provider | Deployment options | | - | - | | AWS | ECS or EKS behind your ingress | | Google Cloud | Cloud Run or GKE | | Azure | Azure Container Apps or AKS | Confirm the supported configuration for your workload during planning. ## Responsibilities | Area | Responsibility | | - | - | | Cloud account, region, ingress, and network policy | Your team, with Silmaril's deployment requirements | | Firewall images and runtime configuration | Supplied by Silmaril for the agreed deployment | | Credentials and endpoint provisioning | Agreed with Silmaril during setup | | Integration mode and application failure policy | Your application or gateway owners | | Release delivery and maintenance | Agreed deployment process and owners | | Evidence storage, retention, and access | Agreed deployment configuration and service terms | ## Before deployment Confirm the target platform and region, ingress authentication, outbound dependencies, secret storage, monitoring ownership, and the release and rollback process. Review [data handling](/docs/deployment/data-handling) with the team responsible for the workload. ## Connect clients | Client | Environment variable | Supplied endpoint | | - | - | - | | [SDK](/docs/start/quickstart) | `SILMARIL_API_URL` | Complete classify URL | | [LiteLLM](/docs/integrations/litellm) | `SILMARIL_GUARDRAIL_URL` | Guardrail URL from the same deployment | ## Verify the deployment [Verify your integration](/docs/start/verify) against the supplied endpoints. Confirm traffic and configured enforcement before expanding production use. Use [troubleshooting](/docs/operate/troubleshooting) for authentication, timeout, and transport failures. # AI gateways Source: https://silmaril.dev/docs/gateways/overview Protect model traffic routed through LiteLLM or Vercel AI Gateway. Connect Silmaril where your application routes model requests. Choose the integration that matches your gateway. > **[LiteLLM](/docs/integrations/litellm)** > > Configure pre-call and post-call guardrails in your LiteLLM proxy. > **[Vercel AI Gateway](/docs/gateways/vercel)** > > Wrap gateway models with Silmaril middleware in your application. ## Choose the right connection | Gateway | Where Silmaril runs | Silmaril endpoint | | - | - | - | | LiteLLM | The proxy calls Silmaril before and after model requests. | Guardrail endpoint | | Vercel AI Gateway | Your application wraps the gateway model with the TypeScript SDK. | Classify endpoint | Silmaril provides your API key and endpoint through [deployment access](/docs/start/access). Gateway credentials are configured separately. ## Verify coverage Start with observation, confirm requests appear in Silmaril, then configure blocking for the boundaries you intend to protect. LiteLLM coverage depends on proxy guardrail configuration. Vercel coverage applies to calls made through the wrapped model. Other callers of the gateway are not automatically protected. For direct model and orchestration integrations, see [Framework SDKs](/docs/integrations/frameworks). ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Vercel AI Gateway Source: https://silmaril.dev/docs/gateways/vercel Check input and generated text for model calls routed through Vercel AI Gateway. Silmaril middleware runs in your application around the gateway model. It inspects calls made through that wrapped model. ## Prerequisites * Install and configure the [TypeScript SDK](/docs/sdk/typescript). * Set `AI_GATEWAY_API_KEY` for your Vercel AI Gateway account, or use Vercel's supported deployment authentication. * Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` to your provisioned Silmaril classify credentials. ## Install the AI SDK Location: protected application service ```bash theme={"theme":"github-light-default"} npm install ai@^6 ``` ## Wrap the gateway model Location: protected application service ```typescript theme={"theme":"github-light-default"} import { Firewall, FirewallBlockedException, HookLabel } from "@silmaril-security/sdk" import { gateway, generateText, wrapLanguageModel } from "ai" const shadowMode = true const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, shadowMode, }) const model = wrapLanguageModel({ model: gateway("anthropic/claude-sonnet-4.6"), middleware: fw.asMiddleware({ scanOutput: false }), }) const response = await generateText({ model, prompt: "Summarize the benefits of request validation.", }) const result = await fw.classify(response.text, { hook: HookLabel.LLM_OUTPUT }) const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block" if (blocked && !shadowMode) { throw new FirewallBlockedException({ score: result.score, threshold: result.threshold, promptText: response.text, hook: HookLabel.LLM_OUTPUT, result, }) } console.log(response.text) ``` Use a model available to your gateway account. The example starts in shadow mode, so would-block decisions do not interrupt model calls. ## Verify and enforce Confirm both input and output classifications appear in Silmaril. The middleware checks input, and the explicit `classify` call checks generated text before it is returned. When ready to enforce, set `shadowMode` to `false` and handle `FirewallBlockedException` before returning a response to the caller. See [TypeScript error handling](/docs/sdk/typescript#errors). Handle gateway authentication, model, and network errors separately. A failed request is not a benign verdict. ## Coverage With AI SDK 6, use the explicit output check above instead of relying on the middleware's `scanOutput` option. This example protects generated text. Classify tool-call arguments before executing a tool, and buffer streaming output until classification finishes when blocking before delivery is required. Every protected call must use the wrapped `model` and the output check. Configuring middleware in one application does not enable a gateway-wide guardrail for other applications or unwrapped calls. * [Vercel AI Gateway documentation](https://vercel.com/docs/ai-gateway) * [Vercel AI SDK and framework integrations](/docs/integrations/frameworks) ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Modes and enforcement Source: https://silmaril.dev/docs/guides/enforcement Choose how a verdict affects execution and how your application handles failed checks. A classification evaluates an event. Your integration applies the result before the protected action or content is consumed. ## Read the decision Check `prediction` and the optional `governance.action`. A malicious prediction or a governance action of `block` is a would-block decision. Governance can block an event whose prediction is `BENIGN`. | Effective mode | Would-block decision | Allowed decision | | - | - | - | | Block | Stop the event | Continue | | Warn | Report the finding and continue | Continue | | Shadow | Record the finding and continue | Continue | The returned score and threshold are diagnostic fields. Route on the verdict and applicable governance decision. Use [outcomes](/docs/sdk/outcomes) for additional malicious-result detail. ## Apply the result | Integration | Block behavior | | - | - | | TypeScript direct `classify()` | Returns the verdict. Your code stops the event | | Python, Go, and Java direct calls | Raise or return a blocking error in effective Block mode | | LangChain callbacks | Raise a blocking exception in effective Block mode | | Deep Agents | Replace denied content with safe messages in effective Block mode | | Vercel AI SDK | Input middleware can block. Classify generated text before returning it | | LiteLLM | Uses the configured guardrail response behavior | Choose Shadow or Warn explicitly when observing. Python and Go use a Block fallback when the response and local configuration omit a mode. Framework adapters resolve their own override first, then the client setting, then an optional response mode, then Block. Current classify responses omit `mode`. SDK results preserve explicit local choices. Java examples choose a mode explicitly. ## Handle a failed check An API, network, timeout, or cancellation error leaves the event without a verdict. Choose whether that boundary stops or continues when classification fails. Deep Agents allows execution on classification errors by default. Set `failOpen: false` in TypeScript or `fail_open: False` in Python when execution requires a completed check. The LiteLLM starter uses `unreachable_fallback: fail_open`. Check each [integration guide](/docs/integrations/frameworks) for its error policy. ## Enable blocking Start with observation and [verify connected boundaries](/docs/start/verify). Review would-block findings, choose the error policy, then enable Block on the intended boundary. Repeat verification after changing the mode. For streaming output, buffer and classify content before delivery when blocking must prevent exposure. Checks cover the paths connected to Silmaril. Route every protected operation through those paths. # Events and conversations Source: https://silmaril.dev/docs/guides/events Label execution boundaries and preserve ordered context across an agent sequence. Send one complete event at each boundary you protect. Apply its verdict before the application consumes that event or performs the proposed action. ## Choose a hook | Boundary | API hook | Check before | | - | - | - | | User input | `user_input` | The model consumes the input | | Tool call | `tool_call` | The tool executes | | Tool response | `tool_response` | The model consumes the tool output | | Model output | `llm_output` | The application returns or consumes generated content | Supply the tool name for tool calls and tool responses. SDKs expose typed hook labels for these values. ## Preserve conversation order Use a caller-owned `metadata.conversationId` for one agent sequence. Send ordered individual `classify()` calls with that ID, waiting for and handling each verdict or error before sending the next event. Each SDK event also receives a `metadata.silmaril.request_id`. Use the request ID to correlate a classification and the conversation ID to group the sequence. Let the SDK manage request identity. Location: protected TypeScript service with an initialized `fw` client ```typescript theme={"theme":"github-light-default"} import { HookLabel } from "@silmaril-security/sdk" const metadata = { conversationId: "build-repair-123" } const inputVerdict = await fw.classify(userInput, { hook: HookLabel.USER_INPUT, metadata, }) ``` Import `HookLabel` from `@silmaril-security/sdk`. Handle `inputVerdict` according to the [effective mode](/docs/guides/enforcement) before continuing the sequence. ## Batch independent events Use batch classification for independent inputs. Responses preserve input order. Batch calls preserve per-item metadata while leaving conversation history unchanged. The same conversation ID on batch items leaves those items independent. Use concurrency for independent events or different conversations. Keep events within one conversation sequential. ## Field names | API field | TypeScript option | Python option | Go option | | - | - | - | - | | `hook` | `hook` | `hook` | `WithHook` | | `tool_name` | `toolName` | `tool_name` | `WithToolName` | | `metadata` | `metadata` | `metadata` | `WithMetadata` | See the [single-event API](/docs/api-reference/classify) and [SDK reference](/docs/sdk/typescript) for complete request options. # Review depth Source: https://silmaril.dev/docs/guides/review-depth Choose standard or deep review for a classification request. ## Choose a review level | Selection | Behavior | | - | - | | Omitted | Inherit the deployment's configured threat-judge setting | | `standard` | Disable the optional large-model threat-judge stage for this request | | `deep` | Enable the configured judge for eligible threat candidates | Deep review follows server-owned eligibility, sampling, uncertainty, and failure handling. The server controls the judge model, prompt, and timeout. Review depth is independent of [enforcement mode](/docs/guides/enforcement). `deep` requires an active Cascade runtime with a supported threat judge configured for the selected deployment and surface. Otherwise, the server returns HTTP `400` before inference, with no verdict. It overrides the judge's `enabled: false` default. `standard` works without a configured judge. ## SDK compatibility | Client | Minimum version | Selection | | - | - | - | | [TypeScript](/docs/sdk/typescript#review-depth) | 0.7.8 | Client `review` or per-call `review` | | [Python](/docs/sdk/python#review-depth) | 0.7.5 | Client `review` or per-call `review` | | [Go](/docs/sdk/go#review-depth) | v0.7.5 | `Options.Review`, `WithReview`, or `WithBatchReview` | | [LiteLLM](/docs/integrations/litellm#optional-review-selection) | Guardrail API | `additional_provider_specific_params.review` | An explicit per-call SDK selection wins the client default. With neither selected, the request omits the wire field. Adapters inherit the client setting. ## Validate the response Use the exact strings `standard` and `deep`. Invalid wire values return HTTP `400`. An explicit selection requires the same exact value acknowledged at the response's top-level `review` field. For batches, the acknowledgement belongs to the batch envelope. LiteLLM guardrail responses acknowledge their selection in `silmaril_review`. The acknowledgement identifies the applied setting. Eligible events may still complete without a judge invocation. SDKs check acknowledgement before returning results, running callbacks, or applying block handling. Missing or mismatched acknowledgement produces `ReviewCompatibilityError`. Earlier transient attempts follow the normal retry policy. # Overview Source: https://silmaril.dev/docs/index Protect AI applications and agents at the points where they receive input, call tools, and produce output. # Silmaril Protect applications and agents at input, tool, and output boundaries. Choose a setup below, then verify protection. ## Choose your path [Protect an AI application Add a language SDK and classify your first request at the model or tool boundary.](/docs/start/quickstart) [Deploy on macOS Protect local coding agents with a direct install or a managed rollout through your MDM.](/docs/macos) [Secure an AI gateway Inspect model traffic through LiteLLM guardrails or Vercel AI Gateway middleware.](/docs/gateways/overview) ## Classify a request Location: protected application service ```typescript TypeScript theme={"theme":"github-light-default"} import { Firewall, HookLabel } from "@silmaril-security/sdk" const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, }) const userInput = "Summarize this release note." const result = await fw.classify(userInput, { hook: HookLabel.USER_INPUT, }) console.log(result.prediction) ``` ```python Python theme={"theme":"github-light-default"} import os from silmaril_security.sdk import Firewall, HookLabel fw = Firewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], ) user_input = "Summarize this release note." result = fw.classify( user_input, hook=HookLabel.USER_INPUT, ) print(result.prediction) ``` ```go Go theme={"theme":"github-light-default"} package main import ( "context" "log" "os" "github.com/Silmaril-Security/sdk-go/firewall" ) func main() { fw, err := firewall.New(firewall.Options{ APIKey: os.Getenv("SILMARIL_API_KEY"), APIURL: os.Getenv("SILMARIL_API_URL"), }) if err != nil { log.Fatal(err) } result, err := fw.Classify(context.Background(), "Summarize this release note.", firewall.WithHook(firewall.HookUserInput), ) if err != nil { log.Fatal(err) } log.Println(result.Prediction) } ``` ```java Java theme={"theme":"github-light-default"} import dev.silmaril.security.Firewall; import dev.silmaril.security.HookLabel; import dev.silmaril.security.ClassifyOptions; public class Example { public static void main(String[] args) throws Exception { var fw = Firewall.builder() .apiKey(System.getenv("SILMARIL_API_KEY")) .apiUrl(System.getenv("SILMARIL_API_URL")) .build(); var result = fw.classify( "Summarize this release note.", ClassifyOptions.builder() .hook(HookLabel.USER_INPUT) .build() ); System.out.println(result.prediction()); } } ``` Start with the [quick start](/docs/start/quickstart). Apply the returned decision with your chosen [enforcement mode](/docs/guides/enforcement). ## Language SDKs [ TypeScriptSDK 0.7.9](/docs/sdk/typescript#install) [ PythonSDK 0.7.5](/docs/sdk/python#install) [ GoSDK 0.7.5](/docs/sdk/go#install) [ JavaSDK 0.7.0](/docs/sdk/java#install) Java setup requires the artifact repository and runtime supplied by Silmaril. [Get access](/docs/start/access) for your deployment. ## Integrate and operate [Coding agentsSet up Codex, Claude Code, Pi, and other supported agent plugins.](/docs/integrations/agent-plugins) [Framework SDKsAdd protection to Vercel AI SDK, LangChain, or LangGraph.](/docs/integrations/frameworks) [Verify protectionConfirm traffic, connected boundaries, and blocking behavior.](/docs/start/verify) [Firewall MCPLet your MCP client investigate findings and traffic.](/docs/integrations/firewall-mcp) [Self-hostingPlan a deployment in your cloud account with Silmaril.](/docs/deployment/self-hosting) # Agent plugins Source: https://silmaril.dev/docs/integrations/agent-plugins Secure Codex, Claude Code, OpenClaw, Hermes, OpenCode, and Pi at the coding-agent boundary. ## Choose your agent Arrange [deployment access](/docs/start/access), then follow the setup for your agent. | Agent | Installation | Setup | | - | - | - | | Codex | Access supplied by Silmaril | [Arrange installation](#codex-and-claude-code) | | Claude Code | Access supplied by Silmaril | [Arrange installation](#codex-and-claude-code) | | Pi | Published package | [Install Pi plugin](#pi) | | OpenClaw | Repository instructions | [Setup guide](https://github.com/Silmaril-Security/FirewallPlugin) | | Hermes | Repository instructions | [Setup guide](https://github.com/Silmaril-Security/HermesFirewallPlugin) | | OpenCode | Repository instructions | [Setup guide](https://github.com/Silmaril-Security/OpenCodeFirewallPlugin) | For each agent, confirm its supported platform, lifecycle hooks, and blocking controls in its setup guide. For managed Macs, let the [macOS app](/docs/macos) prepare supported integrations. Coordinate manual plugin setup with the Mac's managed configuration. ## Codex and Claude Code [Contact Silmaril](https://cal.com/silmaril/30min) for plugin access and installation instructions. For managed protection on Macs, use the [macOS app](/docs/macos). ## Pi Install the published package. Location: workstation terminal with your coding agent installed ```sh theme={"theme":"github-light-default"} pi install npm:@silmaril-security/pi-firewall-plugin@0.2.8 ``` Or install the matching Git release. Location: workstation terminal with your coding agent installed ```sh theme={"theme":"github-light-default"} pi install git:github.com/Silmaril-Security/PiFirewallPlugin@v0.2.8 ``` Follow the [Pi setup guide](https://github.com/Silmaril-Security/PiFirewallPlugin#configure) to configure deployment access. Each input callback receives a fresh request identity. Retries of that classification keep the same identity. ## Verify the plugin Start a new agent session and [verify its activity](/docs/start/verify) in your Firewall. Check the hook names for the actions you intend to protect. Use a controlled test agreed with Silmaril to confirm blocking on supported boundaries. ## Next steps To protect a service you run, start with the [quick start](/docs/start/quickstart). To manage protection for coding agents across Macs, use [Silmaril for macOS](/docs/macos). To review findings from an MCP client, connect [Firewall MCP](/docs/integrations/firewall-mcp). # Deep Agents Source: https://silmaril.dev/docs/integrations/deep-agents Protect root agents, tools, and subagents in TypeScript or Python. ## Before you begin Initialize the [TypeScript](/docs/sdk/typescript#initialize) or [Python](/docs/sdk/python#initialize) client as `fw`. Supply your model, tools, and a conversation ID for the agent sequence. Choose Shadow or Warn explicitly for observation. Effective Block mode replaces denied tool interactions and model content with safe messages. Classification failures allow execution by default. Set `failOpen: false` in TypeScript or `fail_open: False` in Python when a successful classification is required. ## Create a protected agent Deep Agents creates its own subagent graphs. Use the protected constructor to cover the root, its general-purpose subagent, and declarative subagents. In the examples below, supply your initialized `fw`, configured chat `model`, `tools` array or list, and a caller-generated `conversationId` or `conversation_id` for this agent sequence. **TypeScript** Location: your service project directory ```bash theme={"theme":"github-light-default"} npm install @silmaril-security/sdk@0.7.9 deepagents langchain @langchain/core @langchain/langgraph ``` Location: protected agent application ```typescript theme={"theme":"github-light-default"} import { createProtectedDeepAgent } from "@silmaril-security/sdk/adapters/deepagents" const agent = createProtectedDeepAgent(fw, { model, tools, subagents: [{ name: "research", description: "Research safely" }], silmaril: { conversationId }, }) ``` For a compiled subagent, use the verified factory before creating the parent: Location: protected agent application ```typescript theme={"theme":"github-light-default"} import { createProtectedCompiledSubagent } from "@silmaril-security/sdk/adapters/deepagents" const review = createProtectedCompiledSubagent(fw, { name: "review", description: "Review results", model, }) const agent = createProtectedDeepAgent(fw, { model, protectedCompiledSubagents: [review], }) ``` **Python** Python DeepAgents requires **Python 3.11 or later**. The base Python SDK also supports Python 3.10, but its DeepAgents extra does not. Location: your service's Python environment ```bash theme={"theme":"github-light-default"} pip install "silmaril-security-sdk[deepagents]==0.7.5" ``` Location: protected agent application ```python theme={"theme":"github-light-default"} from silmaril_security.sdk.deepagents import create_protected_deep_agent agent = create_protected_deep_agent( fw, model=model, tools=tools, subagents=[{"name": "research", "description": "Research safely"}], middleware_options={"conversation_id": conversation_id}, ) ``` For a compiled subagent, use the verified factory before creating the parent: Location: protected agent application ```python theme={"theme":"github-light-default"} from silmaril_security.sdk.deepagents import create_protected_compiled_subagent review = create_protected_compiled_subagent( fw, name="review", description="Review results", model=model, ) agent = create_protected_deep_agent( fw, model=model, protected_compiled_subagents=[review], ) ``` For a compiled subagent, use `createProtectedCompiledSubagent` in TypeScript or `create_protected_compiled_subagent` in Python. These helpers install Silmaril middleware before compiling the graph. Pass the returned spec through `protectedCompiledSubagents` or `protected_compiled_subagents`, respectively. The parent constructor verifies the exact graph and Firewall client and rejects an arbitrary compiled runnable. Root custom middleware does not automatically reach every subagent. Classification errors allow execution by default. Set `silmaril: { failOpen: false }` in TypeScript or `middleware_options={"fail_open": False}` in Python when execution requires a successful classification. The middleware checks user input before model use, tool calls before execution, tool results before the next model call, and non-streamed model output before the graph consumes it. In Block mode, denied tool calls and results become a fixed safe tool message with the original call ID. The agent may choose an allowed alternative. Repeated denials end with a fixed safe response. Denied model output is replaced before it enters graph messages. Shadow and Warn report decisions without replacing content. Already emitted streaming text cannot be recalled. TypeScript SDK 0.7.3 and later apply the repeated-denial cap only in effective Block mode. Genuine earlier Block denials remain counted, but do not halt Warn or Shadow continuation. ## Verify coverage [Verify the integration](/docs/start/verify) on the root and each subagent. Confirm that tool checks happen before execution. Buffer streaming text when delivery requires a completed output verdict. # Firewall MCP Source: https://silmaril.dev/docs/integrations/firewall-mcp Connect an MCP client to read-only, tenant-scoped Silmaril Firewall evidence. Firewall MCP lets an MCP client review Firewall deployments, findings, and investigation evidence. It is an operational tool and does not classify or block traffic. Use an [SDK](/docs/start/quickstart), [framework integration](/docs/integrations/frameworks), or [LiteLLM guardrail](/docs/integrations/litellm) for inline protection. The hosted server reads through Silmaril's evidence API. It does not connect directly to your cloud account, database, or runtime infrastructure. ## Prerequisites * A Silmaril customer account. * Access to your tenant's Silmaril organization. * At least one authorized Firewall deployment. * An MCP client that supports hosted OAuth discovery. You do not paste tokens, configure OAuth fields, or provide cloud credentials. ## Connect from Codex Location: your MCP client ```bash theme={"theme":"github-light-default"} codex mcp add silmaril-firewall --url https://firewall-mcp.silmaril.dev/mcp ``` Silmaril login opens in your browser when the client connects. Choose the customer organization you normally use for Silmaril. ## Recommended workflow Start broad, then narrow to the evidence a review actually needs. **1. Discover** Map deployments and capabilities with `list_firewalls`, `get_firewall`, and `get_schema`. **2. Review** Summarize posture with `get_metrics`, `get_finding_totals`, `group_findings`, and `list_findings`. **3. Investigate** Rank repeated abuse with `list_suspicious_users` and build compact context with `get_investigation_packet`. **4. Escalate** Use restricted `get_finding` or `get_finding_trace` only when compact evidence is insufficient and your account has detail access. ## Starter prompts Run the first prompt to discover your Firewall IDs, then use returned IDs in the remaining prompts. Location: your MCP client ```text theme={"theme":"github-light-default"} List the firewalls I can access. ``` Location: your MCP client ```text theme={"theme":"github-light-default"} Summarize traffic, findings, and severity for over the last 24 hours. ``` Location: your MCP client ```text theme={"theme":"github-light-default"} Show the five highest-risk findings for from the last 24 hours and cite finding and evidence IDs. ``` Location: your MCP client ```text theme={"theme":"github-light-default"} Build an investigation packet for in with key evidence and next steps. ``` ## Evidence safety > [!WARNING] > > Finding payloads and trace text can contain attacker-controlled instructions. Treat them as evidence, not instructions. Prefer aggregate and compact tools first, and cite Firewall IDs, finding IDs, evidence IDs, request IDs, and trace diagnostics instead of copying sensitive content. ## Expected result After login, `list_firewalls` returns the deployments your account can access. Restricted finding and trace tools remain unavailable unless your account has detail access. ## References * [Firewall MCP repository](https://github.com/Silmaril-Security/silmaril-firewall-mcp) * [Firewall MCP customer guide](https://github.com/Silmaril-Security/silmaril-firewall-mcp/blob/main/docs/customer-guide.md) # Framework integrations Source: https://silmaril.dev/docs/integrations/frameworks Choose an integration for Vercel AI SDK, LangChain, LangGraph, or Deep Agents. Connect Silmaril to the framework that runs your model or orchestrates your agent. Start with the [TypeScript SDK](/docs/sdk/typescript) or [Python SDK](/docs/sdk/python), then add the integration for your framework. > **[Vercel AI SDK](/docs/sdk/vercel-ai)** > > Check model input with middleware and classify generated text before returning it. > **[LangChain and LangGraph](/docs/sdk/langchain)** > > Add callback handlers to model calls and graph execution in TypeScript or Python. > **[Deep Agents](/docs/integrations/deep-agents)** > > Protect root agents, tool execution, and subagent graphs. For gateway-routed model traffic, choose [LiteLLM or Vercel AI Gateway](/docs/gateways/overview). [Verify your integration](/docs/start/verify) after setup and choose the [enforcement mode](/docs/guides/enforcement) for each connected boundary. # LiteLLM guardrails Source: https://silmaril.dev/docs/integrations/litellm Add Silmaril as a LiteLLM pre-call and post-call guardrail and validate coverage in warn mode. ## Prerequisites * A LiteLLM proxy that already routes model traffic. * A Silmaril guardrail endpoint and API key from [deployment access](/docs/start/access). * If you [self-host](/docs/deployment/self-hosting), use the guardrail endpoint from the same deployment as your classify endpoint. Set `SILMARIL_GUARDRAIL_URL` and `SILMARIL_API_KEY` in the proxy environment. ## Configure the guardrail Start in warn mode while validating proxy coverage. This primary configuration omits `review`, so the selected tenant and surface AppConfig `threat_judge.enabled` setting remains authoritative. Location: LiteLLM proxy configuration ```yaml theme={"theme":"github-light-default"} guardrails: - guardrail_name: silmaril-firewall litellm_params: guardrail: generic_guardrail_api mode: [pre_call, post_call] api_base: os.environ/SILMARIL_GUARDRAIL_URL headers: x-api-key: os.environ/SILMARIL_API_KEY default_on: true unreachable_fallback: fail_open additional_provider_specific_params: on_error: warn ``` This configuration applies the guardrail by default. An enforcement configuration is not included here. Agree on one with Silmaril before enabling blocking. ## Expected result LiteLLM calls Silmaril before and after the model call. Would-block traffic returns warning metadata while the proxy call continues. If the guardrail cannot be reached, `unreachable_fallback: fail_open` also allows the call to continue. ## Optional review selection Keep `review` omitted to inherit AppConfig as shown above. Select `standard` or `deep` with `additional_provider_specific_params.review`. Location: optional LiteLLM provider parameters ```yaml theme={"theme":"github-light-default"} additional_provider_specific_params: mode: shadow review: deep on_error: warn ``` The provider-specific `mode: shadow` explicitly selects Shadow response mode and takes precedence over the `on_error: warn` response-mode fallback. Review selection and response mode are independent. `standard` disables the optional large-model threat-judge stage and is valid without a configured judge. `deep` requires an active Cascade runtime with a supported judge configured for the selected deployment and surface. Otherwise Silmaril returns HTTP `400` before inference. The judge may be configured with `enabled: false` because an authenticated caller's explicit `deep` selection overrides that default. Explicit `null`, wrong types, whitespace or case variants, and other strings are invalid. Deep review is a second opinion on eligible Qwen threat candidates, not a review of every event. Existing eligibility, sampling, uncertainty handling, and failures remain in effect, so selecting `deep` does not guarantee a judge invocation or completion. Model, prompt, and timeout settings are server-owned. A successful explicit selection is acknowledged by the same exact string in top-level `silmaril_review`. This reports the applied setting, not whether the optional judge ran or completed. Omitted review needs no acknowledgement and preserves the legacy response shape. Location: Silmaril LiteLLM guardrail response ```json theme={"theme":"github-light-default"} { "action": "NONE", "silmaril_mode": "shadow", "silmaril_review": "deep" } ``` For operators, AppConfig `enabled: false` is an overridable default: an authenticated caller may explicitly request `deep`. Remove the server judge configuration when callers must not be able to select deep review. ## References * [LiteLLM AI Gateway](https://docs.litellm.ai/docs/proxy/quick_start) * [LiteLLM Guardrails Quick Start](https://docs.litellm.ai/docs/proxy/guardrails/quick_start) * [LiteLLM Generic Guardrail API](https://docs.litellm.ai/docs/adding_provider/generic_guardrail_api) ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Silmaril for macOS Source: https://silmaril.dev/docs/macos Protect local coding agents on one Mac or deploy through Jamf Pro, Iru, or Fleet. Silmaril discovers supported local coding agents, prepares their protection, inventories integrations, and shows current protection activity. ## Choose a path > **[Install on one Mac](/docs/macos/install)** > > Use the DMG and enter the connection values on the Mac. > **[Deploy through MDM](/docs/macos/managed-deployment)** > > Complete the managed profile, then deploy with Jamf Pro, Iru, or Fleet. For managed Macs, [prepare a rollout](/docs/macos/managed-deployment) within your existing MDM configuration. ## Release and coverage Stable 0.3.22 (build 1077) requires macOS 14 or later. This release is Guardian Disabled. It runs without Silmaril's privileged background service or system extensions, so macOS does not ask for Full Disk Access, system-extension, or background-service approval. File and network extension audit collection is not available in this variant. Nightly 0.3.37 (build 1119), Guardian Disabled. [Nightly DMG](https://downloads.silmaril.dev/macos/releases/0.3.37/1119/Silmaril-0.3.37-1119-guardian-disabled.dmg) for interactive installation. [Nightly PKG](https://downloads.silmaril.dev/macos/releases/0.3.37/1119/Silmaril-0.3.37-1119-guardian-disabled.pkg) for an explicitly selected managed pilot. See [Nightly installation](/docs/macos/install#nightly) before choosing that channel. ## Next steps [Verify protection](/docs/macos/verify) on each pilot Mac, including its existing baseline, before expanding a rollout. Add optional policy with [managed settings](/docs/macos/settings), resolve policy, connection, and restart states with [troubleshooting](/docs/macos/troubleshooting), and remove the app and its integrations with [uninstall](/docs/macos/uninstall). # Fleet Source: https://silmaril.dev/docs/macos/fleet Deploy Silmaril with a configuration profile and a custom software package. Deploy Stable 0.3.22 (build 1077) as a configuration profile and a custom software package assigned to the same hosts. ## Prepare the pilot Have administrator access, an enrolled Mac running macOS 14 or later, and the vendor management agent where required. Download the Stable 0.3.22 (1077) Guardian Disabled PKG and complete the required configuration before upload. The completed `Silmaril-Managed.mobileconfig` has passed validation, and the package is the [Stable PKG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.pkg). Complete the [shared profile preparation](/docs/macos/managed-deployment) before adding the Fleet profile or package. ## Install the profile Stay in the Mac's current fleet. Open **Controls > OS settings > Configuration profiles**, choose **Add profile**, and upload `Silmaril-Managed.mobileconfig`. Fleet signs uploaded profiles. Confirm supported targeting for the intended hosts before upload. Label-based profile targeting requires Fleet Premium. Keep existing baseline controls and assignments in place. If scoped targeting is unavailable, arrange a supported rollout with your administrator before uploading. For replacement, edit the existing profile row. Keep PayloadIdentifier `dev.silmaril.mdm.managed`, PayloadDisplayName `Silmaril Managed`, and every supplied UUID and permission payload. Confirm the profile is installed on the pilot hosts before distributing the package. ## Install the package With Fleet software management and scripts enabled, open **Software** in the current fleet, then **Add software > Custom package**. Upload the Stable PKG and match its targets to the profile. Use matching software labels where supported. Add software within the intended fleet. For a pilot, open Hosts > the Mac > Software > Library, find Silmaril under Available for install, and select Install. Check Status or device Activity. Use the linked automatic-install policy workflow for broader rollout. Use Fleet's supported software/package status for deployment, then verify version 0.3.22, build 1077, and Guardian Disabled inside Silmaril. ## Verify the pilot Open Silmaril and [verify its identity, managed connection, and fresh Protection activity](/docs/macos/verify). Check the intended Guardian Disabled build. Resolve Restart required by fully quitting and reopening the affected agent, then starting a new session. ## Expand the rollout For each later cohort, confirm profile installation before assigning or installing the package. Adding hosts to a label that already installs software can deliver both at once. Repeat the profile-first staging and endpoint verification for every cohort. ## Update or replace policy Deploy the intended Guardian Disabled version and update app/package metadata together. Replace the profile in its existing row with the same supplied identity and permissions. Removing managed keys restores saved local preferences. Verify a pilot before expanding the update. ## Uninstall Pause every Silmaril software install for the target Macs. Keep their fleet and baseline controls assigned. Run the shared [MDM uninstall script](/docs/macos/uninstall#remove-through-mdm) as root with the affected user signed in. Retain its JSON receipt and exit code, then follow shared result handling. Upload the script under **Controls > Scripts**, select the Mac under **Hosts**, then **Actions > Run script**. Use a manual run and inspect the host activity feed before retrying. Complete reported permission follow-up and remove only the Silmaril profile when policy should stop applying. ## Vendor references * [Custom OS settings](https://fleetdm.com/guides/custom-os-settings) * [Managing labels in Fleet](https://fleetdm.com/guides/managing-labels-in-fleet) * [Deploy software packages](https://fleetdm.com/guides/deploy-software-packages) * [Run scripts](https://fleetdm.com/guides/scripts) * [Automatic software installation](https://fleetdm.com/guides/automatic-software-install-in-fleet) # Install Source: https://silmaril.dev/docs/macos/install Set up Silmaril on one Mac using the Stable DMG. For organization-managed Macs, use [managed deployment](/docs/macos/managed-deployment). You need a Mac running macOS 14 or later and the Firewall URL and API key from your [deployment access](/docs/start/access). Stable 0.3.22 (build 1077) is Guardian Disabled and installs no privileged background service. Contact support before replacing an incompatible installed variant. ## Install from the DMG **1. Download** Download the [Stable DMG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.dmg) for this interactive install. The [Stable PKG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.pkg) is the MDM artifact. Both are 0.3.22 (1077), Guardian Disabled. **Expected result**\\ The file is the Stable Guardian Disabled DMG for that version and build. **If this differs**\\ Stop. This hand install uses that DMG. The PKG belongs to [managed deployment](/docs/macos/managed-deployment). **2. Install and open** Drag Silmaril to Applications and open it. **Expected result**\\ Silmaril opens from `/Applications/Silmaril.app`. **If this differs**\\ Confirm the app is in Applications, then open that copy. An app that does not open is not yet ready to [verify](/docs/macos/verify). **3. Connect and protect** Enter the Firewall URL and API key supplied by Silmaril, save the connection, prepare supported agents, then start a new agent session and confirm current Protection activity. A key typed here is stored in that user's login Keychain. **Expected result**\\ The connection is saved. When the first-run connection screen is displayed, it reports Firewall connection verified. A new supported-agent session shows current activity in Protection. **If this differs**\\ Recheck the URL and key Silmaril supplied. Missing Protection activity means protection is not confirmed. Dragging the app into Applications, or saving a connection, does not by itself establish protection. Continue with [Verify the endpoint](/docs/macos/verify). ## Nightly channel Nightly 0.3.37 (build 1119), Guardian Disabled. [Nightly DMG](https://downloads.silmaril.dev/macos/releases/0.3.37/1119/Silmaril-0.3.37-1119-guardian-disabled.dmg) for interactive installation. [Nightly PKG](https://downloads.silmaril.dev/macos/releases/0.3.37/1119/Silmaril-0.3.37-1119-guardian-disabled.pkg) for an explicitly selected managed pilot. For an interactive Nightly install, use the DMG and follow the install/open and connection steps above. Check the Nightly identity in [Verify protection](/docs/macos/verify). Nightly publication does not establish Stable qualification. Selecting Nightly as the app-owned update channel changes where the in-app updater looks for new builds. Under MDM update authority, the channel setting does not install builds. Your MDM deploys each PKG. # Iru Source: https://silmaril.dev/docs/macos/iru Deploy Silmaril with a System Channel profile and an audit-and-enforce Custom App. Deploy Stable 0.3.22 (build 1077) with a System Channel Custom Profile and an audit-and-enforce Mac Custom App. Iru was formerly Kandji. ## Prepare the pilot Have administrator access, an enrolled Mac running macOS 14 or later, and the vendor management agent where required. Download the Stable 0.3.22 (1077) Guardian Disabled PKG and complete the required configuration before upload. The completed `Silmaril-Managed.mobileconfig` has passed validation, and the package is the [Stable PKG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.pkg). Complete the [shared profile preparation](/docs/macos/managed-deployment) before creating Library Items. ## Install the profile Create a **Custom Profile** Library Item, upload `Silmaril-Managed.mobileconfig`, and choose **System Channel**. Keep the Mac on its existing base Blueprint. In that Blueprint's **Assignment Map**, put the Silmaril profile behind a condition for a dedicated tag such as `silmaril`. Preserve baseline Library Items outside the condition. Update the existing Library Item when the profile identity already exists. Preserve supplied identifiers, UUIDs, and permission payloads. Keep the Mac Custom App unassigned for the initial pilot. In the profile's **Status** and the device's **Activity**, confirm **InstallProfile** completed before assigning the app. ## Install the package Create a Mac Custom App with Package Type Installer Package, upload the 0.3.22 (1077) PKG, choose Audit and enforce, leave restart disabled, and use the Guardian Disabled audit and console-user post-install scripts below. Exit 0 means a matching Guardian Disabled build at or above minimum build 1077 is installed. A nonzero audit result triggers enforcement. The minimum-build check accepts 1077 or newer and prevents downgrade. Assign it to the same Assignment Map condition as the profile, and only after that profile install is confirmed on those Macs. Audit exits 0 for bundle ID `dev.silmaril.SilmarilMacOS`, Guardian Disabled, and build 1077 or newer. The post-install script opens Silmaril for a signed-in console user above UID 500. With no signed-in user, it exits 0 and the profile opens Silmaril at the next login. A nonzero audit causes Iru enforcement to run. Keep restart disabled. Complete endpoint verification after the package installs. ## Verify the pilot Open Silmaril and [verify its identity, managed connection, and fresh Protection activity](/docs/macos/verify). Resolve Restart required by fully quitting and reopening the affected agent, then starting a new session. ## Expand the rollout Once the Custom App is assigned behind the `silmaril` condition, hold app delivery for each later cohort before applying that tag. **1. Hold the app** In Assignment Map, select the Silmaril Mac Custom App. Expand **Manual device exclusions**, choose **Add device**, add each new Mac, and **Save**. Confirm the app is excluded using the map or device lookup. Leave the profile and baseline items assigned. **2. Deliver the profile** Add the `silmaril` tag to that cohort. Confirm **InstallProfile** completed on every Mac while the app remains excluded. **3. Release the app** Remove only those Macs from the Custom App exclusions with the **X** control and **Save**. Preserve exclusions for other devices. Verify the new cohort before expanding again. Keep the same tag condition on the existing base Blueprint. A shared condition assigns the profile and app together, so repeat this hold for every cohort. Device exclusions control delivery. Use the removal workflow to uninstall an existing app. ## Update or replace policy Deploy the intended Guardian Disabled version and update the app/package metadata together. Replace the existing profile while preserving supplied identifiers, UUIDs, and permissions. Removing managed keys restores saved local preferences. Verify the updated pilot, then expand with the cohort hold above. ## Uninstall Pause every Silmaril Mac Custom App assignment for the target Macs. Preserve the base Blueprint and baseline Library Items. Run the shared [MDM uninstall script](/docs/macos/uninstall#remove-through-mdm) as root with the affected user signed in. Retain the JSON receipt and exit code, then follow the shared result handling. Paste the script into the **Audit Script** field of a **Custom Script** Library Item. Keep **Restart** disabled and leave the remediation script empty. Inspect its output. **Install once per device** retries nonzero results. If an exit 2 receipt confirms app removal and Guardian absence with only permissions remaining, unassign the removal item and complete the permission follow-up. Keep the receipt, since the bundled helper is removed with the app. Remove only the Silmaril profile when policy should stop applying. ## Guardian Disabled audit script Exit 0 means bundle ID `dev.silmaril.SilmarilMacOS`, Guardian Disabled (`SilmarilPrivilegedProtectionEnabled` false), and `CFBundleVersion` greater than or equal to `MIN_BUILD`. `MIN_BUILD` is the numeric Stable build 1077. Any other result exits 1. Location: MDM administrator workflow ```bash theme={"theme":"github-light-default"} #!/bin/bash APP="/Applications/Silmaril.app" BIN="$APP/Contents/MacOS/Silmaril" PLIST="$APP/Contents/Info.plist" EXPECTED_BUNDLE_ID="dev.silmaril.SilmarilMacOS" MIN_BUILD="1077" EXPECTED_GUARDIAN_ENABLED="0" case "$MIN_BUILD" in ''|*[!0-9]*) exit 1 ;; esac if [[ ! -x "$BIN" || ! -f "$PLIST" ]]; then exit 1 fi bundle_id="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$PLIST" 2>/dev/null)" || exit 1 installed="$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$PLIST" 2>/dev/null)" || exit 1 guardian="$(/usr/libexec/PlistBuddy -c "Print :SilmarilPrivilegedProtectionEnabled" "$PLIST" 2>/dev/null)" || exit 1 case "$installed" in ''|*[!0-9]*) exit 1 ;; esac case "$guardian" in true) guardian_enabled=1 ;; false) guardian_enabled=0 ;; *) exit 1 ;; esac if [[ "$bundle_id" == "$EXPECTED_BUNDLE_ID" ]] && [[ "$guardian_enabled" == "$EXPECTED_GUARDIAN_ENABLED" ]] && (( 10#$installed >= 10#$MIN_BUILD )); then exit 0 fi exit 1 ``` ## Console-user post-install script With no signed-in user, the script exits 0 and the profile opens Silmaril at the next login. Location: MDM administrator workflow ```bash theme={"theme":"github-light-default"} #!/bin/bash APP="/Applications/Silmaril.app" [[ -x "$APP/Contents/MacOS/Silmaril" ]] || exit 1 console_uid="$(/usr/bin/stat -f %u /dev/console)" || exit 1 case "$console_uid" in ''|*[!0-9]*) exit 1 ;; esac # With no signed-in user, the profile opens Silmaril at the next login. [[ "$console_uid" -gt 500 ]] || exit 0 /bin/launchctl asuser "$console_uid" \ /usr/bin/sudo -n -H -u "#$console_uid" \ /usr/bin/open "$APP" ``` ## Vendor references * [Custom Profiles overview](https://docs.iru.com/en/endpoint/library/library-items-profiles/custom-profiles-overview) * [Configure a Mac Custom App](https://docs.iru.com/en/endpoint/library/library-items-profiles/configure-the-custom-apps-library-item) * [Library Item status and activity](https://docs.iru.com/en/endpoint/library/library-items-profiles/library-item-status-activity-timeline) * [Custom Scripts overview](https://docs.iru.com/en/endpoint/library/library-items-profiles/custom-scripts-overview) * [Conditional logic in Assignment Maps](https://support.kandji.io/kb/using-conditional-logic-in-assignment-maps) * [Move a device to a different Blueprint](https://support.kandji.io/kb/move-a-device-to-a-different-blueprint) # Jamf Pro Source: https://silmaril.dev/docs/macos/jamf Deploy Silmaril with a device profile and a scoped package policy. Deploy Stable 0.3.22 (build 1077) with a Computer Level profile and a scoped package policy. ## Prepare the pilot Have administrator access, an enrolled Mac running macOS 14 or later, and the vendor management agent where required. Download the Stable 0.3.22 (1077) Guardian Disabled PKG and complete the required configuration before upload. The completed `Silmaril-Managed.mobileconfig` has passed validation, and the package is the [Stable PKG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.pkg). Complete the [shared profile preparation](/docs/macos/managed-deployment) before creating the Jamf profile or package policy. ## Install the profile In **Computers > Configuration Profiles**, upload `Silmaril-Managed.mobileconfig` as a **Computer Level** profile with **Install Automatically**. Scope it to a dedicated Silmaril smart or static group within the existing device configuration. Keep baseline profiles on their existing scopes. Export the stored payload and compare it with your completed file. Preserve every supplied identifier, UUID, and permission payload. Signed profiles are read-only. For an existing `dev.silmaril.mdm.managed` identity, use Jamf's supported replacement workflow. If a signed profile requires removal and reimport, pause the Silmaril app policy, restore that profile identity, and verify policy on pilot Macs before resuming installation. In the target computer's **Management** and **History** views, confirm the profile is installed and its install command completed. Keep the package policy disabled until then. ## Install the package Upload the 0.3.22 (1077) PKG in Settings > Computer management > Packages. Create a build-specific policy with Recurring check-in, Once per computer, package action Install, no restart, and the same Silmaril rollout group as the profile. Run the verify-and-launch script after the package. It checks bundle ID `dev.silmaril.SilmarilMacOS`, build 1077, and Guardian Disabled, then opens Silmaril for the console user or relies on login launch when nobody is signed in. Expect the policy to install the intended Guardian Disabled PKG once, with restart disabled. Script exit 0 opens Silmaril when a console user above UID 500 is signed in. With no signed-in user, the profile launches Silmaril at the next login. The script's failure message names the mismatch. It can report a missing app, an unreadable bundle ID or build, an unexpected bundle ID, a build older than 1077, or a Guardian variant that does not match this policy. Correct that result before treating the policy as successful. Package success alone does not mean protection is active. ## Verify the pilot Open Silmaril and [verify its identity, managed connection, and fresh Protection activity](/docs/macos/verify). Resolve Restart required by fully quitting and reopening the affected agent, then starting a new session. ## Expand the rollout For each later cohort, keep the app policy disabled until those Macs show the profile installed. Adding Macs to a group whose app policy is already enabled can deliver both at once. Repeat the profile-first staging and pilot verification for every cohort. ## Update or replace policy Deploy the intended Guardian Disabled version and update the app/package metadata together. Create a new version-specific **Once per computer** policy and script with the new minimum build. Disable old installer policies before enabling the new policy. Editing a completed policy leaves earlier Macs on their existing install. When replacing the profile, preserve its supplied identity and permission payloads. Removing managed keys restores saved local preferences. Profile removal and app removal are separate operations. ## Uninstall Disable every Silmaril package policy for the target Macs and preserve baseline scopes. Run the shared [MDM uninstall script](/docs/macos/uninstall#remove-through-mdm) as root with the affected user signed in. Retain its JSON receipt and exit code. Review exit 2 for the specific remaining items before taking further action. Upload the script in **Settings > Computer management > Scripts** and add it to a scoped script-only policy under **Computers > Policies**. Use **Once per computer** with restart disabled. Follow the shared receipt instructions, then remove only the Silmaril profile when policy should stop applying. ## Verify-and-launch script `MIN_BUILD` is the numeric Stable build 1077. `EXPECTED_GUARDIAN` is `false` for this Guardian Disabled policy. The script opens Silmaril only after the bundle ID, build, and Guardian variant match. Location: MDM administrator workflow ```bash theme={"theme":"github-light-default"} #!/bin/bash APP="/Applications/Silmaril.app" PLIST="$APP/Contents/Info.plist" EXPECTED_BUNDLE_ID="dev.silmaril.SilmarilMacOS" MIN_BUILD="1077" EXPECTED_GUARDIAN="false" fail() { echo "$1" >&2; exit 1; } [[ -x "$APP/Contents/MacOS/Silmaril" && -f "$PLIST" ]] || fail "Silmaril is not installed." bundle_id="$(/usr/libexec/PlistBuddy -c "Print :CFBundleIdentifier" "$PLIST" 2>/dev/null)" || fail "Cannot read bundle ID." build="$(/usr/libexec/PlistBuddy -c "Print :CFBundleVersion" "$PLIST" 2>/dev/null)" || fail "Cannot read build." guardian="$(/usr/libexec/PlistBuddy -c "Print :SilmarilPrivilegedProtectionEnabled" "$PLIST" 2>/dev/null)" || fail "Cannot read Guardian variant." [[ "$bundle_id" == "$EXPECTED_BUNDLE_ID" ]] || fail "Unexpected bundle ID: $bundle_id" case "$build" in ''|*[!0-9]*) fail "Invalid build: $build" ;; esac (( 10#$build >= 10#$MIN_BUILD )) || fail "Installed build $build is older than $MIN_BUILD." [[ "$guardian" == "$EXPECTED_GUARDIAN" ]] || fail "Installed Guardian variant does not match this policy." console_uid="$(/usr/bin/stat -f %u /dev/console)" || fail "Cannot read console user." case "$console_uid" in ''|*[!0-9]*) fail "Invalid console user." ;; esac [[ "$console_uid" -gt 500 ]] || exit 0 /bin/launchctl asuser "$console_uid" /usr/bin/sudo -n -H -u "#$console_uid" /usr/bin/open "$APP" ``` ## Vendor references * [Upload a macOS configuration profile](https://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Uploading_a_Configuration_Profile_macOS) * [Smart groups](https://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Smart_Groups) * [Package management](https://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Package_Management) * [Scripts](https://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Scripts) * [Policy management](https://learn.jamf.com/r/en-US/jamf-pro-documentation-current/Policy_Management) # Managed rollout Source: https://silmaril.dev/docs/macos/managed-deployment Prepare a configuration profile and package for deployment through MDM. ## Plan the rollout Keep each Mac in its existing device configuration. Add the Silmaril profile and app to that configuration, scoped to the intended devices with a tag, group, or label. Preserve existing controls such as CIS settings, FileVault, Falcon, and Okta Device Trust. Silmaril is a separate profile within the existing deployment structure. Never merge its payloads into unrelated baseline profiles. ## Before you begin Use this path for Jamf Pro, Iru, and Fleet. Deploy the Stable 0.3.22 (build 1077) Guardian Disabled PKG with the completed universal profile below. End users do not configure the connection. Assign the profile and the app to the same intended devices. A shared tag, group, or label does not set install order. For each rollout cohort, keep the app unassigned or its install policy disabled until that cohort shows the profile installed. Expanding the rollout repeats this staging. Adding devices to an assignment that already installs the app does not recreate the order. You need MDM administrator access, an enrolled Mac running macOS 14 or later, and the vendor management agent where required. | Item | Owner | | - | - | | Shared installer and standard configuration profile | Provided by Silmaril | | Deployment-specific API URL and API key | Supplied by Silmaril. Customer IT places both in the profile | | Optional Dashboard URL and Firewall ID | Supplied by Silmaril when needed | | Assignments, rollout, update deployment, optional policy choices, and validation | Customer IT | ## Release bundle Download the [release MDM ZIP](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled-MDM.zip) and the [SHA-256 sidecar](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled-MDM.zip.sha256) published with that ZIP. **Expected result**\ The ZIP checksum matches `86efaf60414339c97c5fcaac86d98b215f44010798f58e83ea3850b1243fd92a`. **If this differs**\ Stop and use the ZIP whose digest matches that value. A mismatched archive is not the Stable MDM artifact. The standard release profile includes notification and login permissions while leaving application settings unmanaged. It does not assign update ownership. The customized starter below adds deployment connection values and explicitly sets MDM update authority. Install one profile or the other, never both. For an existing Guardian Disabled deployment with separate Silmaril profiles, install the completed universal profile and confirm delivery, then remove the old split profiles through MDM. Confirm removal and recheck the intended managed values before expanding the rollout. > [!WARNING] > > A delivered profile or a successful package install does not mean endpoint protection is active. After the PKG is installed, [verify](/docs/macos/verify) the app, the managed connection, and current Protection activity. ## Required configuration [Download the complete profile template](https://silmaril.dev/docs/macos/Silmaril-Managed.template.mobileconfig). Silmaril supplies both required values: the API URL and API key. Other application preferences are [optional settings](/docs/macos/settings#optional-settings). The template is the universal device profile (`PayloadIdentifier` `dev.silmaril.mdm.managed`, display name Silmaril Managed). Its application-settings payload states that omitted keys remain unmanaged. The template explicitly keeps automatic agent maintenance off and leaves the inference override empty, so supporting releases derive that endpoint from the trusted Dashboard URL. The template includes notification permission and launch at login. Save the completed file as `Silmaril-Managed.mobileconfig` and upload that file. Managed credentials are readable by MDM administrators. Restrict access to the completed profile. A managed API key requires a forced valid HTTPS API URL. Rotate the URL and key together. **1. Fill in required values** Open the complete template in a plain-text XML editor. Replace its existing `mcx_preference_settings` key and dictionary with the XML below, then fill in both placeholders. Keep `SilmarilUpdateAuthority` set to `mdm` so IT controls updates. Location: MDM administrator workflow ```xml theme={"theme":"github-light-default"} mcx_preference_settings silmaril.apiURL REPLACE_WITH_SILMARIL_API_URL silmaril.apiKey REPLACE_WITH_SILMARIL_API_KEY silmaril.agentMaintenanceEnabled silmaril.inferenceURL SilmarilUpdateAuthority mdm ``` **Expected result**\\ Both placeholders contain the exact URL and API key supplied by Silmaril, update authority remains `mdm`, and the maintenance policy entries remain present with the values shown. **If this differs**\\ Do not upload a profile with unresolved placeholders. Obtain the deployment URL and key from Silmaril. MDM update authority disables the in-app updater. Your MDM still needs to deploy each PKG update. **2. Validate the completed profile** Preserve every profile and payload identifier, UUID, permission payload, and surrounding payload. Save the complete file as `Silmaril-Managed.mobileconfig`, then run the validation commands on your administrator Mac. Location: MDM administrator workflow ```bash theme={"theme":"github-light-default"} plutil -lint Silmaril-Managed.mobileconfig || exit 1 if grep -Eq 'REPLACE_WITH_|PLACEHOLDER_' Silmaril-Managed.mobileconfig; then echo "Unresolved placeholder found" >&2 exit 1 fi ``` **Expected result**\\ `plutil` reports the file is OK, and the command exits 0. **If this differs**\\ A lint failure or the message `Unresolved placeholder found` means the file is not ready to upload. Correct the XML and keep the supplied identifiers. Never invent identifiers to bypass a conflict. **3. Upload through MDM** Upload the whole completed profile. Assign the profile and PKG to the same intended devices, and confirm profile delivery before deploying the PKG. For a later cohort, leave the app unassigned or disabled until those Macs show the profile installed. End users do not configure the connection. Continue with the vendor guide for [Jamf Pro](/docs/macos/jamf), [Iru](/docs/macos/iru), or [Fleet](/docs/macos/fleet). **Expected result**\\ The completed profile is installed on the pilot Macs before the [Stable PKG](https://downloads.silmaril.dev/macos/releases/0.3.22/1077/Silmaril-0.3.22-1077-guardian-disabled.pkg) is deployed. That PKG is 0.3.22 (1077), Guardian Disabled. **If this differs**\\ Wait until the profile install has completed. Deploying the PKG first leaves the Mac outside this order. Optional keys, if used, are added inside the same `mcx_preference_settings` dictionary. See [Managed settings](/docs/macos/settings). ## Deploy with your MDM Each guide adds Silmaril to the existing configuration. It does not replace that configuration. The guides cover upload, assignment, verification, updates, and removal. * [Jamf Pro](/docs/macos/jamf) * [Iru (formerly Kandji)](/docs/macos/iru) * [Fleet](/docs/macos/fleet) # Managed settings Source: https://silmaril.dev/docs/macos/settings Configure optional policy and look up the 19 supported MDM keys. Add optional settings only inside the completed universal profile from [required configuration](/docs/macos/managed-deployment#managed-template). Silmaril checks forced organization policy first, then a saved local value, then the application default. If a managed key is later removed, the setting falls back to any saved local value. Use these statuses to check policy application. Confirm active protection separately with [endpoint verification](/docs/macos/verify). | Status | Meaning | | - | - | | Managed | Your organization owns the value, so the control is locked. | | Applying | Silmaril is still applying the latest managed value. | | Restart required | Restart the affected agent session before the new policy can protect new work. | | Needs attention | The managed value is malformed, unavailable, or could not be applied. Correct it instead of expecting local fallback. | ## Optional settings Add the settings required for your deployment inside the existing `mcx_preference_settings` dictionary, and add each key only once. Use the Dashboard URL or Firewall ID supplied by Silmaril when provided. To remove an optional setting, delete its complete key element and the following value element. To force Launch at Login Off, remove the complete `com.apple.loginitems.managed` payload before adding `silmaril.launchAtLoginEnabled` set to false. Duplicate keys, a display name used where an ID is required, or Launch at Login forced off while the template login-items payload remains, leave the profile invalid or still forcing launch. Fix the XML, validate again, and replace the profile while preserving its identity. See [troubleshooting](/docs/macos/troubleshooting). ### Dashboard URL Sets the supplied dashboard origin used by Fleet and Playground links. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.dashboardURL REPLACE_WITH_SILMARIL_DASHBOARD_URL ``` ### Firewall ID Routes audit records to the supplied Firewall. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.firewallID REPLACE_WITH_SILMARIL_FIREWALL_ID ``` ### Protection mode Requires Shadow mode for all protected agents. Shadow records decisions while work continues. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.preferredFirewallMode shadow ``` ### Request timeout Wait up to 2500 milliseconds for a Firewall response. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.timeoutMS 2500 ``` ### Audit Off Stops new audit collection and uploads. Existing records remain. Enable this only when your policy requires audit collection to stop. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.auditEnabled ``` ### Agent exclusions Excludes Codex and Cursor from endpoint protection. Use the exact agent identifiers you intend to exclude. Location: the existing `mcx_preference_settings` dictionary in your managed profile ```xml theme={"theme":"github-light-default"} silmaril.agentProtectionOptOuts codex cursor ``` ## Connection | Key | Type | Default | Accepted values | Behavior and dependencies | | - | - | - | - | - | | `silmaril.apiURL` | String | App default | Valid HTTPS URL with a host | Connection URL for this deployment. Use the full URL supplied by Silmaril. Required as a forced, valid value when `silmaril.apiKey` is forced. | | `silmaril.dashboardURL` | String | App default | HTTPS origin only. No path, query, or fragment | Dashboard root for Fleet and Playground links. It is never inferred from the API URL. | | `silmaril.apiKey` | String | None | Non-empty string | Organization-managed API credential. Profile contents are readable by MDM administrators. Requires a forced, valid `silmaril.apiURL` in the same profile. Restrict profile access and rotate URL and key together. | | `silmaril.timeoutMS` | Integer | 2500 | 250–10000 milliseconds | Maximum Firewall request wait. The native classifier caps the effective wait at 8000 milliseconds. Values above 8000 remain accepted for compatibility but do not add waiting time. | | `silmaril.firewallID` | String | Empty | 1–128 ASCII letters, digits, underscores, or hyphens | Routes audit records to the supplied Firewall. Use only when Silmaril supplies a Firewall ID. | ## Protection and audit | Key | Type | Default | Accepted values | Behavior and dependencies | | - | - | - | - | - | | `silmaril.firewallEnabled` | Boolean | true | true or false | Endpoint protection master switch. | | `silmaril.preferredFirewallMode` | String | shadow | shadow, warn, or block | Local value is the default for agents. A managed value is mandatory across agents. Legacy aliases Shadow, Enforce, and backend-controlled remain compatible. Use current values. | | `silmaril.agentProtectionOptOuts` | Array of strings | Empty array | codex, copilot, claudeCode, openClaw, hermes, openCode, cursor, pi, vscode | Excludes named hosts from endpoint protection. Unknown managed identifiers are invalid. | | `silmaril.protectionNotificationsEnabled` | Boolean | true | true or false | Controls the app notification preference. macOS notification permission is separate. | | `silmaril.auditEnabled` | Boolean | true | true or false | Off stops new audit collection and uploads while preserving existing records. | ## Agent maintenance Nightly 0.3.31 (1110) supports these settings as the minimum documented Nightly build. Confirm support for your selected channel before enabling maintenance. The complete profile keeps maintenance off and the inference override empty. These keys do not add maintenance capability to an older release. | Key | Type | Default | Accepted values | Behavior and dependencies | | - | - | - | - | - | | `silmaril.agentMaintenanceEnabled` | Boolean | false | true or false | Enables automatic agent updates, Silmaril setup repair, and supported-session restart checks. Keep the template's forced false value to leave this under organization policy. Set true only when automatic maintenance is intended. | | `silmaril.inferenceURL` | String | Empty | Empty, or a valid HTTPS URL on the trusted Dashboard origin using `/api/firewalls//setup/endpoints//inference` | Empty derives that maintenance endpoint from the Dashboard URL. Requires a valid Dashboard URL. User information, queries, fragments, and percent-encoded paths are rejected. An invalid forced override blocks maintenance without falling back to a saved local URL. | ## Discovery and startup | Key | Type | Default | Accepted values | Behavior and dependencies | | - | - | - | - | - | | `silmaril.mcpAutomaticDiscoveryEnabled` | Boolean | true | true or false | Controls automatic MCP discovery. | | `silmaril.pausedMCPConfigurationIDs` | Array of strings | Empty array | MCP configuration ID strings | Pauses discovery for the listed configurations. Use the exact IDs shown under Paused MCP configurations in Settings on a pilot Mac. Do not use display names. | | `silmaril.launchAtLoginEnabled` | Boolean | Current macOS registration | true or false | Controls the app-owned Launch at Login registration. To force Off, first remove the template's native login-items payload that forces launch On. | ## Updates | Key | Type | Default | Accepted values | Behavior and dependencies | | - | - | - | - | - | | `SilmarilUpdateAuthority` | String | app | app or mdm | `mdm` disables the in-app updater. MDM installs PKG updates. A bare PKG or installed profile does not infer ownership. | | `SilmarilUpdateChannel` | String | stable | stable or nightly | Selects the app feed when app-owned. Under MDM authority it does not install the chosen PKG. Restricted while `SilmarilUpdateAuthority` is `mdm`. | | `SUEnableAutomaticChecks` | Boolean | true | true or false | Controls automatic app update checks. Relevant when update authority is app. | | `SUAutomaticallyUpdate` | Boolean | false | true or false | Controls automatic installation of app-owned updates. Relevant when update authority is app. | For current release and Nightly availability, see [Install](/docs/macos/install#nightly). See [updates and channels](/docs/macos/updates) to choose update ownership and verify a new build. # Troubleshooting Source: https://silmaril.dev/docs/macos/troubleshooting Resolve connection, agent activity, and managed policy issues on a Mac. Resolve the relevant state below for Stable 0.3.22 (build 1077), then repeat [endpoint verification](/docs/macos/verify). ## No protection activity Confirm the app is running and its Firewall connection uses the supplied URL and key. Prepare the supported agent, fully quit and reopen it, then start a new session. Look for current activity in Protection. For MDM, confirm the profile reached the Mac before checking the package. Resolve pending policy states below. Use [verification](/docs/macos/verify) to check the installed identity and connection. ## One-Mac connection On a direct install, enter the supplied connection values in Silmaril and save them. The key is stored in your login Keychain. Managed labels apply when an organization profile controls the setting. For a saved connection that fails, confirm network reachability and ask Silmaril to check the deployment values. Keep the key out of support reports. ## Managed policy ### No Managed label Confirm the completed device or system profile was delivered to that Mac, the scope includes it, and the imported custom payload contains the intended application setting. **Expected result**\ API URL, API key, and any other forced settings show Managed in Silmaril. **If this differs**\ A profile that is merely assigned, or a payload that lost the application settings on import, will not show Managed. Export the stored profile and compare it with the completed `Silmaril-Managed.mobileconfig`. Jamf can change an unsigned import. Direct installs store a typed key in the login Keychain and do not show Managed unless a profile is also installed. ### Needs attention Fix the type, value, or dependency using the [managed settings reference](/docs/macos/settings), then replace the profile while preserving its identity. **Expected result**\ After the corrected profile is installed, the setting leaves Needs attention and shows Managed. **If this differs**\ Invalid managed input does not fall back to a saved local value. Check required pairs, especially a forced API key with a forced valid HTTPS API URL, accepted modes `shadow`, `warn`, or `block`, and known agent identifiers. Keep every supplied identifier and UUID. ## Connection failed Customer IT confirms the exact supplied URL and credential in the profile, replaces the profile if either is wrong, checks network reachability, and asks Silmaril to confirm the deployment values. **Expected result**\ The profile contains the URL and key Silmaril supplied for this deployment, and the first-run connection screen reports Firewall connection verified when that screen is displayed. **If this differs**\ Replace the profile with the corrected URL and key together. Use the values supplied for this deployment. Template placeholders are not valid credentials. ## Restart required Fully quit and reopen the affected agent, then start a new session. **Expected result**\ Current activity appears in Protection using the new policy. **If this differs**\ An agent that was only backgrounded is not the restart this state requires. Quit it fully, open it again, and start a new session before judging Protection. ## Notifications missing Confirm Notifications is enabled in Silmaril and in macOS System Settings > Notifications. **Expected result**\ Both the Silmaril preference and the macOS notification permission allow notifications. **If this differs**\ `silmaril.protectionNotificationsEnabled` controls the app preference only. macOS notification permission is separate. The universal template includes a notification payload, and that payload still does not replace the macOS authorization check. ## Applying persists Relaunch Silmaril, confirm the latest profile is installed, then contact Silmaril Support if the state remains. **Expected result**\ After relaunch, the setting finishes applying and shows Managed. **If this differs**\ Confirm the latest completed profile is the one installed, then contact Silmaril Support. Leaving the Mac on Applying means that setting is not confirmed. ## Get support Use **Support** in your supplied dashboard. Include the app version and build, affected agent, timestamp, and a sanitized description. For managed settings, include the key name and displayed state. See [support guidance](/docs/operate/troubleshooting#get-support). # Uninstall Source: https://silmaril.dev/docs/macos/uninstall Remove Silmaril through MDM and resolve items reported in the uninstall receipt. ## Remove from one Mac Save your work and sign in to the account whose Silmaril integrations you want to remove. On shared Macs, arrange cleanup for every affected account with Silmaril before removing the application. Open **Settings**, find **Uninstall**, and choose **Uninstall Silmaril**. Review the confirmation, choose **Uninstall**, and complete the authentication prompt. The uninstaller removes verified Silmaril integrations and local state. Check the **Silmaril Uninstall Report** JSON file in your home folder for preserved items or required permission cleanup. Complete the reported follow-up in System Settings. Server-side API key revocation remains a separate administrator action. If the app remains after a failed attempt, retain the report and [contact support](/docs/operate/troubleshooting#get-support) before retrying. ## Prepare managed removal Use this script to remove Stable 0.3.22 (1077) through Jamf Pro, Iru, or Fleet. [Download the uninstall script](https://silmaril.dev/docs/macos/Silmaril-Uninstall.sh) or copy it below unchanged. It calls the uninstaller bundled with the installed Silmaril app. ## Remove through MDM **1. Pause installation** Pause every Silmaril app installation assignment or policy for the target Macs, so the vendor cannot reinstall Silmaril. Leave unrelated management enabled. Do not delete a shared Blueprint, fleet, or group. Removing a tag, group, or label does not uninstall Silmaril. **Expected result**\\ Jamf, Iru, or Fleet can no longer reinstall Silmaril onto the target Macs. Unrelated management stays enabled. Vendor-specific task placement is in the [Jamf](/docs/macos/jamf), [Iru](/docs/macos/iru), and [Fleet](/docs/macos/fleet) uninstall steps. **If this differs**\\ Leave the uninstall script unrun until those install policies are paused. Otherwise MDM can put the app back. **2. Prepare the user session** Have the affected user sign in and save their work. The uninstaller removes that user's Silmaril integrations and local data, removes the shared application, and may stop and reopen agent applications. This script cleans one signed-in account and removes the shared app in the same run. For shared Macs, contact Silmaril to plan cleanup of every affected account before running it. rerunning after app removal cannot use the bundled helper. **Expected result**\\ The affected user is signed in at the console. Console UID is above 500. **If this differs**\\ The script exits 69 and attempts no removal when it cannot identify a signed-in user, or when that UID is 500 or below. Sign the user in and run the script again. **3. Run through MDM** Copy the script below unchanged into a root script task in Jamf Pro, Iru, or Fleet, or upload the downloaded file. Keep the JSON receipt and the exit code from the task. **Expected result**\\ The task runs as root and returns the uninstaller JSON plus exit code 0 or 2, or another code explained below. **If this differs**\\ Exit 77 means the script was not run as root. Exit 69 before the uninstaller runs means there is no eligible signed-in user, or the bundled uninstaller at `/Applications/Silmaril.app/Contents/Helpers/SilmarilUninstaller` is unavailable. Review the installed app and any previous receipt. A missing app or helper does not prove that earlier cleanup completed. **4. Complete the reported follow-up** Use the exit-code table below to review the result. Remove only the Silmaril configuration profile through MDM when policy should no longer apply. Confirm baseline assignments and controls are still intact. Local credential removal does not revoke the server API key. **Expected result**\\ Exit 0, with the app removed, and only the Silmaril profile removed through MDM when policy should stop applying. Baseline assignments and controls remain. **If this differs**\\ Follow the row for that exit code. Profile removal is a separate MDM change. Removing the profile does not uninstall Silmaril, and uninstalling the app does not remove the profile. ## Managed uninstall script Location: MDM administrator workflow ```bash theme={"theme":"github-light-default"} #!/bin/bash # Run through MDM as root with the affected user signed in. set -u APP="/Applications/Silmaril.app" UNINSTALLER="$APP/Contents/Helpers/SilmarilUninstaller" if [[ "$(/usr/bin/id -u)" -ne 0 ]]; then echo "Run the Silmaril uninstall script as root through MDM." >&2 exit 77 fi console_uid="$(/usr/bin/stat -f %u /dev/console)" || exit 69 case "$console_uid" in ''|*[!0-9]*) echo "Cannot identify the signed-in user. No removal was attempted." >&2 exit 69 ;; esac if [[ "$console_uid" -le 500 ]]; then echo "The affected user must be signed in. No removal was attempted." >&2 exit 69 fi if [[ ! -x "$UNINSTALLER" ]]; then echo "The bundled Silmaril uninstaller is unavailable. Review the installed app and any previous uninstall receipt." >&2 exit 69 fi if "$UNINSTALLER" --managed --user "$console_uid" --json; then result=0 else result=$? fi case "$result" in 0) echo "Silmaril removal completed." >&2 ;; 2) echo "Silmaril reported remaining items (exit 2). Review the JSON receipt for the affected components and required actions." >&2 ;; *) echo "Silmaril removal did not complete (exit $result). Review the uninstaller output before retrying." >&2 ;; esac exit "$result" ``` ## Exit codes | Exit code | Meaning | Next step | | - | - | - | | 0 | Removal completed. | Verify app removal and remove only the Silmaril profile through MDM. Confirm baseline assignments and controls remain intact. | | 2 | Removal completed with reported remaining items. | Review the receipt's residue or failed steps. The exit code alone does not identify the component or prove Guardian remains. Do not continue until the reported items have been reviewed and resolved. | | Any other nonzero code | Removal did not complete or could not start. | Read the uninstaller output and correct the reported problem before retrying. A missing app or helper does not prove that earlier cleanup completed. | For Stable 0.3.22 (1077), Guardian Disabled, a receipt can report successful app and integration removal with only Managed permissions and Notification permission remaining. Managed permissions means MDM policy may remain. Notification permission may be unverified. Remove only the Silmaril profile through MDM and, if Silmaril still appears in System Settings > Notifications, turn off Allow Notifications. Confirm baseline assignments and controls remain intact. These permission entries alone do not indicate remaining protection. If the receipt reports remaining Guardian components, failed app removal, or `protectionMayStillRun=true`, do not continue until that reported item is resolved. After the app is removed, complete permission follow-up directly through MDM or System Settings. Do not rerun a missing helper to obtain exit 0. Contact support before replacing an incompatible installed variant. > [!WARNING] > > Exit 2 is a receipt to read, not a Guardian finding. Confirm installed state separately. Check `/Applications/Silmaril.app` and its process, the Guardian daemon `dev.silmaril.guardian`, and the system extension `dev.silmaril.SilmarilMacOS.GuardianExtension`. # Updates and channels Source: https://silmaril.dev/docs/macos/updates Choose app-owned or MDM-owned updates and verify each deployed build. ## Choose update ownership | Update authority | How updates arrive | | - | - | | App | Silmaril's in-app updater uses the selected channel | | MDM | Your MDM deploys each PKG update | The managed starter profile sets `SilmarilUpdateAuthority` to `mdm`. A package install alone leaves update ownership to the configured setting. ## Choose a channel Use Stable for the standard deployment path. Select Nightly deliberately for a pilot. [Install](/docs/macos/install#nightly) lists the current artifacts and channel behavior. Under MDM authority, changing `SilmarilUpdateChannel` selects a preference while your MDM remains responsible for package delivery. ## Deploy and verify For app-owned updates, use the app's update controls. For MDM, follow the update section for [Jamf Pro](/docs/macos/jamf#update-or-replace-policy), [Iru](/docs/macos/iru#update-or-replace-policy), or [Fleet](/docs/macos/fleet#update-or-replace-policy). Keep profile and payload identities intact when replacing managed policy. On a pilot Mac, [verify the release, connection, and fresh agent activity](/docs/macos/verify) before expanding the update. See [managed settings](/docs/macos/settings#updates) for update keys and accepted values. # Verify protection Source: https://silmaril.dev/docs/macos/verify Check the release, connection, and current agent activity. ## Confirm the release Open Settings and check the installed identity. For Stable, Settings shows 0.3.22, build 1077, and Guardian Disabled. For the selected Nightly download, Settings shows 0.3.37, build 1119, and Guardian Disabled. Contact support before replacing an incompatible installed variant. Jamf and Iru treat 1077 as a minimum build so an install check can accept a newer build. Match the version, build, and Guardian capability to the channel you downloaded. A matching Nightly identity does not establish Stable qualification. ## Confirm the connection **One Mac** Confirm the saved Firewall URL and API key match the supplied values. A typed key is stored in your login Keychain. When the first-run connection screen is displayed, expect **Firewall connection verified**. If connection fails, check the URL, key, and network using [troubleshooting](/docs/macos/troubleshooting#connection-failed). **Managed Macs** Check that the API URL, API key, and every setting your profile forces show **Managed**. Resolve Applying, Restart required, or Needs attention states before continuing. Compare the pilot Mac's MDM assignments and profiles before and after rollout. Confirm existing baseline controls remain and the Silmaril profile reaches only intended Macs. Stop rollout and restore any missing baseline control. Use [managed settings](/docs/macos/settings) to correct profile values while preserving profile identity. ## Confirm live activity Prepare a supported agent, fully quit and reopen it, then start a new session. Check for current activity in **Protection**. Resolve any Restart required state by reopening the affected agent and starting another session. If activity is missing, follow [No protection activity](/docs/macos/troubleshooting#no-protection-activity). ## Confirm the mode New local defaults start in Shadow unless organization policy selects another mode. Shadow records would-block decisions while work continues. Check the intended mode for the protected agent and verify blocking separately with a controlled test agreed with Silmaril. For managed rollouts, complete these checks on the pilot before expanding each cohort. See [modes and enforcement](/docs/guides/enforcement) for the decision model. # Review findings Source: https://silmaril.dev/docs/operate/findings Connect a Firewall finding to its event, decision, and application behavior. Open the dashboard supplied for your deployment. Select the Firewall and time window, then open **Findings**. Use **Monitoring** to review traffic volume alongside findings. ## Inspect the event Open a finding and record its finding ID, request ID, timestamp, hook, and tool name. Review the available conversation and captured evidence to understand the input, proposed action, and preceding events. Read the prediction, applicable governance decision, and effective mode together. A finding can describe a would-block event observed in Shadow or Warn. Confirm actual execution behavior in your application when investigating impact. Captured evidence can be partial or unavailable. Check the evidence availability notice before drawing conclusions about what the model saw. Handle payloads as untrusted content and keep sensitive excerpts within your authorized review workflow. ## Report a false positive Use **Support** in your dashboard and select the affected Firewall. Include the finding ID, request ID, timestamp, expected legitimate behavior, and a short explanation of why the action was authorized. Share a sanitized reproduction when possible. Keep API keys, credentials, and unrelated private content out of the report. Silmaril can arrange access to additional evidence when needed. Feedback gives the team evidence to investigate and evaluate improvements. Confirm any enforcement change separately before relying on it. ## Investigate with an MCP client Use [Firewall MCP](/docs/integrations/firewall-mcp) to summarize traffic and findings, build an investigation packet, and retrieve authorized detail. Start with aggregates and narrow to the evidence the review needs. For missing events or connection failures, see [troubleshooting](/docs/operate/troubleshooting). # Troubleshooting Source: https://silmaril.dev/docs/operate/troubleshooting Resolve access failures, missing activity, and classification errors. ## Access or authentication fails Confirm the API key and complete endpoint URL belong to the same deployment. SDKs use the classify URL. LiteLLM uses the supplied guardrail URL. Check [authentication responses](/docs/api-reference/authentication) for the status returned by your deployment path. ## Traffic is missing Check the selected Firewall, region, and dashboard time window. Record the timestamp and request ID from a new test request. Confirm that execution uses the configured client, wrapped model, proxy, or protected agent session. Follow [verification](/docs/start/verify) for the integration you installed. ## A call fails An API or transport failure leaves the request without a verdict. Apply the boundary's configured failure policy and retain the status code and request ID for review. Use your SDK's documented retry and cancellation behavior. Retry model or tool operations only when the application can safely repeat them. ### Request body timeout The direct Firewall runtime returns HTTP `408` with code `request_body_timeout` when receiving the request body exceeds its deadline. It closes the connection before classification runs, so the response contains no verdict. Python and Go intentionally retry HTTP `408` by default. Python callers can configure `max_retries` (set it to `0` to disable retries); Go callers can use context cancellation or deadlines to bound retrying. The TypeScript SDK retries HTTP `429` only and surfaces `408` as an API error. A final API error means no verdict was returned to the caller. Retrying this pre-classification request does not establish that replaying an arbitrary model or tool operation is safe. Apply your application's fail-open or fail-closed policy when the SDK returns an error. ## Review compatibility fails Check that the client meets the [minimum SDK version](/docs/guides/review-depth#sdk-compatibility). An explicit selection requires a matching top-level acknowledgement. Retain the requested and returned values when reporting a compatibility error. ## A legitimate request is blocked Inspect the prediction, optional governance decision, effective mode, and connected boundary. [Report the finding](/docs/operate/findings#report-a-false-positive) with the authorized task and expected behavior. ## Get support Open **Support** in your dashboard and select the affected Firewall. Include the integration and SDK version, timestamp, request or finding ID, status code, and a sanitized description of the problem. For initial access, [book a call](https://cal.com/silmaril/30min). For local coding-agent setup and managed policy, use [macOS troubleshooting](/docs/macos/troubleshooting). # Go SDK Source: https://silmaril.dev/docs/sdk/go Install the Go SDK, classify user input and tool output, and handle typed block errors. ## Prerequisites Use Go 1.22 or later. Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` from your [deployment access](/docs/start/access). ## Install Location: your Go module directory ```sh theme={"theme":"github-light-default"} go get github.com/Silmaril-Security/sdk-go/firewall@v0.7.5 ``` ## Classify your first request Create one client per protected system and reuse it. Label each call with the boundary it checks, and check each call's error before making the next one. Location: protected application service ```go theme={"theme":"github-light-default"} package main import ( "context" "errors" "log" "os" "github.com/Silmaril-Security/sdk-go/firewall" ) func main() { fw, err := firewall.New(firewall.Options{ APIKey: os.Getenv("SILMARIL_API_KEY"), APIURL: os.Getenv("SILMARIL_API_URL"), }) if err != nil { log.Fatal(err) } ctx := context.Background() result, err := fw.Classify(ctx, "Summarize this release note.", firewall.WithHook(firewall.HookUserInput), ) if err != nil { var blocked *firewall.FirewallBlockedError if errors.As(err, &blocked) { log.Printf("block score=%.4f threshold=%.4f", blocked.Score, blocked.Threshold) return } log.Fatalf("classify failed: %v", err) } log.Printf("prediction=%s score=%.4f", result.Prediction, result.Score) } ``` In a service, pass the request context instead of `context.Background()`. For tool output, pass the tool name as well, and handle its error the same way. Location: protected application service ```go theme={"theme":"github-light-default"} result, err = fw.Classify(ctx, toolOutput, firewall.WithHook(firewall.HookToolResponse), firewall.WithToolName("read_file"), ) ``` ### Expected result A benign request logs `prediction=BENIGN` and its score. When the effective mode is Block, a malicious request returns `*firewall.FirewallBlockedError`. Its `Result` field holds the full verdict. In Shadow or Warn mode the call returns the result instead, so route on `result.Prediction`. A per-call mode option wins the client setting. Either local choice is sent with the request and preserved in `result.Mode`. Without a local override, current classify responses omit `mode`. `result.Mode` remains empty, and the SDK uses Block as its enforcement fallback. Choose Shadow or Warn explicitly on the client or call when that boundary should be observational. `OnClassify` callbacks inherit the same effective behavior. Outcome meanings and recommended actions are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). Any other error means the call did not produce a verdict. That includes `*firewall.APIError` for a non-2xx API response, and a network, timeout, or context error. Decide whether that boundary fails open or closed. For agent governance, `WithGovernance` adds typed agent and resource context to one `Classify` call. `WithBatchGovernance` supplies one optional context per `ClassifyBatch` item. The SDK sends these fields under `metadata.silmaril.governance`. The server returns an optional `BlockResult.Governance` decision with action, policy version, and rule ID. An explicit governance block is enforced in Block mode even with a benign prediction. Older responses can omit the decision. `ClassifyBatch` preserves each item's metadata, including `metadata.conversationId`. The current batch classifier treats each input independently and neither reads nor updates conversation history for that batch. See [Batch calls and conversation history](#batch-calls-and-conversation-history). Go uses direct classification calls and has no Deep Agents graph adapter. ## Concurrent requests Reuse one client across goroutines for independent events or different conversations. Ordered events that share one `metadata.conversationId` wait for each `Classify` call before the next event is sent. Each `Classify` or `ClassifyBatch` call takes a context, so canceling one call also stops its retry wait. Caller-owned metadata, callbacks, and custom transports must be safe for concurrent access. ## Batch calls and conversation history Pass `firewall.WithMetadata(firewall.ClassificationMetadata{"conversationId": conversationID})` on each ordered `Classify` call. Wait for and handle its verdict or error before sending the next event. `ClassifyBatch` preserves per-item metadata and treats items independently of conversation history. See [events and conversations](/docs/guides/events) for sequencing and [modes and enforcement](/docs/guides/enforcement) for returned verdicts in Warn and Shadow. ## Shadow mode An explicit local Shadow mode returns results instead of blocking errors, so you can measure would-block decisions while traffic continues. Per-call options can enforce one boundary before you change the client setting. Location: protected application service ```go theme={"theme":"github-light-default"} fw, err := firewall.New(firewall.Options{ APIKey: os.Getenv("SILMARIL_API_KEY"), APIURL: os.Getenv("SILMARIL_API_URL"), ShadowMode: true, OnClassify: func(event firewall.ClassifyEvent) { if event.Blocked && event.ShadowMode { log.Printf("would block %s score=%.4f", event.Hook, event.Result.Score) } }, }) if err != nil { log.Fatal(err) } _, err = fw.Classify(ctx, text, firewall.WithHook(firewall.HookToolResponse), firewall.WithShadowMode(true), ) ``` ### Enforce one boundary Location: protected Go service with the client above ```go theme={"theme":"github-light-default"} _, err = fw.Classify(ctx, text, firewall.WithHook(firewall.HookToolResponse), firewall.WithShadowMode(false), ) ``` Handle `err` before consuming the event. This call overrides Shadow mode and enforces its verdict. `OnClassify` can run on overlapping calls for independent events or different conversations. Synchronize any shared state it touches. Ordered events in one conversation stay on the sequential `Classify` path above. ## Errors Enforced blocks return `*firewall.FirewallBlockedError`, which carries `Score`, `Threshold`, and `Result`. Location: protected application service ```go theme={"theme":"github-light-default"} var blocked *firewall.FirewallBlockedError if errors.As(err, &blocked) { log.Printf("blocked score=%.4f threshold=%.4f", blocked.Score, blocked.Threshold) return } ``` Read `blocked.Result` for the verdict, or use shadow mode when you need the result without a blocking error. Outcome meanings and recommended actions are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). ### HTTP 408 handling Go retries HTTP `408` by default, including the direct runtime request-body timeout before classification. Use the call context to cancel retries or bound them with a deadline. This timeout closes the connection and produces no verdict. It does not establish replay safety for model or tool operations. ## Review depth See [review depth](/docs/guides/review-depth) for minimum SDK versions and selection behavior. Use the public `ReviewLevel` constants `ReviewStandard` and `ReviewDeep`. Set `Options.Review` for an explicit client default, `WithReview` for one `Classify` call, or `WithBatchReview` for one `ClassifyBatch` call. Resolution is explicit per-call option, then explicit client default, then omission of the wire field. An empty client option omits review. An explicitly passed empty or invalid per-call value is an error, not a request to reset server authority. Location: protected Go service ```go theme={"theme":"github-light-default"} fw, err := firewall.New(firewall.Options{ APIKey: os.Getenv("SILMARIL_API_KEY"), APIURL: os.Getenv("SILMARIL_API_URL"), Review: firewall.ReviewDeep, }) if err != nil { log.Fatal(err) } // This per-call selection overrides the client's deep default. result, err := fw.Classify(ctx, "Summarize this release note.", firewall.WithReview(firewall.ReviewStandard), ) if err != nil { var compatibility *firewall.ReviewCompatibilityError if errors.As(err, &compatibility) { log.Fatal("deploy backend review acknowledgement support first") } log.Fatal(err) } log.Printf("prediction=%s", result.Prediction) batch, err := fw.ClassifyBatch( ctx, []string{"Check the first event.", "Check the second event."}, firewall.WithBatchReview(firewall.ReviewDeep), ) if err != nil { log.Fatal(err) } log.Printf("batch results=%d", len(batch)) ``` Use `ReviewStandard` or `ReviewDeep`. An empty client setting omits review. An explicit empty per-call selection is invalid. Missing or mismatched backend acknowledgement produces `ReviewCompatibilityError`. See [review depth](/docs/guides/review-depth) for runtime requirements and response handling. ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Java SDK Source: https://silmaril.dev/docs/sdk/java Java 0.7 guide for classifying user input and tool output and handling block verdicts. ## Prerequisites Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` from your [deployment access](/docs/start/access). Confirm Java SDK availability, artifact repository, and compatible Java runtime with Silmaril before adding the dependency or running these examples. ## Install Location: your application's `build.gradle.kts` ```kotlin Gradle theme={"theme":"github-light-default"} dependencies { implementation("com.silmaril.security:silmaril-security-sdk:0.7.0") } ``` ## Classify your first request Create one client per protected system and reuse it. Label each call with the boundary it checks. Location: protected application service ```java theme={"theme":"github-light-default"} import dev.silmaril.security.BlockResult; import dev.silmaril.security.ClassifyOptions; import dev.silmaril.security.Firewall; import dev.silmaril.security.FirewallBlockedException; import dev.silmaril.security.FirewallMode; import dev.silmaril.security.HookLabel; public class FirstClassification { public static void main(String[] args) throws Exception { Firewall fw = Firewall.builder() .apiKey(System.getenv("SILMARIL_API_KEY")) .apiUrl(System.getenv("SILMARIL_API_URL")) .mode(FirewallMode.BLOCK) .build(); try { BlockResult result = fw.classify( "Summarize this release note.", ClassifyOptions.builder() .hook(HookLabel.USER_INPUT) .build() ); System.out.println("prediction=" + result.prediction()); } catch (FirewallBlockedException blocked) { System.err.println("block score=" + blocked.score() + " threshold=" + blocked.threshold()); return; } catch (Exception failure) { throw failure; } } } ``` For tool output, pass the tool name as well and keep the returned verdict: Location: protected application service ```java theme={"theme":"github-light-default"} BlockResult toolVerdict = fw.classify( toolOutput, ClassifyOptions.builder() .hook(HookLabel.TOOL_RESPONSE) .toolName("read_file") .build() ); System.out.println("prediction=" + toolVerdict.prediction()); ``` Read `toolVerdict` before the application consumes that tool output. In effective Block mode, a malicious prediction or an explicit governance block throws `FirewallBlockedException` instead of returning. ### Expected result A benign request prints `prediction=BENIGN`. `prediction` is the authoritative `BENIGN` or `MALICIOUS` verdict. `score()` and `threshold()` are diagnostics. Thresholds are fixed values controlled by the tenant. Effective Block stops the event when `prediction` is `MALICIOUS` or the governance action is `block`, including when `prediction` is `BENIGN`. That stop throws `FirewallBlockedException`, which carries `result()`, `score()`, and `threshold()`. Warn reports the would-block finding and continues. Shadow records it and continues. Both return the verdict. This example chooses Block explicitly. Choose an explicit mode for each client, and use per-call `mode(...)` only as an intentional override. An API, network, timeout, or cancellation failure means the call produced no verdict. It is not a block. Decide whether that boundary fails open or closed. Outcome categories are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). ## Governance context The API accepts typed agent and resource context on an individual `classify()` call and on each batch item. Follow the same concepts as the other SDKs: `agent`, `resource.kind`, and `resource.id`. Location: protected application service ```java theme={"theme":"github-light-default"} import dev.silmaril.security.GovernanceContext; import dev.silmaril.security.GovernanceResource; import dev.silmaril.security.GovernanceDecision; GovernanceContext context = GovernanceContext.builder() .agent("research-agent") .resource(GovernanceResource.builder() .kind("tool") .id("search") .build()) .build(); BlockResult result = fw.classify( "Search for the latest release notes", ClassifyOptions.builder() .hook(HookLabel.TOOL_CALL) .toolName("search") .governance(context) .build() ); GovernanceDecision decision = result.governance(); if (decision != null) { System.out.println(decision.action() + " " + decision.policyVersion() + " " + decision.ruleId()); } ``` The optional governance decision contains the action, policy version, and rule ID. Older responses can omit it. Check that the decision is present before reading `action()`. A malicious prediction or an explicit governance block stops the event only in effective Block mode. In Warn, report the would-block finding and continue. In Shadow, record it and continue. ## Conversation metadata A `conversationId` is caller-owned metadata. Pass it on each call with the option `ClassifyOptions.builder().metadata(Map.of("conversationId", conversationId))`: Location: protected application service ```java theme={"theme":"github-light-default"} import java.util.Map; ClassifyOptions options = ClassifyOptions.builder() .hook(HookLabel.USER_INPUT) .metadata(Map.of("conversationId", conversationId)) .build(); ``` Current Cascade conversation-aware checks require ordered individual `classify()` calls that reuse that id. Wait for and handle every verdict or error before consuming that event or sending the next event for the same id. ## Concurrent requests Concurrent calls are for independent events or different conversations. For a shared `conversationId`, wait for and handle every verdict or error before sending the next event for that id. ## Batch calls and conversation history `classifyBatch()` accepts one metadata context per item, including `conversationId`, and one optional governance context per item. Current Cascade batch classification preserves that per-item metadata and neither reads nor updates conversation history. The same id does not connect batch items into a sequence or attach them to previously cached history. Prefer ordered individual `classify()` calls for conversation-aware checks. Keep every returned verdict, including a Warn or Shadow tool-output result, and inspect its prediction and optional governance action before the event is consumed or the next event for that conversation is sent. Use the effective mode for a malicious prediction or an explicit governance block: Block stops the event. Warn reports the would-block finding and continues. Shadow records it and continues. Inspecting a Warn or Shadow verdict does not enforce it. See the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy) for verdict categories. ## Modes | Mode | Malicious prediction or governance action `block` | | - | - | | `FirewallMode.BLOCK` | Stop the event. `classify()` throws `FirewallBlockedException`. | | `FirewallMode.WARN` | Report the would-block finding and continue. The call returns the verdict. | | `FirewallMode.SHADOW` | Record the would-block finding and continue. The call returns the verdict. | Warn and Shadow return verdicts. They are not enforced automatically. Choose an explicit client mode. Use a per-call mode only when that override is intentional: Location: protected application service ```java theme={"theme":"github-light-default"} import dev.silmaril.security.FirewallMode; Firewall fw = Firewall.builder() .apiKey(System.getenv("SILMARIL_API_KEY")) .apiUrl(System.getenv("SILMARIL_API_URL")) .mode(FirewallMode.SHADOW) .build(); BlockResult verdict = fw.classify( text, ClassifyOptions.builder() .hook(HookLabel.TOOL_RESPONSE) .toolName("read_file") .mode(FirewallMode.BLOCK) .build() ); ``` Keep `verdict` when the call returns. The per-call mode is Block, so that call enforces. `shadowMode(true)` is legacy shorthand for `mode(FirewallMode.SHADOW)`, and `shadowMode(false)` is legacy shorthand for an enforcing call. Prefer `FirewallMode`. ## Errors An enforced block throws `FirewallBlockedException`. The exception carries the full `result()`, plus `score()` and `threshold()`. Location: protected application service ```java theme={"theme":"github-light-default"} try { BlockResult result = fw.classify(userInput, options); System.out.println("prediction=" + result.prediction()); } catch (FirewallBlockedException blocked) { BlockResult verdict = blocked.result(); System.err.println( "blocked prediction=" + verdict.prediction() + " score=" + blocked.score() + " threshold=" + blocked.threshold() ); return; } catch (Exception failure) { throw failure; } ``` `options` is the `ClassifyOptions` value for that boundary. Any other failure, including an API error, network error, timeout, or cancellation, means that call produced no verdict. It is not a block. Outcome categories are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # LangChain and LangGraph Source: https://silmaril.dev/docs/sdk/langchain Protect LangChain model calls and LangGraph execution in TypeScript or Python. ## Prerequisites Install and initialize the [TypeScript SDK](/docs/sdk/typescript#initialize) or [Python SDK](/docs/sdk/python#initialize). The examples use that client as `fw`. Set `OPENAI_API_KEY` for the OpenAI model examples. Choose Shadow or Warn explicitly for observation. These examples use the client's effective mode. In Block mode, callbacks raise a blocking exception. See [modes and enforcement](/docs/guides/enforcement). ## Add the callback handler **TypeScript** Location: protected application service ```bash theme={"theme":"github-light-default"} npm install @langchain/core @langchain/openai ``` Location: protected application service ```typescript theme={"theme":"github-light-default"} import { ChatOpenAI } from "@langchain/openai" const handler = await fw.asLangChainHandler() const model = new ChatOpenAI({ callbacks: [handler] }) await model.invoke("Hello") ``` LangGraph.js can use the same LangChain callback handler through graph invocation config. Location: protected application service ```typescript theme={"theme":"github-light-default"} const handler = await fw.asLangChainHandler() await graph.invoke(state, { callbacks: [handler], }) ``` `graph` and `state` belong to the LangGraph application. Shadow-mode overrides for this handler are on the [TypeScript SDK page](/docs/sdk/typescript#shadow-mode). **Python** Location: your service's Python environment ```sh theme={"theme":"github-light-default"} pip install "silmaril-security-sdk[langchain]==0.7.5" langchain-openai ``` Location: protected application service ```python theme={"theme":"github-light-default"} from langchain_openai import ChatOpenAI handler = fw.as_langchain_handler() model = ChatOpenAI(callbacks=[handler]) model.invoke("Hello") ``` LangGraph can pass the same callback handler through runnable config. Location: protected application service ```python theme={"theme":"github-light-default"} handler = fw.as_langchain_handler() graph.invoke( state, config={"callbacks": [handler]}, ) ``` `fw` is the [Python client](/docs/sdk/python#initialize). `graph` and `state` belong to the LangGraph application. Direct Python calls raise `FirewallBlockedException` in enforcement mode. See [Python errors](/docs/sdk/python#errors). ## Expected result Callback handlers enable model-start, tool-start, and tool-end checks by default; retriever hooks are opt-in. They inspect the latest user message on model start without rescanning prior tool messages. Each classification has a distinct `metadata.silmaril.request_id`. The LangChain run ID is correlation metadata at `metadata.langgraph.run_id`. Pass `conversationId` (TypeScript) or `conversation_id` (Python) to the handler when the backend should correlate a sequence. The SDK sends it as `metadata.conversationId`. Mode resolution for callback handlers and Deep Agents middleware is an explicit handler or middleware override, then the client setting, then an optional response mode, then Block. Current classify responses omit `mode`, so an integration without a local override uses the Block fallback. Set Shadow or Warn explicitly for observation. In Block mode, LangChain callbacks raise a blocking exception, while Deep Agents replaces denied content as described in the [Deep Agents guide](/docs/integrations/deep-agents). ## Deep Agents Use the [Deep Agents guide](/docs/integrations/deep-agents) to protect root agents, tools, and subagents. ## Verify coverage [Verify your integration](/docs/start/verify) with a new model call and tool interaction. Review [modes and enforcement](/docs/guides/enforcement) before enabling blocking. [LangChain documentation](https://docs.langchain.com/) · [LangGraph documentation](https://docs.langchain.com/oss/python/langgraph/overview) # Verdicts and outcomes Source: https://silmaril.dev/docs/sdk/outcomes Use prediction as the benign or malicious verdict, then route optional malicious detail with typed outcome constants. `prediction` is the authoritative `BENIGN` or `MALICIOUS` verdict. `primary_outcome` is a forward-compatible string: SDKs expose constants for known labels, but deployments can return additional labels. Treat the constants as routing conveniences rather than a closed verdict enum. The backend wire fields are `prediction`, `primary_outcome`, `outcome_scores`, `detector_scores`, and `detector_counts`. Handler functions such as `continueNormally` and `redactAndSuppress` in these examples belong to your application. ## Prerequisites Initialize the SDK client for your runtime and classify a boundary. * [TypeScript](/docs/sdk/typescript) * [Python](/docs/sdk/python) * [Go](/docs/sdk/go) * [Java](/docs/sdk/java) The routing example below checks the TypeScript governance verdict. Python and Go also expose typed governance decisions in direct classification results. In effective Block mode, Python and Go raise blocking errors. TypeScript callers must enforce the returned verdict themselves. ## Outcome taxonomy In Block mode, continue only when `prediction` is benign and no applicable governance policy blocks the request. For malicious results, route the primary outcome using this table of known legacy labels. The table is not exhaustive. Block by default if the outcome is missing or unrecognized. | Outcome | Meaning | Recommended action | | - | - | - | | `benign` | No harmful outcome detected. Check `prediction` for the verdict. | Continue if governance policy also allows the request. | | `information_disclosure` | Exposure of private data, documents, internal context, logs, or other non-secret sensitive information. | Block or require review before returning private data. | | `secret_exposure` | Exposure of credentials, tokens, keys, cookies, passwords, or other secrets. | Redact or suppress secret-bearing content. | | `control_abuse` | Misuse of authorized tools or user privileges without a stronger outcome. | Deny the action and request explicit confirmation. | | `system_compromise` | Privilege escalation, takeover, persistence, lateral movement, or hostile code execution. | Block, record a security event, and escalate. | | `service_disruption` | Downtime, lockout, degradation, resource exhaustion, cost spikes, or destructive activity. | Block destructive or disruptive actions. | ## Route a TypeScript result in Block mode This example enforces blocking. For observational behavior, record the verdict and continue according to [your effective mode](/docs/guides/enforcement). Location: protected TypeScript service ```typescript theme={"theme":"github-light-default"} import { HookLabel, Outcome } from "@silmaril-security/sdk" const result = await fw.classify(text, { hook: HookLabel.TOOL_RESPONSE, toolName: "read_file", }) if (result.governance?.action === "block") { return denyByPolicy(result) } if (result.prediction === "BENIGN") { return continueNormally(result) } switch (result.primaryOutcome) { case Outcome.SecretExposure: return redactAndSuppress(result) case Outcome.InformationDisclosure: return requireReviewBeforeReturningPrivateData(result) case Outcome.ControlAbuse: return denyAndRequestExplicitConfirmation(result) case Outcome.SystemCompromise: securityLog(result) return blockAndEscalate(result) case Outcome.ServiceDisruption: return blockDestructiveOrDisruptiveAction(result) default: return blockByDefault(result) } ``` `fw` is the [TypeScript client](/docs/sdk/typescript#initialize). ## Expected result A benign prediction invokes your continue handler. A governance-policy block is denied before the prediction is checked. A malicious prediction invokes the matching outcome handler, or the default blocking handler when no outcome matches. ## Field names | API response | TypeScript | Python | Go | | - | - | - | - | | `prediction` | `prediction` | `prediction` | `Prediction` | | `primary_outcome` | `primaryOutcome` | `primary_outcome` | `PrimaryOutcome` | | `governance.action` | `governance?.action` | `governance.action` when present | `Governance.Action` when present | Read optional governance fields only when the decision is present. See [events and conversations](/docs/guides/events) for request fields. # Python SDK Source: https://silmaril.dev/docs/sdk/python Install the Python SDK, classify user input and tool output, and handle blocks with synchronous or asynchronous clients. ## Prerequisites Use Python 3.10 or later. Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` from your [deployment access](/docs/start/access). ## Install Location: your service's Python environment ```sh theme={"theme":"github-light-default"} pip install silmaril-security-sdk==0.7.5 ``` ## Classify your first request Create one client per protected system and reuse it. Label each call with the boundary it checks. Location: protected application service ```python theme={"theme":"github-light-default"} import logging import os from silmaril_security.sdk import ( Firewall, FirewallBlockedException, HookLabel, SilmarilApiError, ) logging.basicConfig(level=logging.INFO) fw = Firewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], ) try: result = fw.classify( "Summarize this release note.", hook=HookLabel.USER_INPUT, ) logging.info("prediction=%s score=%.4f", result.prediction, result.score) except FirewallBlockedException as exc: logging.warning("block score=%.4f threshold=%.4f", exc.score, exc.threshold) except SilmarilApiError as exc: logging.error("Silmaril API error: %s", exc) raise ``` For tool output, pass the tool name as well. Location: protected application service ```python theme={"theme":"github-light-default"} fw.classify( tool_output, hook=HookLabel.TOOL_RESPONSE, tool_name="read_file", ) ``` ### Expected result A benign request logs `prediction=BENIGN` and its score. When the effective mode is Block, a malicious request raises `FirewallBlockedException`. Its `result` holds the full verdict. In Shadow or Warn mode the call returns the result instead, so route on `result.prediction`. A per-call `mode` override wins the client setting. Either local choice is sent with the request and preserved in `result.mode`. Without a local override, current classify responses omit `mode`. `result.mode` remains `None`, and the SDK uses Block as its enforcement fallback. Choose Shadow or Warn explicitly on the client or call when that boundary should be observational. Classification callbacks inherit the same effective behavior. Outcome meanings and recommended actions are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). `SilmarilApiError` means the API returned a non-2xx response after retries. Network failures raise the underlying `requests` exception. Neither is a verdict, so decide whether that boundary fails open or closed. LangChain and LangGraph handlers for this client are documented in [Framework SDKs](/docs/integrations/frameworks). ## Caller-owned HTTP sessions Starting with SDK 0.7.1, a supplied `requests.Session` keeps its default headers. The SDK attaches its API key to each Firewall request, so unrelated calls through that session do not inherit the key. Reuse a client for repeated classifications. ## Governance context `GovernanceContext.agent` is caller attribution, not policy-authoritative identity. Setting it does not activate agent-scoped rules. Those rules match the authenticated principal binding configured by the server. `GovernanceContext.resource` is serialized as the request's top-level `resource` and matches canonical resource rules. A malicious prediction or an explicit governance block raises `FirewallBlockedException` in Block mode. Location: protected application service ```python theme={"theme":"github-light-default"} from silmaril_security.sdk import GovernanceContext, GovernanceResource context = GovernanceContext( agent="research-agent", # Caller attribution. resource=GovernanceResource( # Matches canonical resource rules. kind="tool", id="search", ), ) try: result = fw.classify( "Search for the latest release notes", hook=HookLabel.TOOL_CALL, tool_name="search", governance=context, ) except FirewallBlockedException as exc: decision = exc.result.governance logging.warning( "blocked: prediction=%s action=%s rule=%s", exc.result.prediction, decision.action if decision else None, decision.rule_id if decision else None, ) raise else: if result.governance is not None: logging.info( "governance action=%s policy=%s", result.governance.action, result.governance.policy_version, ) ``` For `classify_batch()`, pass one optional governance context per input using `governance=[context, None, ...]`. Each item also keeps its own metadata, including `metadata.conversationId`. That value is carried with the item. The current batch classifier treats each input independently and neither reads nor updates conversation history for that batch. See [Batch calls and conversation history](#batch-calls-and-conversation-history). In Warn or Shadow mode, inspect `result.prediction` and, only when `result.governance is not None`, `result.governance.action` because the SDK returns the verdict instead of raising. Older backends may omit `result.governance`. ## Batch calls and conversation history Pass `metadata={"conversationId": conversation_id}` on each ordered synchronous or asynchronous `classify()` call. Wait for and handle its verdict or error before sending the next event. `classify_batch()` preserves per-item metadata and treats items independently of conversation history. See [events and conversations](/docs/guides/events) for sequencing and [modes and enforcement](/docs/guides/enforcement) for returned verdicts in Warn and Shadow. ## Async classify Install async support before using `AsyncFirewall`. Location: your service's Python environment ```sh theme={"theme":"github-light-default"} pip install "silmaril-security-sdk[async]==0.7.5" ``` Location: protected application service ```python theme={"theme":"github-light-default"} import asyncio import os from collections.abc import AsyncIterator from contextlib import asynccontextmanager from silmaril_security.sdk import AsyncFirewall, HookLabel @asynccontextmanager async def firewall_lifespan() -> AsyncIterator[AsyncFirewall]: async with AsyncFirewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], ) as fw: yield fw # share with request handlers until shutdown async def handle_request(fw: AsyncFirewall, first_input: str, second_input: str): # Independent events in different conversations. return await asyncio.gather( fw.classify( first_input, hook=HookLabel.USER_INPUT, metadata={"conversationId": "conv-a"}, ), fw.classify( second_input, hook=HookLabel.USER_INPUT, metadata={"conversationId": "conv-b"}, ), ) ``` Share one `AsyncFirewall` across tasks on one event loop for the service lifetime and close it at shutdown. The gathered calls are independent events or different conversations. Ordered events that share one `metadata.conversationId` wait for each `classify()` call before the next event is sent. Cancellation stops the calling task's request. Async calls raise the same block exceptions as the synchronous client. For a synchronous off-thread fallback, create one `Firewall` per worker thread. ## Shadow mode An explicit local Shadow mode returns results instead of raising block exceptions, so you can measure would-block decisions while traffic continues. Per-call overrides let one boundary enforce before you change the client setting. Location: protected application service ```python theme={"theme":"github-light-default"} import logging import os from silmaril_security.sdk import ClassifyEvent, Firewall, HookLabel def on_classify(event: ClassifyEvent) -> None: if event.blocked and event.shadow_mode: logging.info("would block %s score=%.4f", event.hook, event.result.score) fw = Firewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], shadow_mode=True, on_classify=on_classify, ) result = fw.classify(text, hook=HookLabel.TOOL_RESPONSE) ``` ### Enforce one boundary Location: protected Python service with the client above ```python theme={"theme":"github-light-default"} fw.classify(text, hook=HookLabel.TOOL_RESPONSE, shadow_mode=False) ``` This call overrides Shadow mode and enforces its verdict. ## Errors Direct calls raise `FirewallBlockedException` when a block is enforced. It carries `score`, `threshold`, and the full `result`. Location: protected application service ```python theme={"theme":"github-light-default"} try: fw.classify(user_input, hook=HookLabel.USER_INPUT) except FirewallBlockedException as exc: logging.warning("blocked score=%.4f threshold=%.4f", exc.score, exc.threshold) ``` Read `exc.result` for the verdict, or use shadow mode when you need the result without a block exception. Outcome meanings and recommended actions are in the [outcome taxonomy](/docs/sdk/outcomes#outcome-taxonomy). ### HTTP 408 handling Python retries HTTP `408` by default, including the direct runtime request-body timeout before classification. Configure `max_retries=0` to disable retries. This timeout closes the connection and produces no verdict. It does not establish replay safety for model or tool operations. ## Review depth See [review depth](/docs/guides/review-depth) for minimum SDK versions and selection behavior. The public `ReviewLevel` type accepts only `"standard"` or `"deep"`. Pass `review` to `Firewall` or `AsyncFirewall` for an explicit client default, and to synchronous or asynchronous `classify()` and `classify_batch()` calls for a per-call selection. Resolution is explicit per-call value, then explicit client default, then omission of the wire field. `None` means no selection at that layer, so a per-call `None` inherits an explicit client default and a client `None` omits the field. The SDK never supplies an implicit review default. Location: protected Python service ```python theme={"theme":"github-light-default"} import os from silmaril_security.sdk import ( Firewall, ReviewCompatibilityError, ReviewLevel, ) client_review: ReviewLevel = "deep" fw = Firewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], review=client_review, ) try: # This per-call selection overrides the client's deep default. result = fw.classify( "Summarize this release note.", review="standard", ) batch = fw.classify_batch( ["Check the first event.", "Check the second event."], review="deep", ) except ReviewCompatibilityError: # Deploy backend acknowledgement support before enabling review selection. raise ``` Use the exact strings `standard` and `deep`. `None` inherits an explicit client default or omits the selection when no default exists. Other values fail SDK validation. Missing or mismatched backend acknowledgement produces `ReviewCompatibilityError`. See [review depth](/docs/guides/review-depth) for runtime requirements and response handling. ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # TypeScript SDK Source: https://silmaril.dev/docs/sdk/typescript Install the TypeScript SDK, classify user input and tool output, and route the verdict. ## Prerequisites Use Node.js 20 or later. Set `SILMARIL_API_KEY` and `SILMARIL_API_URL` from your [deployment access](/docs/start/access). ## Install Location: your service project directory ```sh theme={"theme":"github-light-default"} npm install @silmaril-security/sdk@0.7.9 ``` ## Classify your first request Create one client per protected system and reuse it. Label each call with the boundary it checks. Location: protected application service ```typescript theme={"theme":"github-light-default"} import { Firewall, HookLabel } from "@silmaril-security/sdk" const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, }) const result = await fw.classify("Summarize this release note.", { hook: HookLabel.USER_INPUT, }) const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block" console.log(result.prediction, result.score.toFixed(4), blocked ? "block" : "allow") ``` For tool output, pass the tool name as well. Location: protected application service ```typescript theme={"theme":"github-light-default"} await fw.classify(toolOutput, { hook: HookLabel.TOOL_RESPONSE, toolName: "read_file", }) ``` ### Expected result A benign request logs `BENIGN`, its score, and `allow`. Direct `classify` calls return a result in every mode. To enforce Block mode, stop the event when `prediction` is `MALICIOUS` or `governance.action` is `block`, then route it with [firewall outcomes](/docs/sdk/outcomes). A per-call mode wins the client setting. Either local choice is sent with the request and preserved in `result.mode`. Without a local override, current classify responses omit `mode`, so `result.mode` remains absent. Direct calls are decision-only regardless: the caller chooses whether to enforce according to its local mode and application policy. A rejected promise means the call did not produce a verdict. That includes `SilmarilApiError` for a non-2xx API response, and a network, timeout, or abort error. With native `fetch`, a refused redirect raises a `TypeError`. Custom `fetch` implementations can differ. Decide whether that boundary fails open or closed. Vercel, LangChain, and LangGraph integrations that wrap this client are documented in [Framework SDKs](/docs/integrations/frameworks). ## Concurrent requests Reuse one client for concurrent calls in a JavaScript runtime when those calls are independent events or different conversations. Ordered events that share one `metadata.conversationId` wait for each `classify()` call before the next event is sent. Pass `signal` to `classify` or `classifyBatch` to cancel one request and its retry wait. Create a client in each worker thread. Location: protected application service ```typescript theme={"theme":"github-light-default"} // Independent events in different conversations. const [first, second] = await Promise.all([ fw.classify(firstInput, { hook: HookLabel.USER_INPUT, metadata: { conversationId: "conv-a" }, }), fw.classify(secondInput, { hook: HookLabel.TOOL_RESPONSE, toolName: "read_file", metadata: { conversationId: "conv-b" }, }), ]) await fw.classify(userInput, { hook: HookLabel.USER_INPUT, signal: request.signal, }) ``` `request.signal` is the caller's `AbortSignal`. `classifyBatch()` cancels as one request. Its items stay independent of one another. ## Batch calls and conversation history `classifyBatch()` requires a `predictions` array with one result per input. Missing or mismatched results reject with an `Error`, including during retries. Pass `metadata: { conversationId }` on each ordered `classify()` call. Wait for and handle its verdict or error before sending the next event. Batch calls preserve per-item metadata and treat items independently of conversation history. See [events and conversations](/docs/guides/events) for sequencing and [modes and enforcement](/docs/guides/enforcement) for returned verdicts in Warn and Shadow. ## Shadow mode An explicit local Shadow mode reports would-block decisions with the same thresholds while traffic continues. Framework adapters resolve an adapter override first, then the client setting, then an optional response mode, and finally a Block fallback. Current classify responses omit `mode`, so set Shadow or Warn locally when an adapter should observe rather than block. Location: protected application service ```typescript theme={"theme":"github-light-default"} const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, shadowMode: true, }) const handler = await fw.asLangChainHandler({ onClassify: (event) => { if (event.blocked && event.shadowMode) { console.warn("would block", event.hook, event.result.score) } }, }) ``` ### Enforce one boundary Location: protected TypeScript service with the client above ```typescript theme={"theme":"github-light-default"} const enforcingHandler = await fw.asLangChainHandler({ shadowMode: false, }) ``` Attach `enforcingHandler` to the model or graph whose callbacks should enforce. ## Errors Blocking behavior depends on the integration. Vercel non-streaming generation and LangChain callbacks throw `FirewallBlockedException` when they enforce a malicious or governance-block decision. The exception carries `score` and `threshold`. Location: protected application service ```typescript theme={"theme":"github-light-default"} import { FirewallBlockedException } from "@silmaril-security/sdk" try { await generateText({ model, prompt: userInput }) } catch (err) { if (err instanceof FirewallBlockedException) { console.warn("blocked", err.score, err.threshold) return } throw err } ``` `generateText` and `model` come from the [Vercel integration](/docs/sdk/vercel-ai). With Vercel streaming, an output block surfaces as an error stream part after any text already emitted. Deep Agents replaces denied tool interactions or model content with fixed safe messages in Block mode in Block mode. See [Deep Agents](/docs/integrations/deep-agents). Rethrow any non-block error from non-streaming calls according to your application error policy. ### HTTP 408 handling TypeScript retries HTTP `429` only. HTTP `408`, including the direct runtime request-body timeout before classification, is surfaced as `SilmarilApiError`. This timeout closes the connection and produces no verdict. It does not establish replay safety for model or tool operations. ## Review depth See [review depth](/docs/guides/review-depth) for minimum SDK versions and selection behavior. The public `ReviewLevel` type is `"standard" | "deep"`. Set `review` in `Firewall` options for an explicit client default, or in `classify` and `classifyBatch` options for a per-call selection. Resolution is explicit per-call value, then explicit client default, then omission of the wire field. The SDK never invents an implicit default. Location: protected TypeScript service ```typescript theme={"theme":"github-light-default"} import { Firewall, ReviewCompatibilityError, type ReviewLevel, } from "@silmaril-security/sdk" const clientReview: ReviewLevel = "deep" const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, review: clientReview, }) try { // This per-call selection overrides the client's deep default. const result = await fw.classify("Summarize this release note.", { review: "standard", }) const batch = await fw.classifyBatch( ["Check the first event.", "Check the second event."], { review: "deep" }, ) } catch (error) { if (error instanceof ReviewCompatibilityError) { // Deploy backend acknowledgement support before enabling review selection. } throw error } ``` Use the exact strings `standard` and `deep`. Invalid values, including `null`, fail SDK validation. Missing or mismatched backend acknowledgement produces `ReviewCompatibilityError`. See [review depth](/docs/guides/review-depth) for runtime requirements and response handling. ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Vercel AI SDK Source: https://silmaril.dev/docs/sdk/vercel-ai Check model input and generated text before returning a Vercel AI SDK response. ## Prerequisites Install and initialize the [TypeScript SDK](/docs/sdk/typescript#initialize). The example uses that client as `fw`. ## Wrap your model Install compatible AI SDK and OpenAI provider packages. Set `OPENAI_API_KEY` in your service environment for the model provider. Location: protected application service ```bash theme={"theme":"github-light-default"} npm install ai@^6 @ai-sdk/openai@^3 ``` Use middleware to check input, then classify the generated text before returning it. This example enforces both checks. Location: protected application service ```typescript theme={"theme":"github-light-default"} import { FirewallBlockedException, HookLabel } from "@silmaril-security/sdk" import { wrapLanguageModel, generateText } from "ai" import { openai } from "@ai-sdk/openai" const model = wrapLanguageModel({ model: openai("gpt-5.4"), middleware: fw.asMiddleware({ shadowMode: false, scanOutput: false }), }) const response = await generateText({ model, prompt: "Summarize the benefits of request validation.", }) const result = await fw.classify(response.text, { hook: HookLabel.LLM_OUTPUT }) const blocked = result.prediction === "MALICIOUS" || result.governance?.action === "block" if (blocked) { throw new FirewallBlockedException({ score: result.score, threshold: result.threshold, promptText: response.text, hook: HookLabel.LLM_OUTPUT, result, }) } console.log(response.text) ``` ## Expected result Allowed input reaches the model, and allowed output is returned only after classification. A blocked input or output throws `FirewallBlockedException`. Handle it at your application boundary and return a safe response. Let API and network failures follow your error policy. See [TypeScript errors](/docs/sdk/typescript#errors). ## Coverage The middleware checks the latest user message or tool responses before each model call. With AI SDK 6, use the explicit output check above instead of relying on the middleware's `scanOutput` option. This example protects generated text. Classify tool-call arguments before executing a tool. Streaming output must be buffered and classified before delivery if you need to prevent blocked content from reaching the caller. Every protected call must use the wrapped model. For models routed through Vercel AI Gateway, use the [gateway guide](/docs/gateways/vercel). [Vercel AI SDK documentation](https://ai-sdk.dev/docs) ## Next steps [Verify your integration](/docs/start/verify), then review [modes and enforcement](/docs/guides/enforcement). # Get access Source: https://silmaril.dev/docs/start/access Get the credentials and endpoints for your Silmaril deployment. [Book a call](https://cal.com/silmaril/30min) to arrange a hosted or self-hosted deployment. Silmaril supplies the connection values for your application, gateway, plugins, or Macs. ## Connection values | Value | Used by | What to enter | | - | - | - | | `SILMARIL_API_KEY` | SDKs and gateways | The key supplied for your deployment | | `SILMARIL_API_URL` | SDKs and framework integrations | The complete classify URL, including `/classify` and any path prefix | | `SILMARIL_GUARDRAIL_URL` | LiteLLM | The separate guardrail URL supplied for your proxy | | Dashboard URL | macOS and operators | The supplied dashboard origin | | Firewall ID | Evidence and managed settings | The deployment identifier supplied by Silmaril | Model-provider and gateway credentials are configured separately. ## Configure a service Store the key in your service environment or secret manager. Make Firewall requests from the service that owns model calls or tool execution. Keep credentials out of browser bundles and source control. Location: local development terminal for your protected service ```bash theme={"theme":"github-light-default"} export SILMARIL_API_KEY='REPLACE_WITH_YOUR_API_KEY' export SILMARIL_API_URL='REPLACE_WITH_YOUR_COMPLETE_CLASSIFY_URL' ``` For production, set the same values through your service's deployment configuration. Use the supplied URL as-is. ## Choose your setup | Protect | Setup | | - | - | | An application | [Quick start](/docs/start/quickstart) | | A framework | [Framework integrations](/docs/integrations/frameworks) | | Gateway traffic | [AI gateways](/docs/gateways/overview) | | A coding agent | [Agent plugins](/docs/integrations/agent-plugins) | | One Mac or a fleet | [Silmaril for macOS](/docs/macos) | Review [data handling](/docs/deployment/data-handling) before sending production traffic. For connection issues, use [troubleshooting](/docs/operate/troubleshooting). # Quick start Source: https://silmaril.dev/docs/start/quickstart Install an SDK, classify a request, and verify your connection. ## Get access Silmaril supplies an API key and complete classify URL for your deployment. [Book a call](https://cal.com/silmaril/30min) to arrange access. See [connection values](/docs/start/access) for gateways, plugins, and Macs. ## Prerequisites Use Node.js 20 or later, Python 3.10 or later, or Go 1.22 or later. For Java, confirm the artifact repository and compatible runtime with Silmaril before setup. Choose the service that owns model calls or tool execution. Create one Firewall client for that service and reuse it. ## Configure the environment Set the supplied values in your service environment. For a local check, use the same terminal that runs the example. Location: local development terminal ```bash theme={"theme":"github-light-default"} export SILMARIL_API_KEY='REPLACE_WITH_YOUR_API_KEY' export SILMARIL_API_URL='REPLACE_WITH_YOUR_COMPLETE_CLASSIFY_URL' ``` The URL includes `/classify` and any deployment path prefix. ## Make your first classification These examples select Shadow mode so you can inspect would-block decisions while the workflow continues. **TypeScript** Location: your service project directory ```sh theme={"theme":"github-light-default"} npm install @silmaril-security/sdk@0.7.9 ``` Location: your service project directory ```bash theme={"theme":"github-light-default"} npm install --save-dev tsx ``` Location: `quickstart.ts` in your service project ```typescript theme={"theme":"github-light-default"} import { Firewall, HookLabel } from "@silmaril-security/sdk" async function main() { const fw = new Firewall({ apiKey: process.env.SILMARIL_API_KEY!, apiUrl: process.env.SILMARIL_API_URL!, shadowMode: true, }) const result = await fw.classify("Summarize this release note.", { hook: HookLabel.USER_INPUT, }) console.log(result.prediction) } main().catch((error) => { console.error(error) process.exitCode = 1 }) ``` Location: your service project directory ```bash theme={"theme":"github-light-default"} npx tsx quickstart.ts ``` **Python** Location: your service's Python environment ```sh theme={"theme":"github-light-default"} pip install silmaril-security-sdk==0.7.5 ``` Location: `quickstart.py` in your service project ```python theme={"theme":"github-light-default"} import os from silmaril_security.sdk import Firewall, HookLabel fw = Firewall( api_key=os.environ["SILMARIL_API_KEY"], api_url=os.environ["SILMARIL_API_URL"], shadow_mode=True, ) result = fw.classify( "Summarize this release note.", hook=HookLabel.USER_INPUT, ) print(result.prediction) ``` Location: your service project directory ```bash theme={"theme":"github-light-default"} python quickstart.py ``` **Go** Use your service's existing Go module. For a new example directory, initialize one first. Location: a new example directory ```bash theme={"theme":"github-light-default"} go mod init example.com/firewall-check ``` Location: your Go module directory ```sh theme={"theme":"github-light-default"} go get github.com/Silmaril-Security/sdk-go/firewall@v0.7.5 ``` Location: `main.go` in your Go module ```go theme={"theme":"github-light-default"} package main import ( "context" "log" "os" "github.com/Silmaril-Security/sdk-go/firewall" ) func main() { fw, err := firewall.New(firewall.Options{ APIKey: os.Getenv("SILMARIL_API_KEY"), APIURL: os.Getenv("SILMARIL_API_URL"), ShadowMode: true, }) if err != nil { log.Fatal(err) } result, err := fw.Classify(context.Background(), "Summarize this release note.", firewall.WithHook(firewall.HookUserInput), ) if err != nil { log.Fatal(err) } log.Println(result.Prediction) } ``` Location: your Go module directory ```bash theme={"theme":"github-light-default"} go run . ``` **Java** Use an existing Gradle application with the artifact repository supplied by Silmaril. Location: your application's `build.gradle.kts` ```kotlin Gradle theme={"theme":"github-light-default"} dependencies { implementation("com.silmaril.security:silmaril-security-sdk:0.7.0") } ``` Location: `src/main/java/FirstClassification.java` ```java theme={"theme":"github-light-default"} import dev.silmaril.security.ClassifyOptions; import dev.silmaril.security.Firewall; import dev.silmaril.security.FirewallMode; import dev.silmaril.security.HookLabel; public class FirstClassification { public static void main(String[] args) throws Exception { var fw = Firewall.builder() .apiKey(System.getenv("SILMARIL_API_KEY")) .apiUrl(System.getenv("SILMARIL_API_URL")) .mode(FirewallMode.SHADOW) .build(); var result = fw.classify( "Summarize this release note.", ClassifyOptions.builder().hook(HookLabel.USER_INPUT).build() ); System.out.println(result.prediction()); } } ``` Set the application's main class to `FirstClassification`, then run it with your Gradle application task. See the [Java guide](/docs/sdk/java) for modes and error handling. ## Read the result For this sample input, expect `BENIGN`. Inspect any different verdict with the [finding review workflow](/docs/operate/findings). An API or network failure leaves the event without a verdict. When you enable Block, stop an event whose prediction is `MALICIOUS` or whose optional `governance.action` is `block`. TypeScript direct calls return the decision for your code to enforce. Python, Go, and Java use blocking errors in effective Block mode. See [modes and enforcement](/docs/guides/enforcement). ## Next steps [Verify your integration](/docs/start/verify) in the dashboard. Then connect the [framework](/docs/integrations/frameworks), [gateway](/docs/gateways/overview), or [execution boundaries](/docs/guides/events) your application uses. Reuse a conversation ID for ordered checks in one agent sequence. Read [events and conversations](/docs/guides/events) before adding conversation-aware or batch calls. For blocked events and failed calls, follow [enforcement guidance](/docs/guides/enforcement) and [troubleshooting](/docs/operate/troubleshooting). # Verify your integration Source: https://silmaril.dev/docs/start/verify Confirm traffic, connected boundaries, and enforcement before production rollout. Use a test environment and the deployment credentials supplied by Silmaril. Begin in Shadow or Warn mode so you can review would-block decisions while the workflow runs. ## Confirm traffic Send a benign request through your integration. Record its timestamp, request ID, conversation ID, and verdict. Open the supplied dashboard, select the matching Firewall and time window, and check **Monitoring** for traffic. Use available request and trace evidence to correlate the event. A traffic count establishes that requests arrived. Inspect a specific event to verify its hook, tool name, and deployment. ## Check each boundary | Integration | Check | | - | - | | Direct SDK | Each intended input, tool, and output path calls `classify()` and handles the result | | Framework | Model and tool callbacks run on the model or graph used by the application | | Deep Agents | Root and subagent checks run through protected constructors | | LiteLLM | Pre-call and post-call checks appear for the proxy traffic you intend to cover | | Vercel AI SDK or Gateway | Calls use the wrapped model and generated text passes the explicit output check | | Agent plugin | A new supported-agent session produces activity for its supported lifecycle hooks | For endpoint deployment, follow [macOS verification](/docs/macos/verify). ## Verify a block Arrange a controlled blocking test with Silmaril for your deployment. Run it through the same connected path in a test environment. In observation mode, confirm the would-block finding and continued execution. Enable Block for that boundary, repeat the test, and confirm the application stops or safely replaces the event before it is consumed. Review the [integration's enforcement behavior](/docs/guides/enforcement). Check the application's handling of an unavailable Firewall endpoint in the test environment. Confirm it follows the error policy you chose. ## Expand the rollout Review findings and legitimate workflows from the pilot. Keep the tested mode, credentials, and integration version in the deployment configuration. Expand one cohort or boundary at a time and repeat verification after changes. If blocking interrupts legitimate work, return the affected boundary to the previous mode while you [report the finding](/docs/operate/findings#report-a-false-positive). Keep unaffected protections in place.