Documentation
¶
Overview ¶
Package aifirewall wires local AI clients to the Vulnetix AI Firewall gateway and reconciles the org's gateway policy.
The gateway is an OpenAI-compatible proxy at
https://guardrails.vulnetix.com/{providerSlug}/{orgUuid}/v1
The client authenticates as the org with its Vulnetix API key; the org's real provider key is held server-side and swapped in by the gateway. So a client is "wired" when its base URL points at the gateway AND its API key is the Vulnetix key — either half alone is a misconfiguration, and one of them (base URL set, key not) silently sends the Vulnetix key to the provider.
Index ¶
- Constants
- Variables
- func AiderFile(o Options) (managedfile.File, bool)
- func Chmod600(path string) error
- func ClaudeCodeFile(o Options) (managedfile.File, bool)
- func ClaudeCodeSecretsFile(o Options) (managedfile.File, bool)
- func CodexFile(o Options) (managedfile.File, bool)
- func ContinueFile(o Options) (managedfile.File, bool)
- func ContinueSecretsFile(o Options) (managedfile.File, bool)
- func CreateProjectEnv(o Options) managedfile.File
- func EnvKeys(o Options) []string
- func EnvVars(o Options, valueSyntax string) []managedfile.KV
- func Errors(checks []Check) int
- func Export(org string, server ServerState, providerCatalog []string) ([]byte, error)
- func GatewayHost(gateway string) (string, error)
- func GatewayURL(gateway, slug, orgUUID string) string
- func InfoBaseURLEnv(slug string) string
- func Installed(c Client, scope Scope, home, root string) bool
- func ProjectEnvFiles(o Options) []managedfile.File
- func RenderSnippet(s Snippet, data SnippetData) (string, error)
- func SecretSafe(path string) error
- func ShellBlock(o Options, kind string) string
- func ShellFile(o Options) (path string, block string, err error)
- func SnippetLangs() []string
- func SnippetSDKs() []string
- func SupportsWire(gw *Gateway, c Client, providerSlug string) (bool, string)
- func ValidateGuardrail(name, ruleType, action, pattern string) error
- func Warnings(checks []Check) int
- type Baseline
- type BaselineGuardrail
- type BaselineSpec
- type Change
- type Check
- type Client
- type Detected
- type Gateway
- type Guardrail
- type GuardrailSpec
- type KeySourceSpec
- type Kind
- type ModelSpec
- type Op
- type Options
- type Paths
- type Policy
- type PolicyFile
- type PolicyMetadata
- type PolicySpec
- type Provider
- type ProviderSpec
- type Scope
- type ServerGuardrail
- type ServerState
- type SettingsSpec
- type Severity
- type Snippet
- type SnippetData
- type State
Constants ¶
const ( PolicyAPIVersion = "vulnetix.com/v1" PolicyKind = "AiFirewallPolicy" )
PolicyAPIVersion / PolicyKind identify the document.
const ( WireChat = "chat" // OpenAI /v1/chat/completions WireResponses = "responses" // OpenAI /v1/responses — what Codex requires WireMessages = "messages" // Anthropic /v1/messages — what Claude Code speaks )
Wire is a request format the gateway may or may not proxy for a provider.
const CodexProviderName = configEntryPrefix + "openai"
CodexProviderName is the [model_providers.<name>] table we own.
const ContinueModelName = "Vulnetix AI Firewall (openai)"
ContinueModelName is the models[] entry we own.
const DefaultGatewayURL = "https://guardrails.vulnetix.com"
DefaultGatewayURL is the hosted gateway.
const DefaultPolicyPath = ".vulnetix/ai-firewall.yaml"
DefaultPolicyPath is where the declarative policy lives in a repository.
const VulnetixKeyEnv = "VULNETIX_API_KEY"
VulnetixKeyEnv is the environment variable every wired client reads its Vulnetix API key from. Its value is referenced by config we write, never inlined, so a rc file or a committed settings.json never holds the secret.
Variables ¶
var Markers = managedfile.Markers{
Start: "# Vulnetix AI Firewall",
End: "# End Vulnetix AI Firewall",
}
Markers fence the AI Firewall's managed blocks. They differ from the Package Firewall's on purpose: both write the same shell rc, and each uninstall must strip only its own block.
Functions ¶
func AiderFile ¶
func AiderFile(o Options) (managedfile.File, bool)
AiderFile writes the OpenAI base URL and default model into .aider.conf.yml. The key is not written: aider reads OPENAI_API_KEY from the environment, which the shell and project-env writers already set.
func ClaudeCodeFile ¶
func ClaudeCodeFile(o Options) (managedfile.File, bool)
ClaudeCodeFile writes the Anthropic base URL (and the pinned model) into the `env` object of a Claude Code settings.json, folding into whatever else the file holds.
The auth token is not written here: settings.json is routinely committed, and a credential in it would be published with the repo. It comes from the shell block instead — or, with --embed-key, from settings.local.json, which is git-ignored by convention.
func ClaudeCodeSecretsFile ¶
func ClaudeCodeSecretsFile(o Options) (managedfile.File, bool)
ClaudeCodeSecretsFile holds the literal token, for --embed-key only.
func CodexFile ¶
func CodexFile(o Options) (managedfile.File, bool)
CodexFile folds a model provider into ~/.codex/config.toml.
The file is edited as text, never round-tripped through a TOML encoder: the encoder drops comments and reorders tables, so a user with a commented, hand-ordered config would get it silently rearranged. The result is parsed before it is written, so we never leave behind a config Codex cannot load.
func ContinueFile ¶
func ContinueFile(o Options) (managedfile.File, bool)
ContinueFile folds a model entry into ~/.continue/config.yaml.
Edited through the yaml Node API so comments and key order survive; a map[string]any round-trip would silently discard both.
func ContinueSecretsFile ¶
func ContinueSecretsFile(o Options) (managedfile.File, bool)
ContinueSecretsFile is the one place a literal key is unavoidable: Continue runs inside an IDE and cannot read the shell environment.
func CreateProjectEnv ¶
func CreateProjectEnv(o Options) managedfile.File
CreateProjectEnv renders a .env from scratch, for --create-env.
func EnvKeys ¶
EnvKeys is every variable name EnvVars can produce for the targets, which is what uninstall must remove.
func EnvVars ¶
func EnvVars(o Options, valueSyntax string) []managedfile.KV
EnvVars renders the environment variables that wire the targeted providers.
A provider with no base-URL variable that any SDK reads still gets an informational VULNETIX_AIFW_<P>_BASE_URL: nothing consumes it, but it records the gateway URL for that provider so `status` can see the intent and so the value is to hand when writing base_url into code. It is deliberately NOT named MISTRAL_BASE_URL or similar — a variable that looks like an SDK setting but is read by nothing would leave the user believing their traffic was proxied when it went straight to the provider.
func Export ¶
func Export(org string, server ServerState, providerCatalog []string) ([]byte, error)
Export renders the server's current policy as a policy file. It never writes a key: the server does not return one, and a file in a repository is the last place a credential should be.
func GatewayHost ¶
GatewayHost is the host part of a gateway URL, used to decide whether a base URL found in a client's config points at us or somewhere else.
func GatewayURL ¶
GatewayURL builds the base URL a client points at: the gateway, the provider slug, the org, and — for the OpenAI-shaped SDKs — the version segment.
The two SDK families disagree about who owns the "/v1", and getting it wrong produces a 404 on a path with "/v1/v1/" in it that is baffling to debug:
- OpenAI's base_url INCLUDES the version ("https://api.openai.com/v1"); the SDK appends only "/chat/completions" or "/responses".
- Anthropic's base_url is the bare ROOT ("https://api.anthropic.com"); the SDK appends "/v1/messages" itself.
So the version segment is appended for every provider except the ones whose clients speak the Anthropic wire.
func InfoBaseURLEnv ¶
InfoBaseURLEnv is the variable name used to record the gateway URL of a provider that no SDK can be pointed at with environment variables. Nothing reads it; it exists so `ai-firewall status` can tell "this org uses Mistral through the gateway" from "this org has never heard of Mistral", and so the value is to hand when you write the base_url into code.
func Installed ¶
Installed reports whether a client appears to be present on this machine: its config directory exists, or its binary is on PATH.
func ProjectEnvFiles ¶
func ProjectEnvFiles(o Options) []managedfile.File
ProjectEnvFiles returns the project env files that exist at the git root, with the block to splice into each. Files that do not exist are not created: a .env this tool invented would not be loaded by anything the project already runs.
func RenderSnippet ¶
func RenderSnippet(s Snippet, data SnippetData) (string, error)
RenderSnippet produces ready-to-run boilerplate wired to the gateway.
func SecretSafe ¶
SecretSafe reports whether it is safe to write a literal credential to path: the file must be covered by the repository's ignore rules. A key written to a tracked file is a key that gets committed.
func ShellBlock ¶
ShellBlock renders the managed block for the user's shell rc.
func SnippetLangs ¶
func SnippetLangs() []string
SnippetLangs / SnippetSDKs power shell completion and the error message on a bad pair, from the one registry.
func SnippetSDKs ¶
func SnippetSDKs() []string
func SupportsWire ¶
SupportsWire reports whether the gateway can serve the wire this client speaks, for the given provider. An absent capability advertisement means the server is older than this check: assume chat only, and refuse to write a config for a client that needs something else, rather than writing one that fails at request time with a 404 the user has to reverse-engineer.
func ValidateGuardrail ¶
ValidateGuardrail checks one rule against what the gateway can enforce.
Types ¶
type Baseline ¶
type Baseline struct {
Version string `json:"version" yaml:"version"`
Ref string `json:"ref" yaml:"ref"`
Guardrails []BaselineGuardrail `json:"guardrails" yaml:"guardrails"`
}
Baseline is the recommended guardrail set — PII masking, prompt injection, and so on. It is supplied by the server, not shipped in the binary, so the recommendations can improve without a CLI release. It never contains provider or model allow/deny lists: what an org is permitted to call is the org's decision, not a default we push.
func LoadBaselineFile ¶
LoadBaselineFile reads a baseline from a local JSON or YAML file (--catalog), which replaces the server's set entirely.
type BaselineGuardrail ¶
type BaselineGuardrail struct {
ID string `json:"id" yaml:"id"` // stable; the key for excludes
Name string `json:"name" yaml:"name"`
Description string `json:"description" yaml:"description"`
RuleType string `json:"ruleType" yaml:"ruleType"`
Action string `json:"action" yaml:"action"`
Pattern string `json:"pattern" yaml:"pattern"`
Priority int `json:"priority" yaml:"priority"`
Enabled bool `json:"enabled" yaml:"enabled"`
Tags []string `json:"tags" yaml:"tags"`
Severity string `json:"severity" yaml:"severity"`
}
BaselineGuardrail is one recommended rule. The enums are identical to a guardrail's, so a baseline entry applies through the ordinary guardrail write path with no special casing.
type BaselineSpec ¶
type Change ¶
type Change struct {
Kind Kind `json:"kind"`
Op Op `json:"op"`
Target string `json:"target"`
Detail string `json:"detail,omitempty"`
// Populated for execution.
Guardrail *GuardrailSpec `json:"-"`
Model *ModelSpec `json:"-"`
Provider *ProviderSpec `json:"-"`
UUID string `json:"-"`
Enable *bool `json:"-"`
}
Change is one server mutation apply intends to make.
func Plan ¶
func Plan(desired PolicyFile, server ServerState) []Change
Plan diffs desired against current and returns the changes in execution order.
Guardrails go first, then models, then providers, then keys, then settings. A policy that is being tightened must never pass through a state where a provider is enabled and the guardrails that constrain it are not yet in place.
type Check ¶
type Check struct {
ID string `json:"id"`
Severity Severity `json:"severity"`
Client string `json:"client,omitempty"`
Message string `json:"message"`
}
Check is one finding about the local setup versus the org's policy.
type Client ¶
type Client struct {
ID string
DisplayName string
// Manual clients have no user-editable config for the API base URL — the
// setting lives in application state. We detect them and tell the user where
// to paste the URL; we never write. Fabricating a config file for these would
// produce a file the application ignores, and a status check that lies.
Manual bool
// Wire is the request format this client speaks. A client can only be wired
// if the gateway proxies this wire for the target provider: Codex speaks the
// OpenAI Responses API (its config no longer accepts any other wire_api), and
// Claude Code speaks the Anthropic Messages API. Empty means the client is
// happy with whatever the provider's SDK speaks.
Wire string
// Providers restricts the client to these provider slugs. Empty means every
// provider that can be wired by environment variable.
Providers []string
// DefaultScope is used when --scope is not given.
DefaultScope Scope
}
Client is a local consumer of an AI API that can be pointed at the gateway.
func ClientByID ¶
ClientByID looks up a client by its subcommand name.
type Detected ¶
type Detected struct {
Client Client
Scope Scope
Path string
State State
BaseURL string // whatever base URL the client is configured with, if any
Model string // the model it has pinned, if any
// KeyEnvSet records, per provider API-key variable, whether the environment
// holds a value. Only names and set/unset are recorded — never the value.
KeyEnvSet map[string]bool
// WiredProviders holds the providers this client actually points at the
// gateway for. The shell can be wired for one provider and not another, and a
// provider nobody has wired must not produce findings about its key.
WiredProviders map[string]bool
}
Detected is one client's local configuration.
type Gateway ¶
Gateway is the capability advertisement, mirrored from the API response so this package does not depend on the vdb client.
type GuardrailSpec ¶
type GuardrailSpec struct {
Name string `yaml:"name"`
BaselineID string `yaml:"baselineId,omitempty"`
RuleType string `yaml:"ruleType"`
Action string `yaml:"action"`
Pattern string `yaml:"pattern"`
Priority int `yaml:"priority,omitempty"`
Enabled *bool `yaml:"enabled,omitempty"`
}
func ComposeGuardrails ¶
func ComposeGuardrails(spec []GuardrailSpec, baseline *Baseline, exclude []string) []GuardrailSpec
ComposeGuardrails merges the baseline into the file's own guardrails.
A local guardrail with the same name always wins — the file is the org's considered position, the baseline is a recommendation. Excluded ids are dropped. The result is the desired guardrail set.
func (GuardrailSpec) IsEnabled ¶
func (g GuardrailSpec) IsEnabled() bool
IsEnabled defaults to true: a guardrail you wrote down is one you meant.
type KeySourceSpec ¶
type KeySourceSpec struct {
FromEnv string `yaml:"fromEnv,omitempty"`
FromFile string `yaml:"fromFile,omitempty"`
}
KeySourceSpec names where the provider key comes from. Never the key itself: a credential in a file that lives in a repository is a credential that gets committed.
type Options ¶
type Options struct {
Gateway string // https://guardrails.vulnetix.com
OrgUUID string
APIKey string // the Vulnetix API key; only written when EmbedKey
// Targets are the providers to wire, already filtered to those the org has a
// BYOK key for.
Targets []Provider
// Model pins a default model in the agent configs that take one.
Model string
// EmbedKey writes the literal API key instead of a reference to
// $VULNETIX_API_KEY. Opt-in, because it puts a credential in a file.
EmbedKey bool
Home string
Root string // git root, "" when not in a repository
Scope Scope
}
Options is everything a writer needs to render a client's config.
func (Options) ScopeOrDefault ¶
ScopeOrDefault is the requested scope, or the client's default.
type Paths ¶
type Paths struct {
// Config is the file we write, or would write.
Config string
// Secrets is a second file that must hold the API key, when the client cannot
// read it from the environment. Empty for every client that can.
Secrets string
// Detect are additional paths whose presence means the client is installed,
// even when Config does not exist yet.
Detect []string
}
Paths locates a client's config for a scope. home is $HOME; root is the git root ("" when not in a repository). The returned paths may not exist.
type Policy ¶
type Policy struct {
// ProviderAction maps a provider slug to "allow", "deny", or "" (default).
ProviderAction map[string]string
// ProviderHasKey maps a provider slug to whether a BYOK key is stored.
ProviderHasKey map[string]bool
// ModelAction maps "provider/model" to "allow" or "deny".
ModelAction map[string]string
// AllowlistMode holds the providers with at least one allow entry. For those,
// a model that is not on the list is refused — the single most surprising
// semantic in the product, and the reason a "not denied" model can still fail.
AllowlistMode map[string]bool
Guardrails []Guardrail
Gateway *Gateway
}
Policy is the org's gateway policy, in the shape the checks need.
type PolicyFile ¶
type PolicyFile struct {
APIVersion string `yaml:"apiVersion"`
Kind string `yaml:"kind"`
Metadata PolicyMetadata `yaml:"metadata,omitempty"`
Spec PolicySpec `yaml:"spec"`
}
PolicyFile is the declarative form of the org's AI Firewall policy.
func LoadPolicyFile ¶
func LoadPolicyFile(path string) (*PolicyFile, error)
LoadPolicyFile reads and validates a policy document.
func (*PolicyFile) Validate ¶
func (pf *PolicyFile) Validate() error
Validate rejects a document the gateway would refuse, or silently misapply.
type PolicyMetadata ¶
type PolicyMetadata struct {
// Org guards against applying one org's policy to another — the commonest way
// to do real damage with a config file. Apply refuses on a mismatch.
Org string `yaml:"org,omitempty"`
}
type PolicySpec ¶
type PolicySpec struct {
// Prune lets apply delete server objects this file does not mention. Off by
// default: a CLI apply must not silently destroy guardrails someone created
// in the dashboard.
Prune bool `yaml:"prune,omitempty"`
Settings *SettingsSpec `yaml:"settings,omitempty"`
Baseline *BaselineSpec `yaml:"baseline,omitempty"`
Providers []ProviderSpec `yaml:"providers,omitempty"`
Models []ModelSpec `yaml:"models,omitempty"`
Guardrails []GuardrailSpec `yaml:"guardrails,omitempty"`
}
type Provider ¶
type Provider struct {
Slug string
DisplayName string
// APIKeyEnv is the variable an SDK reads its credential from. For a wired
// client this must hold the Vulnetix API key, not the provider's.
APIKeyEnv string
// BaseURLEnv lists the variables that SDKs genuinely honour for overriding
// the API host — verified, not guessed. An empty list means no SDK reads an
// environment variable for this provider's base URL, so the only way to route
// it through the gateway is to set base_url in code (see `ai-firewall
// snippet`). Inventing a plausible-looking variable here would be worse than
// useless: the user would believe they were protected while their traffic
// went straight to the provider.
BaseURLEnv []string
// Wire is the request format this provider's SDKs speak.
Wire string
// Verified records whether the BaseURLEnv list was confirmed against the
// SDK's source, as opposed to its documentation.
Verified bool
}
Provider is one upstream the gateway can proxy, plus the local knowledge the server does not have: which environment variables real SDKs actually read.
func ProviderBySlug ¶
ProviderBySlug looks up a provider by its gateway path segment.
type ProviderSpec ¶
type ProviderSpec struct {
Slug string `yaml:"slug"`
Action string `yaml:"action"` // allow | deny | default
Key *KeySourceSpec `yaml:"key,omitempty"`
}
type ServerGuardrail ¶
type ServerState ¶
type ServerState struct {
Providers map[string]string // slug -> action ("" = default)
HasKey map[string]bool
Models map[string]string // "provider/model" -> action
Guardrails map[string]ServerGuardrail
LogsEnabled bool
}
ServerState is the current policy, in the shape the planner needs.
type SettingsSpec ¶
type SettingsSpec struct {
LogsEnabled *bool `yaml:"logsEnabled,omitempty"`
}
type Snippet ¶
type Snippet struct {
Lang string
SDK string
File string
// Provider pins the snippet to one provider when the SDK only speaks to it.
Provider string
// Wire is the request format the SDK in this snippet speaks. A snippet cannot
// be rendered for a provider that does not serve it — an OpenAI-shaped call
// against Anthropic would be built from the wrong base URL and 404.
Wire string
// Note explains why this snippet exists, when the reason is not obvious.
Note string
}
Snippet is one language/SDK pair we can emit boilerplate for.
func FindSnippet ¶
FindSnippet resolves a lang/SDK pair.
type SnippetData ¶
type SnippetData struct {
GatewayURL string
Provider string
OrgUUID string
Model string
KeyEnv string
}
SnippetData is what a template renders against.
type State ¶
type State string
State is what we found on disk for one client.
const ( StateWired State = "wired" // points at this org's gateway // StateElsewhere is the finding that matters: the client is configured with a // base URL, and it is not ours. Traffic that the user believes is being // screened is going straight to the provider. StateElsewhere State = "points elsewhere" StateNotWired State = "not wired" StateManual State = "manual" // no config file exists to write StateAbsent State = "not installed" // the client is not on this machine )