aifirewall

package
v3.71.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 23, 2026 License: AGPL-3.0 Imports: 15 Imported by: 0

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

View Source
const (
	PolicyAPIVersion = "vulnetix.com/v1"
	PolicyKind       = "AiFirewallPolicy"
)

PolicyAPIVersion / PolicyKind identify the document.

View Source
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.

View Source
const CodexProviderName = configEntryPrefix + "openai"

CodexProviderName is the [model_providers.<name>] table we own.

View Source
const ContinueModelName = "Vulnetix AI Firewall (openai)"

ContinueModelName is the models[] entry we own.

View Source
const DefaultGatewayURL = "https://guardrails.vulnetix.com"

DefaultGatewayURL is the hosted gateway.

View Source
const DefaultPolicyPath = ".vulnetix/ai-firewall.yaml"

DefaultPolicyPath is where the declarative policy lives in a repository.

View Source
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

View Source
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 Chmod600

func Chmod600(path string) error

Chmod600 tightens a file that holds a credential.

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

func EnvKeys(o Options) []string

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 Errors

func Errors(checks []Check) int

Errors counts the findings that mean requests are failing right now.

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

func GatewayHost(gateway string) (string, error)

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

func GatewayURL(gateway, slug, orgUUID string) string

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:

So the version segment is appended for every provider except the ones whose clients speak the Anthropic wire.

func InfoBaseURLEnv

func InfoBaseURLEnv(slug string) string

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

func Installed(c Client, scope Scope, home, root string) bool

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

func SecretSafe(path string) error

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

func ShellBlock(o Options, kind string) string

ShellBlock renders the managed block for the user's shell rc.

func ShellFile

func ShellFile(o Options) (path string, block string, err error)

ShellFile locates the shell rc and renders its block.

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

func SupportsWire(gw *Gateway, c Client, providerSlug string) (bool, string)

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

func ValidateGuardrail(name, ruleType, action, pattern string) error

ValidateGuardrail checks one rule against what the gateway can enforce.

func Warnings

func Warnings(checks []Check) int

Warnings counts the non-fatal findings.

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

func LoadBaselineFile(path string) (*Baseline, error)

LoadBaselineFile reads a baseline from a local JSON or YAML file (--catalog), which replaces the server's set entirely.

func (*Baseline) Compile

func (b *Baseline) Compile() error

Compile validates every entry. The whole baseline is rejected on a single bad pattern rather than applied in part: half a security baseline, silently, is worse than none — the org would believe it had the full set.

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 BaselineSpec struct {
	Enabled bool     `yaml:"enabled"`
	Ref     string   `yaml:"ref,omitempty"`
	Exclude []string `yaml:"exclude,omitempty"` // baseline guardrail ids
}

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 Mutating

func Mutating(changes []Change) []Change

Mutating reports whether the plan actually changes anything on the server.

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.

func RunChecks

func RunChecks(pol Policy, detected []Detected, gatewayHost string) []Check

RunChecks compares what is configured locally against what the gateway will enforce, and reports every way a request is going to fail — or silently not be screened at all.

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

func ClientByID(id string) (Client, bool)

ClientByID looks up a client by its subcommand name.

func Clients

func Clients() []Client

Clients returns the client registry.

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.

func Detect

func Detect(o Options, gatewayHost string) []Detected

Detect inspects every client and reports how it is configured.

type Gateway

type Gateway struct {
	BaseURL  string
	WireAPIs map[string][]string
}

Gateway is the capability advertisement, mirrored from the API response so this package does not depend on the vdb client.

type Guardrail

type Guardrail struct {
	Name     string
	RuleType string
	Pattern  string
	Enabled  bool
	Priority int
}

Guardrail is one rule, in the shape the checks need.

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 Kind

type Kind string

Kind is which policy object a Change touches.

const (
	KindGuardrail Kind = "guardrail"
	KindModel     Kind = "model"
	KindProvider  Kind = "provider"
	KindKey       Kind = "key"
	KindSettings  Kind = "settings"
)

type ModelSpec

type ModelSpec struct {
	Slug        string `yaml:"slug"`
	Provider    string `yaml:"provider,omitempty"`
	AnyProvider bool   `yaml:"anyProvider,omitempty"`
	Action      string `yaml:"action"` // allow | deny
}

type Op

type Op string

Op is what a Change does to the server.

const (
	OpCreate Op = "create"
	OpUpdate Op = "update"
	OpDelete Op = "delete"
	OpDrift  Op = "drift" // present on the server, absent from the file, not pruned
)

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) BaseURL

func (o Options) BaseURL(slug string) string

BaseURL is the gateway URL for one provider.

func (Options) HasTarget

func (o Options) HasTarget(slug string) bool

HasTarget reports whether a provider is among those being wired.

func (Options) ScopeOrDefault

func (o Options) ScopeOrDefault(def Scope) Scope

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.

func ClientPaths

func ClientPaths(c Client, scope Scope, home, root string) Paths

ClientPaths resolves where a client keeps its configuration.

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.

func (Policy) ModelAllowed

func (p Policy) ModelAllowed(provider, model string) (bool, string)

ModelAllowed reports whether a model would pass policy for a provider, and why not if it would not. The empty model is "no opinion".

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

func ProviderBySlug(slug string) (Provider, bool)

ProviderBySlug looks up a provider by its gateway path segment.

func Providers

func Providers() []Provider

Providers returns the local provider registry.

func (Provider) EnvWired

func (p Provider) EnvWired() bool

EnvWired reports whether pointing this provider at the gateway can be done with environment variables alone.

type ProviderSpec

type ProviderSpec struct {
	Slug   string         `yaml:"slug"`
	Action string         `yaml:"action"` // allow | deny | default
	Key    *KeySourceSpec `yaml:"key,omitempty"`
}

type Scope

type Scope string

Scope is where a client's config lives.

const (
	ScopeUser    Scope = "user"    // under $HOME, applies everywhere
	ScopeProject Scope = "project" // under the git root, applies to this repo
)

type ServerGuardrail

type ServerGuardrail struct {
	UUID     string
	RuleType string
	Action   string
	Pattern  string
	Priority int
	Enabled  bool
}

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 Severity

type Severity string

Severity of a check finding.

const (
	SeverityError Severity = "error" // requests will fail, or policy is not enforced
	SeverityWarn  Severity = "warn"
)

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

func FindSnippet(lang, sdk string) (Snippet, error)

FindSnippet resolves a lang/SDK pair.

func Snippets

func Snippets() []Snippet

Snippets returns the registry.

func (Snippet) Supports

func (s Snippet) Supports(p Provider) bool

Supports reports whether this snippet can be rendered for a provider.

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
)

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL