Build With The Firewall
Silmaril is an AI application firewall that protects agent execution.
Silmaril evaluates intent, application context, tool calls, and accumulated execution state together before harmful outcomes materialize. Personalized security comes from the firewall lifecycle. Traces, data flows, and threat-hunting discoveries feed defenses tuned to the system they protect.
We recommend one Silmaril firewall instance per system. Send classification requests from the layer that controls model execution. Use an SDK, a framework handler, a LiteLLM guardrail, or an AI gateway.
Silmaril Firewall is not self-serve yet. Book a call with the Silmaril team to provision credentials, choose a deployment path, and receive the endpoint used by these examples.
Quick Start
Pick the runtime that owns the inference call. Each panel shows the direct SDK path first, then the framework and gateway integrations that apply to that language.
SDK 0.5 sends each sanitized classification input as one complete event, preserves the exact metadata.conversationId value for sequence identity, and adds one metadata.silmaril.request_id per event.
Use the TypeScript SDK from services that own inference, tool execution, or orchestration boundaries.
Install the core SDK for direct classify calls.
npm install @silmaril-security/sdk@0.5.0Create one firewall client with the API key and classify endpoint issued by Silmaril.
import { Firewall } from "@silmaril-security/sdk";
const fw = new Firewall({
apiKey: process.env.SILMARIL_API_KEY!,
apiUrl: process.env.SILMARIL_API_URL!,
});Send the hook and tool name with each call so the firewall sees the execution state in context.
import { HookLabel } from "@silmaril-security/sdk";
const result = await fw.classify(userInput, {
hook: HookLabel.USER_INPUT,
});
await fw.classify(toolOutput, {
hook: HookLabel.TOOL_RESPONSE,
toolName: "read_file",
});Branch on typed outcome constants to redact secrets, review private data, deny control abuse, escalate compromise, block disruption, or continue on benign results.
import { HookLabel, Outcome } from "@silmaril-security/sdk";
const result = await fw.classify(text, {
hook: HookLabel.TOOL_RESPONSE,
toolName: "read_file",
});
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);
}benignNo harmful firewall outcome detected.information_disclosurePrivate data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.secret_exposureCredentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.control_abuseMisuse of authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.system_compromisePrivilege escalation, account takeover, hostile integration/plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.service_disruptionDowntime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.Add the AI SDK packages only when Vercel owns model execution. Gateway routing and middleware can share the same firewall client.
npm install ai@^5
npm install @ai-sdk/openaiimport { wrapLanguageModel, generateText } from "ai";
import { openai } from "@ai-sdk/openai";
const model = wrapLanguageModel({
model: openai("gpt-5.4"),
middleware: fw.asMiddleware({ scanOutput: true }),
});
await generateText({ model, prompt: userInput });import { gateway, generateText, wrapLanguageModel } from "ai";
const model = wrapLanguageModel({
model: gateway("anthropic/claude-sonnet-4.6"),
middleware: fw.asMiddleware({ scanOutput: true }),
});
await generateText({
model,
prompt: userInput,
providerOptions: {
gateway: { tags: ["surface:assistant"] },
},
});LangGraph.js can use the same LangChain callback handler through graph invocation config.
npm install @langchain/core @langchain/openaiimport { ChatOpenAI } from "@langchain/openai";
const handler = await fw.asLangChainHandler();
const model = new ChatOpenAI({ callbacks: [handler] });
await model.invoke("Hello");const handler = await fw.asLangChainHandler();
await graph.invoke(state, {
callbacks: [handler],
});Use shadow mode to measure would-block decisions with the same thresholds while traffic continues. Override individual adapters when a surface is ready to enforce. Direct classify calls return verdicts either way.
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) {
telemetry.track("firewall.would_block", {
hook: event.hook,
score: event.result.score,
});
}
},
});
const enforcingHandler = await fw.asLangChainHandler({
shadowMode: false,
});Framework adapters throw typed block exceptions in enforcement mode.
import { FirewallBlockedException } from "@silmaril-security/sdk";
try {
await generateText({ model, prompt: userInput });
} catch (err) {
if (err instanceof FirewallBlockedException) {
audit({ score: err.score, threshold: err.threshold });
return;
}
throw err;
}Use the Python SDK from services that own inference, tool execution, or orchestration boundaries.
Install the core SDK for direct classify calls.
pip install silmaril-security-sdk==0.5.0Create one firewall client with the API key and classify endpoint issued by Silmaril.
import os
from silmaril_security.sdk import Firewall
fw = Firewall(
api_key=os.environ["SILMARIL_API_KEY"],
api_url=os.environ["SILMARIL_API_URL"],
)Classify user input, tool output, model output, or system prompt content with explicit hook labels.
from silmaril_security.sdk import HookLabel
result = fw.classify(
user_input,
hook=HookLabel.USER_INPUT,
)
fw.classify(
tool_output,
hook=HookLabel.TOOL_RESPONSE,
tool_name="read_file",
)Use shadow mode for direct routing, or catch block exceptions and read the result, then branch on exported outcome constants.
from silmaril_security.sdk import (
HookLabel,
OUTCOME_CONTROL_ABUSE,
OUTCOME_INFORMATION_DISCLOSURE,
OUTCOME_SECRET_EXPOSURE,
OUTCOME_SERVICE_DISRUPTION,
OUTCOME_SYSTEM_COMPROMISE,
)
result = fw.classify(
text,
hook=HookLabel.TOOL_RESPONSE,
tool_name="read_file",
shadow_mode=True,
)
if result.prediction == "BENIGN":
continue_normally(result)
elif result.primary_outcome == OUTCOME_SECRET_EXPOSURE:
redact_and_suppress(result)
elif result.primary_outcome == OUTCOME_INFORMATION_DISCLOSURE:
require_review_before_returning_private_data(result)
elif result.primary_outcome == OUTCOME_CONTROL_ABUSE:
deny_and_request_explicit_confirmation(result)
elif result.primary_outcome == OUTCOME_SYSTEM_COMPROMISE:
security_log(result)
block_and_escalate(result)
elif result.primary_outcome == OUTCOME_SERVICE_DISRUPTION:
block_destructive_or_disruptive_action(result)
else:
block_by_default(result)benignNo harmful firewall outcome detected.information_disclosurePrivate data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.secret_exposureCredentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.control_abuseMisuse of authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.system_compromisePrivilege escalation, account takeover, hostile integration/plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.service_disruptionDowntime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.LangGraph can pass the same callback handler through runnable config.
pip install "silmaril-security-sdk[langchain]==0.5.0" langchain-openaifrom langchain_openai import ChatOpenAI
handler = fw.as_langchain_handler()
model = ChatOpenAI(callbacks=[handler])
model.invoke("Hello")handler = fw.as_langchain_handler()
graph.invoke(
state,
config={"callbacks": [handler]},
)Use shadow mode to return classification results while suppressing block exceptions. Per-call overrides let one boundary enforce before changing the client 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,
)
fw.classify(text, hook=HookLabel.TOOL_RESPONSE, shadow_mode=False)Direct Python calls raise typed block exceptions in enforcement mode.
from silmaril_security.sdk import FirewallBlockedException
try:
fw.classify(user_input, hook=HookLabel.USER_INPUT)
except FirewallBlockedException as exc:
audit({"score": exc.score, "threshold": exc.threshold})Use the Go SDK from services that own inference, tool execution, or orchestration boundaries.
Add the firewall module and import the firewall package from application code.
go get github.com/Silmaril-Security/sdk-go/firewall@v0.5.0Create one firewall client with the API key and classify endpoint issued by Silmaril.
fw, err := firewall.New(firewall.Options{
APIKey: os.Getenv("SILMARIL_API_KEY"),
APIURL: os.Getenv("SILMARIL_API_URL"),
})
if err != nil { log.Fatal(err) }Pass hook metadata with each call so server decisions line up with where the text came from.
result, err := fw.Classify(ctx, userInput,
firewall.WithHook(firewall.HookUserInput),
)
_, err = fw.Classify(ctx, toolOutput,
firewall.WithHook(firewall.HookToolResponse),
firewall.WithToolName("read_file"),
)
_ = resultUse shadow mode for direct routing, or catch typed blocking errors and read the result, then branch on exported outcome constants.
result, err := fw.Classify(ctx, text,
firewall.WithHook(firewall.HookToolResponse),
firewall.WithToolName("read_file"),
firewall.WithShadowMode(true),
)
if err != nil {
return err
}
if result.Prediction == firewall.PredictionBenign {
return continueNormally(result)
}
switch result.PrimaryOutcome {
case firewall.OutcomeSecretExposure:
return redactAndSuppress(result)
case firewall.OutcomeInformationDisclosure:
return requireReviewBeforeReturningPrivateData(result)
case firewall.OutcomeControlAbuse:
return denyAndRequestExplicitConfirmation(result)
case firewall.OutcomeSystemCompromise:
securityLog(result)
return blockAndEscalate(result)
case firewall.OutcomeServiceDisruption:
return blockDestructiveOrDisruptiveAction(result)
default:
return blockByDefault(result)
}benignNo harmful firewall outcome detected.information_disclosurePrivate data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.secret_exposureCredentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.control_abuseMisuse of authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.system_compromisePrivilege escalation, account takeover, hostile integration/plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.service_disruptionDowntime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.Use shadow mode to keep verdict telemetry flowing while typed blocking errors are suppressed. Per-call options can enforce one boundary before changing the client 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)
}
},
})
_, err = fw.Classify(ctx, text,
firewall.WithHook(firewall.HookToolResponse),
firewall.WithShadowMode(false),
)Enforcement returns typed blocking errors so live code can branch cleanly on block decisions.
var blocked *firewall.FirewallBlockedError
if errors.As(err, &blocked) {
log.Printf("blocked score=%.4f threshold=%.4f", blocked.Score, blocked.Threshold)
return
}Use the Java SDK from services that own inference, tool execution, or orchestration boundaries.
Add the SDK dependency to the service that calls the firewall.
dependencies {
implementation("com.silmaril.security:silmaril-security-sdk:0.5.0")
}Create one firewall client with the API key and classify endpoint issued by Silmaril.
import dev.silmaril.security.Firewall;
Firewall fw = Firewall.builder()
.apiKey(System.getenv("SILMARIL_API_KEY"))
.apiUrl(System.getenv("SILMARIL_API_URL"))
.build();Keep hook metadata close to the boundary being evaluated.
import dev.silmaril.security.HookLabel;
import dev.silmaril.security.ClassifyOptions;
BlockResult result = fw.classify(
userInput,
ClassifyOptions.builder()
.hook(HookLabel.USER_INPUT)
.build()
);
fw.classify(
toolOutput,
ClassifyOptions.builder()
.hook(HookLabel.TOOL_RESPONSE)
.toolName("read_file")
.build()
);Use shadow mode for direct routing, or catch typed blocking exceptions and read the result, then branch on Java outcome constants.
import dev.silmaril.security.Outcome;
import dev.silmaril.security.Prediction;
BlockResult result = fw.classify(
text,
ClassifyOptions.builder()
.hook(HookLabel.TOOL_RESPONSE)
.toolName("read_file")
.shadowMode(true)
.build()
);
if (result.prediction() == Prediction.BENIGN) {
return continueNormally(result);
}
switch (result.primaryOutcome()) {
case Outcome.SECRET_EXPOSURE:
return redactAndSuppress(result);
case Outcome.INFORMATION_DISCLOSURE:
return requireReviewBeforeReturningPrivateData(result);
case Outcome.CONTROL_ABUSE:
return denyAndRequestExplicitConfirmation(result);
case Outcome.SYSTEM_COMPROMISE:
securityLog(result);
return blockAndEscalate(result);
case Outcome.SERVICE_DISRUPTION:
return blockDestructiveOrDisruptiveAction(result);
default:
return blockByDefault(result);
}benignNo harmful firewall outcome detected.information_disclosurePrivate data, documents, internal context, logs, traces, customer data, SQL rows, topology, or similar non-secret sensitive information.secret_exposureCredentials, tokens, API keys, cookies, passwords, signing keys, OAuth secrets, session material, or webhook secrets.control_abuseMisuse of authorized tools or user privileges to send, change, approve, delete, operate, or bypass policy/RBAC without a stronger outcome.system_compromisePrivilege escalation, account takeover, hostile integration/plugin takeover, persistence, lateral movement, attacker webhook registration, or code/plugin execution.service_disruptionDowntime, lockout, degradation, alert suppression, destructive loops, resource exhaustion, cost spikes, or hidden outage evidence.Keep verdict telemetry flowing while blocking exceptions are suppressed. Per-call options can enforce one boundary before changing the client default.
Firewall fw = Firewall.builder()
.apiKey(System.getenv("SILMARIL_API_KEY"))
.apiUrl(System.getenv("SILMARIL_API_URL"))
.shadowMode(true)
.onClassify(event -> {
if (event.blocked() && event.shadowMode()) {
telemetry.track("firewall.would_block", event.hook(), event.result().score());
}
})
.build();
fw.classify(
text,
ClassifyOptions.builder()
.hook(HookLabel.TOOL_RESPONSE)
.shadowMode(false)
.build()
);Treat block decisions as typed exceptions so application control flow stays explicit.
try {
fw.classify(userInput, options);
} catch (FirewallBlockedException blocked) {
audit(blocked.score(), blocked.threshold());
return;
}Secure Endpoints
Secure the endpoints where coding agents use repository, shell, and tool access.
Install the plugin for the runtime you use. Silmaril watches prompts, tool calls, command output, and generated code at the agent boundary.
Investigate Firewall Evidence
The hosted Silmaril Firewall MCP server gives supported MCP clients read-only, tenant-scoped access to the Firewall evidence your account is authorized to see. It reads through Silmaril's evidence API and does not connect directly to your cloud account, database, or runtime infrastructure.
You need a Silmaril customer account, access to your tenant's Silmaril organization, at least one authorized Firewall deployment, and an MCP client that supports hosted OAuth discovery. Add the server in Codex with one command:
codex mcp add silmaril-firewall --url https://firewall-mcp.silmaril.dev/mcpSilmaril login opens in your browser when the client connects. Choose the customer organization you normally use for Silmaril. You do not need to paste tokens, configure OAuth fields, or provide cloud credentials.
Recommended workflow
Start broad, then narrow to the evidence a review actually needs.
Map deployments and capabilities with list_firewalls, get_firewall, and get_schema.
Summarize posture with get_metrics, get_finding_totals, group_findings, and list_findings.
Rank repeated abuse with list_suspicious_users and build compact context with get_investigation_packet.
Use restricted get_finding or get_finding_trace only when compact evidence is insufficient and your account has detail access.
Starter prompts
Start with 01, then replace the ID placeholders with returned values.
List the firewalls I can access.Summarize traffic, findings, and severity for <FIREWALL_ID> over the last 24 hours.Show the five highest-risk findings for <FIREWALL_ID> from the last 24 hours and cite finding and evidence IDs.Build an investigation packet for <FINDING_ID> in <FIREWALL_ID> with key evidence and next steps.Evidence safety: 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.
LiteLLM Guardrails
If LiteLLM already routes model traffic, add Silmaril as a guardrail on the proxy. Pre-call and post-call checks cover requests, responses, messages, and tool activity without changing application code.
Use warn mode while validating proxy coverage. Silmaril still evaluates the payload and returns warning metadata for would-block traffic, but LiteLLM keeps the call moving. Switch to enforcement when the gateway path is ready to stop unsafe traffic.
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: warnContainer Deployments
For customer-controlled deployments, run the firewall service and model as containers in the cloud account that holds the protected workload. SDKs use the deployed classify endpoint. LiteLLM uses the guardrail endpoint from the same stage.