Helix — Advanced Security Guide¶
Secure your AI agents, RAG pipelines, and integration workflows with enterprise-grade security. This guide covers authentication (JWT, OAuth2, API Key, BasicAuth), transport security (mTLS), secret management (Vault, AWS Secrets Manager), rate limiting, IP filtering, CORS, multi-tenancy, and production hardening.
All cryptographic operations use the Go standard library (crypto/*) and
golang.org/x/crypto/bcrypt. No third-party JWT or OAuth libraries are
required.
AI Agent Security¶
Beyond workflow-level auth, Helix provides AI-specific security features:
- Human-in-the-loop approval —
approval_policy: taggedrequires human confirmation before executing sensitive tools (e.g.,transfer_money,delete_account) - Guardrails — PII detection/redaction, content filtering, output validation, and token budget enforcement via
pkg/agent/guardrails/ - Session isolation — Per-session memory and state via Redis-backed session store
- Input validation —
tools:with mandatory schemas reject malformed input before execution
Table of Contents¶
- JWT Authentication (HS256 / RS256)
- Basic Authentication (bcrypt)
- OAuth2 Token Introspection (RFC 7662)
- TLS and mTLS Configuration
- API Key Authentication
- Rate Limiting (Token Bucket)
- IP Allowlisting
- Secret Resolution (Vault, AWS Secrets Manager, Env Vars)
- Security Interceptors -- Chaining Auth
- CORS Configuration
- Production Security Best Practices
- Complete Examples
1. JWT Authentication¶
The JWT processor (security.NewAuthProcessor with Type: "jwt") performs
full signature verification and claims validation using only the Go standard
library (crypto/hmac, crypto/rsa, crypto/sha256, crypto/x509). No
third-party JWT libraries are used.
Source: pkg/security/security.go -- validateJWT function.
Configuration Types¶
// pkg/security/security.go
type AuthConfig struct {
Type string // "apikey", "jwt", "basic", "oauth2"
APIKey *APIKeyConfig
JWT *JWTConfig
Basic *BasicAuthConfig
OAuth2 *OAuth2Config
}
type JWTConfig struct {
Secret string // HMAC shared secret (for HS256)
PublicKey string // Path to RSA public key PEM file (for RS256)
Issuer string // Expected "iss" claim
Audience string // Expected "aud" claim
Claims map[string]string // Additional required claims (key -> expected value)
}
Supported Algorithms¶
| Algorithm | Key Material | Verification Method |
|---|---|---|
| HS256 | JWTConfig.Secret (shared secret) |
hmac.Equal (constant-time comparison) |
| RS256 | JWTConfig.PublicKey (PEM file path) |
rsa.VerifyPKCS1v15 via crypto/x509 |
Any other alg value (including none) is rejected with an error.
Validation Steps¶
The validateJWT function performs the following checks in order:
- Bearer token extraction -- Reads the
Authorizationheader, expectsBearer <token>. - Structure validation -- Splits on
.and verifies exactly 3 parts (header, payload, signature). - Header parsing -- Base64url-decodes the header and extracts the
algfield. - Signature verification -- Depending on the algorithm:
- HS256: Computes
HMAC-SHA256(secret, header.payload)and compares withhmac.Equal. - RS256: Reads the PEM public key file, parses it via
x509.ParsePKIXPublicKey, and verifies withrsa.VerifyPKCS1v15. - Expiration check -- If the
expclaim is present, rejects tokens wheretime.Now().Unix() > exp. - Issuer validation -- If
JWTConfig.Issueris set, verifies theissclaim matches exactly. - Audience validation -- If
JWTConfig.Audienceis set, verifiesaudmatches (supports both single-string and array formats per RFC 7519). - Custom claims -- Iterates over
JWTConfig.Claimsand verifies each key-value pair exists in the payload.
Claims Propagation¶
After successful validation, all claims are propagated as exchange headers
with the auth. prefix:
| Header | Source Claim |
|---|---|
auth.subject |
sub |
auth.issuer |
iss |
auth.<key> |
Any other claim (e.g., auth.role, auth.exp) |
This allows downstream processors to access identity information without re-parsing the token.
Go Examples¶
RS256 (asymmetric -- recommended for multi-service):
import "github.com/arvenza/helix/pkg/security"
authProc := security.NewAuthProcessor(&security.AuthConfig{
Type: "jwt",
JWT: &security.JWTConfig{
PublicKey: "/etc/helix/keys/jwt-public.pem",
Issuer: "https://auth.example.com",
Audience: "helix-api",
Claims: map[string]string{
"role": "admin",
},
},
})
// Use in a workflow pipeline:
builder.From("http://0.0.0.0:8080/api").
Process(authProc).
To("direct:business-logic")
HS256 (symmetric -- simpler for single-service):
authProc := security.NewAuthProcessor(&security.AuthConfig{
Type: "jwt",
JWT: &security.JWTConfig{
Secret: "${vault:secret/data/helix#jwt_secret}",
Issuer: "helix-internal",
Audience: "my-service",
},
})
Reading Authenticated Identity Downstream¶
func myProcessor(ctx context.Context, ex *exchange.Exchange) error {
subject := ex.In().HeaderString("auth.subject") // "sub" claim
issuer := ex.In().HeaderString("auth.issuer") // "iss" claim
role := ex.In().HeaderString("auth.role") // custom claim
// Route based on identity...
return nil
}
Key Security Properties¶
- Constant-time comparison for HS256 signatures (
hmac.Equal). - Algorithm enforcement: only
HS256andRS256accepted;noneis rejected. - Expiration is enforced when present; tokens without
expare allowed (configure a requiredexpclaim inClaimsto mandate it). - Audience validation supports both single-string and array formats per RFC 7519.
2. Basic Authentication¶
HTTP Basic authentication is verified against bcrypt password hashes using
golang.org/x/crypto/bcrypt. Passwords are never stored in plaintext.
Source: pkg/security/security.go -- validateBasic function.
Configuration¶
Validation Steps¶
- Reads the
Authorizationheader, expectsBasic <base64>. - Base64-decodes the credentials and splits on
:to extract username and password. - Looks up the username in
BasicAuthConfig.Users. - Verifies the password against the stored bcrypt hash using
bcrypt.CompareHashAndPassword.
Unknown users return a generic "unknown user" error. Wrong passwords return "invalid credentials". Both prevent user enumeration.
Generating bcrypt Hashes¶
import "golang.org/x/crypto/bcrypt"
// Generate a hash (do this offline or at init time, not per-request):
hash, err := bcrypt.GenerateFromPassword([]byte("s3cretP@ss"), bcrypt.DefaultCost)
// hash is something like "$2a$10$..."
Go Example¶
authProc := security.NewAuthProcessor(&security.AuthConfig{
Type: "basic",
Basic: &security.BasicAuthConfig{
Users: map[string]string{
"alice": "$2a$12$LJ3m4O.../hashed...",
"bob": "$2a$12$xyz9.../hashed...",
},
},
})
YAML with Vault-Resolved Hashes¶
security:
auth:
type: basic
basic:
users:
alice: "${vault:secret/data/auth#alice_bcrypt_hash}"
bob: "${vault:secret/data/auth#bob_bcrypt_hash}"
Security Properties¶
- bcrypt comparison is inherently constant-time.
- Always use cost >= 10 (
bcrypt.DefaultCost= 10) for production. Cost 12 is recommended for sensitive endpoints.
3. OAuth2 Token Introspection¶
The OAuth2 processor validates bearer tokens via RFC 7662 Token Introspection. It sends the token to the configured introspection endpoint and checks the response for active status and required scopes.
Source: pkg/security/security.go -- validateOAuth2 function.
Configuration¶
type OAuth2Config struct {
TokenURL string // RFC 7662 introspection endpoint
ClientID string // Client credentials for introspection
ClientSecret string
Scopes []string // Required scopes (AND semantics)
}
Introspection Flow¶
- Extracts the
Bearertoken from theAuthorizationheader. - Sends a
POSTrequest toTokenURLwith: - Body:
token=<bearer_token>(form-encoded) Content-Type: application/x-www-form-urlencodedAccept: application/json- HTTP Basic auth:
ClientID:ClientSecret - 10-second timeout (hardened against slow introspection servers)
- Parses the introspection response JSON.
- Rejects if
activeisfalse. - If
Scopesis configured, verifies every required scope appears in the space-delimitedscopefield (AND semantics, not OR).
Claims Propagation¶
| Header | Source Field |
|---|---|
auth.subject |
sub |
auth.clientId |
client_id |
auth.scope |
scope (space-separated) |
Go Example¶
authProc := security.NewAuthProcessor(&security.AuthConfig{
Type: "oauth2",
OAuth2: &security.OAuth2Config{
TokenURL: "https://auth.example.com/oauth2/introspect",
ClientID: "${env:OAUTH_CLIENT_ID}",
ClientSecret: "${vault:secret/data/helix#oauth_client_secret}",
Scopes: []string{"read:data", "write:data"},
},
})
Security Properties¶
- Introspection requests have a 10-second timeout to prevent hanging.
- Client credentials are sent via HTTP Basic auth on the introspection request (not as form parameters), per RFC 7662 recommendation.
- Scope validation requires all configured scopes (AND semantics).
4. TLS and mTLS Configuration¶
Helix provides two TLS configuration types, both using only the standard
library crypto/tls:
TLSConfig(pkg/security/security.go) -- high-level, string-based configuration for YAML/config files.NetworkTLSConfig(pkg/security/tls.go) -- programmatic, used by component implementations with URI parameter parsing.
4.1 High-Level TLS Configuration (TLSConfig)¶
// pkg/security/security.go
type TLSConfig struct {
Enabled bool
CertFile string // Server/client certificate
KeyFile string // Private key
CAFile string // CA certificate (for peer verification)
SkipVerify bool
MinVersion string // "1.2" (default) or "1.3"
ClientAuth string // "none" (default), "request", or "require"
}
The Build() method converts this to *tls.Config:
- Defaults to TLS 1.2 minimum.
- When
CAFileis set, the CA is loaded into bothRootCAsandClientCAs. ClientAuthmaps to:"none"->tls.NoClientCert"request"->tls.RequestClientCert"require"->tls.RequireAndVerifyClientCert
tlsCfg := &security.TLSConfig{
Enabled: true,
CertFile: "/etc/helix/tls/server.crt",
KeyFile: "/etc/helix/tls/server.key",
CAFile: "/etc/helix/tls/ca.crt",
MinVersion: "1.3",
ClientAuth: "require",
}
goTLS, err := tlsCfg.Build()
4.2 Network Component TLS (NetworkTLSConfig)¶
// pkg/security/tls.go
type NetworkTLSConfig struct {
Enabled bool
CertFile string
KeyFile string
CAFile string
InsecureSkipVerify bool
MinVersion uint16 // tls.VersionTLS12 or tls.VersionTLS13
ServerName string // SNI hostname
}
Three builder functions are provided:
| Function | Purpose | mTLS Behavior |
|---|---|---|
NewTLSConfig(cfg) |
Base TLS config | No auto-mTLS |
NewClientTLSConfig(cfg) |
Client-side (removes ClientCAs) |
N/A |
NewServerTLSConfig(cfg) |
Server-side | Auto-enables RequireAndVerifyClientCert if CAFile is set |
4.3 URI Parameter Parsing¶
Components extract TLS config from endpoint URIs using ParseTLSParams:
kafka://broker:9093?tls=true&tlsCert=/certs/client.pem&tlsKey=/certs/key.pem&tlsCA=/certs/ca.pem&tlsMinVersion=1.3
Recognized URI parameters:
| Parameter | Description | Default |
|---|---|---|
tls |
Enable TLS (true, 1, yes, on) |
false |
tlsCert |
Client/server certificate file | -- |
tlsKey |
Private key file | -- |
tlsCA |
CA certificate file | -- |
tlsInsecure |
Skip certificate verification | false |
tlsMinVersion |
"1.2" or "1.3" |
"1.2" |
tlsServerName |
SNI server name override | -- |
4.4 mTLS (Mutual TLS)¶
For mutual TLS, both sides present certificates and verify each other:
Server side -- requires client certificates:
serverTLS, err := security.NewServerTLSConfig(security.NetworkTLSConfig{
Enabled: true,
CertFile: "/certs/server.crt",
KeyFile: "/certs/server.key",
CAFile: "/certs/client-ca.crt", // Triggers RequireAndVerifyClientCert
MinVersion: tls.VersionTLS13,
})
Client side -- presents client certificate:
clientTLS, err := security.NewClientTLSConfig(security.NetworkTLSConfig{
Enabled: true,
CertFile: "/certs/client.crt",
KeyFile: "/certs/client.key",
CAFile: "/certs/server-ca.crt",
ServerName: "api.internal.example.com",
})
4.5 TLS for Vault Connections¶
The VaultClient (pkg/config/vault_client.go) supports TLS options:
vc := config.NewVaultClient(
"https://vault.example.com:8200",
"s.mytoken",
config.WithTLSSkipVerify(false), // default: verify
config.WithTimeout(30*time.Second),
config.WithNamespace("prod"),
)
Minimum TLS Version¶
Helix enforces TLS 1.2 as the minimum by default. For environments that support it, TLS 1.3 is recommended and eliminates the need to configure cipher suites (Go's TLS 1.3 implementation negotiates automatically).
5. API Key Authentication¶
API key validation uses crypto/subtle.ConstantTimeCompare to prevent
timing attacks that could reveal partial key matches.
Source: pkg/security/security.go -- validateAPIKey function.
Configuration¶
type APIKeyConfig struct {
Header string // Header name; defaults to "X-API-Key"
Keys []string // List of allowed API keys
}
Validation¶
- Reads the configured header (default
X-API-Key) from the exchange. - Compares against each allowed key using
subtle.ConstantTimeCompare. - Returns
"unauthorized: invalid API key"if no key matches.
Go Example¶
authProc := security.NewAuthProcessor(&security.AuthConfig{
Type: "apikey",
APIKey: &security.APIKeyConfig{
Header: "X-API-Key",
Keys: []string{
"${vault:secret/data/helix#api_key_1}",
"${vault:secret/data/helix#api_key_2}",
},
},
})
Custom Header¶
// Use a service-specific header name:
cfg := &security.APIKeyConfig{
Header: "X-Service-Token",
Keys: []string{"${env:SERVICE_API_KEY}"},
}
Key Rotation¶
Multiple keys are supported simultaneously. To rotate:
- Add the new key to the
Keyslist alongside the old key. - Update clients to use the new key.
- Remove the old key from the list.
6. Rate Limiting¶
The RateLimiter (pkg/security/security.go) implements a token-bucket
algorithm with a background refill goroutine.
How It Works¶
- A channel of capacity
rateis pre-filled with tokens on creation. - A background goroutine adds one token every
period / rateinterval. Allow()performs a non-blocking receive -- returnstrueif a token was available,falseotherwise.Wait(ctx)blocks until a token is available or the context is cancelled.Stop()shuts down the refill goroutine (safe to call multiple times viasync.Once).
Configuration¶
Using as a Processor¶
rl := security.NewRateLimiter(50, time.Second)
builder.From("http://0.0.0.0:8080/api").
Process(rl.Processor()). // Rate limit first
Process(authProc). // Then authenticate
To("direct:handler")
When no token is available within the context deadline, the exchange fails
with a "rate limited: context deadline exceeded" error.
Programmatic Usage¶
// Non-blocking check
if rl.Allow() {
// proceed
} else {
// reject with 429
}
// Blocking wait (respects context cancellation)
ctx, cancel := context.WithTimeout(ctx, 5*time.Second)
defer cancel()
if err := rl.Wait(ctx); err != nil {
// rate limited or context cancelled
}
Per-Workflow Rate Limiting¶
Create separate rate limiters for different workflows or tiers:
publicRL := security.NewRateLimiter(50, time.Second) // 50 req/s
premiumRL := security.NewRateLimiter(500, time.Second) // 500 req/s
builder.From("http://0.0.0.0:8080/public").
Process(publicRL.Processor()).
To("direct:handler")
builder.From("http://0.0.0.0:8080/premium").
Process(premiumRL.Processor()).
To("direct:handler")
Lifecycle¶
Always call Stop() during shutdown to clean up the background goroutine.
The Stop() method is idempotent.
7. IP Allowlisting¶
The IPAllowlist (pkg/security/security.go) restricts access to a set of
approved IP addresses.
IP Extraction Order¶
The processor extracts the client IP in this order:
X-Forwarded-Forheader -- Uses the first comma-separated entry (the original client IP).RemoteAddrheader -- Fallback whenX-Forwarded-Foris absent.- Port stripping -- Removes port numbers (e.g.,
192.168.1.1:4321becomes192.168.1.1).
X-Forwarded-For always takes precedence over RemoteAddr.
Go Example¶
allowlist := security.NewIPAllowlist([]string{
"10.0.0.0",
"10.0.0.1",
"192.168.1.100",
"::1", // IPv6 loopback
})
builder.From("http://0.0.0.0:9090/admin").
Process(allowlist.Processor()). // IP check first
Process(authProc). // Then authenticate
To("direct:admin-handler")
Important: Proxy Considerations¶
When running behind a reverse proxy or load balancer:
- Ensure the proxy sets
X-Forwarded-Forcorrectly. - Ensure only trusted proxies can modify this header (untrusted clients can spoof it).
- Consider using CIDR ranges at the network layer (firewall rules) in addition to application-level IP allowlisting.
8. Secret Resolution¶
Helix provides a multi-provider secret resolution system that replaces
${provider:reference} placeholders in configuration values at startup.
All implementations use only the Go standard library (no external SDKs).
8.1 Providers Overview¶
| Provider | Prefix | Reference Format | Implementation |
|---|---|---|---|
| HashiCorp Vault | vault: |
path#field |
HTTP REST API |
| AWS Secrets Manager | aws-sm: |
name or name#field |
REST API + SigV4 |
| Environment Variables | env: |
VARIABLE_NAME |
os.Getenv |
| File | file: |
/path/to/secret |
os.ReadFile |
8.2 Composite Secret Resolver (pkg/config/vault.go)¶
The CompositeSecretResolver delegates to provider-specific resolvers based on
a prefix:
resolver := config.NewDefaultCompositeResolver()
// Pre-registered: "vault:", "aws-sm:", "env:"
err := config.ResolveSecrets(ctx, resolver, configMap)
Example config with placeholders:
helix:
components:
kafka:
brokers: "kafka.example.com:9093"
password: "${vault:secret/data/kafka#password}"
redis:
password: "${aws-sm:redis-credentials#password}"
http:
api-key: "${env:EXTERNAL_API_KEY}"
The ResolveSecrets function walks arbitrary map[string]any structures
recursively (maps, slices, strings) and replaces all ${provider:ref} patterns.
8.3 Vault Resolver¶
The VaultResolver supports:
- KV v1 and KV v2 secret engines (auto-detected via mount metadata at
/v1/sys/mounts/). - Token authentication (from config or
VAULT_TOKENenv var). - AppRole authentication -- Automatic login via
POST /v1/auth/approle/loginwhen Token is empty butAppRoleID/AppSecretIDare set. - Namespace support for Vault Enterprise (sets
X-Vault-Namespaceheader). - Caching with configurable TTL (default 5 minutes with
NewDefaultCompositeResolver).
vaultResolver := config.NewVaultResolver(
"https://vault.example.com:8200", // or VAULT_ADDR env var
"", // or VAULT_TOKEN env var
"prod", // namespace (optional)
5*time.Minute, // cache TTL
)
// With AppRole authentication:
vaultResolver.AppRoleID = "role-id-here"
vaultResolver.AppSecretID = "secret-id-here"
// Reference format: path#field
secret, err := vaultResolver.Resolve(ctx, "secret/data/myapp#database_password")
8.4 AWS Secrets Manager Resolver¶
Uses AWS SigV4 request signing implemented in pure Go -- no AWS SDK dependency. The signing implementation includes canonical request construction, HMAC-SHA256 signing key derivation, and authorization header formatting.
awsResolver := config.NewAWSSecretsResolver(
"", // region (falls back to AWS_REGION -> AWS_DEFAULT_REGION -> us-east-1)
"", // access key (falls back to AWS_ACCESS_KEY_ID)
"", // secret key (falls back to AWS_SECRET_ACCESS_KEY)
5*time.Minute,
)
// Full secret string:
secret, err := awsResolver.Resolve(ctx, "my-database-creds")
// Specific JSON field:
password, err := awsResolver.Resolve(ctx, "my-database-creds#password")
8.5 Security-Level Secret Resolution (pkg/security)¶
The security.SecretResolver is a simpler runtime resolver with a
prefix:key syntax:
r := security.NewSecretResolver()
// Built-in resolvers:
val, _ := r.Resolve("env:DATABASE_URL") // os.Getenv
val, _ := r.Resolve("file:/run/secrets/api_key") // os.ReadFile + TrimSpace
// Register a custom resolver:
r.RegisterResolver("vault", func(key string) (string, error) {
secrets, err := vaultClient.ReadSecret(ctx, key)
if err != nil {
return "", err
}
return secrets["value"], nil
})
8.6 VaultClient (Standalone)¶
For direct Vault access, config.VaultClient provides a lightweight HTTP
client with functional options:
vc := config.NewVaultClient(
"https://vault.example.com:8200",
os.Getenv("VAULT_TOKEN"),
config.WithNamespace("production"),
config.WithTimeout(10*time.Second),
config.WithTLSSkipVerify(false),
)
// Read a single secret (auto-detects KV v1/v2):
secrets, err := vc.ReadSecret(ctx, "secret/data/myapp")
dbPass := secrets["db_password"]
// Resolve all ${vault:path/key} placeholders in a Config struct:
cfg, _ := config.Load("helix.yaml")
err = vc.ResolveSecrets(ctx, cfg)
The ResolveSecrets method uses reflection to walk all exported string fields
in the Config struct (including nested structs, pointers, and maps) and
replaces ${vault:path/key} patterns where the last / separates the Vault
path from the field name.
8.7 Caching Behavior¶
Both VaultResolver and AWSSecretsResolver use in-memory caches protected
by sync.RWMutex:
- Cache key: the full reference string (e.g.,
path#field). - Cache entry: resolved value + expiration time.
- TTL of 0 disables caching entirely.
- Cache is checked before making any network request.
- No cache eviction beyond TTL expiry (entries are lazy-expired on access).
9. Security Interceptors -- Chaining Auth¶
Helix uses the engine.Interceptor interface for cross-cutting security
concerns that apply to every exchange on every workflow (or globally).
Interceptor Interface¶
// pkg/engine/interfaces.go
type Interceptor interface {
Before(ctx context.Context, exchange *exchange.Exchange) error
After(ctx context.Context, exchange *exchange.Exchange) error
}
Before runs before the processor pipeline; returning an error from Before
short-circuits the pipeline and the exchange fails. After runs after
processing completes (even on error).
9.1 Tenant Interceptor¶
The TenantInterceptor (pkg/engine/tenant.go) extracts a tenant identifier
from message headers and enforces an allowlist:
type TenantInterceptorConfig struct {
HeaderName string // default "X-Tenant-ID"
Required bool // reject exchanges without a tenant header
AllowedTenants []string // empty = allow all
}
tenantInterceptor := engine.NewTenantInterceptor(engine.TenantInterceptorConfig{
HeaderName: "X-Tenant-ID",
Required: true,
AllowedTenants: []string{"acme", "globex", "initech"},
})
Behavior:
- Reads the configured header from the exchange message.
- If
Requiredis true and the header is missing, rejects the exchange. - If
AllowedTenantsis non-empty, checks the tenant against the allowlist. - On success, sets the
helix.tenantproperty on the exchange for downstream use (routing decisions, metrics labels, database scoping).
YAML configuration:
helix:
tenant:
enabled: true
header_name: "X-Tenant-ID"
required: true
allowed_tenants:
- acme
- globex
- initech
9.2 Audit Interceptor¶
The AuditInterceptor (pkg/engine/audit.go) records exchange lifecycle
events for compliance and debugging:
store := engine.NewInMemoryAuditStore(10000) // ring buffer
// or: store := engine.NewSlogAuditStore(logger, 10000) // structured log output
audit := engine.NewAuditInterceptor(store, 256) // 256 bytes body preview
Each AuditEntry records:
| Field | Description |
|---|---|
| Timestamp | When the event was recorded |
| ExchangeID | Unique exchange identifier |
| WorkflowID | Workflow that processed the exchange |
| FromURI | Source endpoint URI |
| Operation | "completed" or "failed" |
| Duration | Processing time |
| Error | Error message (if failed) |
| Headers | Message headers snapshot |
| BodyPreview | First N bytes of the message body |
YAML configuration:
helix:
audit:
enabled: true
store: "log" # "memory" (ring buffer) or "log" (slog)
max_entries: 10000
body_preview: 256
9.3 Chaining Multiple Security Interceptors¶
Interceptors run in registration order. Register them on the HelixContext:
hc := engine.NewContext("my-app")
// Order matters: each interceptor runs in registration order.
// 1. Audit everything first (captures timing for all subsequent interceptors)
hc.AddInterceptor(auditInterceptor)
// 2. Tenant isolation
hc.AddInterceptor(tenantInterceptor)
// 3. Distributed tracing
hc.AddInterceptor(tracingInterceptor)
9.4 Interceptors vs Processors: When to Use Which¶
| Mechanism | Scope | Use Case |
|---|---|---|
Interceptor (hc.AddInterceptor) |
Global (all workflows) | Audit logging, tenant isolation, tracing |
Auth Processor (security.NewAuthProcessor) |
Per-workflow | Different auth per endpoint |
Rate Limiter (rl.Processor()) |
Per-workflow | Workflow-specific rate limits |
IP Allowlist (allowlist.Processor()) |
Per-workflow | Endpoint-level IP restrictions |
9.5 Combined Security Pipeline Example¶
hc := engine.NewContext("my-app")
// Global interceptors (all workflows):
hc.AddInterceptor(auditInterceptor)
hc.AddInterceptor(tenantInterceptor)
// Per-workflow: public API (rate limited + JWT)
publicRL := security.NewRateLimiter(100, time.Second)
jwtAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "jwt",
JWT: jwtCfg,
})
builder.From("http://0.0.0.0:8080/api").
Process(publicRL.Processor()).
Process(jwtAuth).
To("direct:api-handler")
// Per-workflow: admin API (IP restricted + API key)
adminIPs := security.NewIPAllowlist([]string{"10.0.0.1", "10.0.0.2"})
adminAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "apikey",
APIKey: &security.APIKeyConfig{Keys: []string{"${vault:secret/data/helix#admin_key}"}},
})
builder.From("http://0.0.0.0:8080/admin").
Process(adminIPs.Processor()).
Process(adminAuth).
To("direct:admin-handler")
// Per-workflow: webhook (API key only, no rate limit)
webhookAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "apikey",
APIKey: &security.APIKeyConfig{
Header: "X-Webhook-Secret",
Keys: []string{"${env:WEBHOOK_SECRET}"},
},
})
builder.From("http://0.0.0.0:8080/webhook").
Process(webhookAuth).
To("direct:webhook-handler")
10. CORS Configuration¶
CORS is configured on REST definitions via the DSL builder or YAML. The
CORSDefinition type lives in pkg/workflow/rest_definition.go.
Data Model¶
// pkg/workflow/rest_definition.go
type CORSDefinition struct {
AllowedOrigins []string
AllowedMethods []string
AllowedHeaders []string
MaxAge int // seconds
}
type SecurityDefinition struct {
Type string // apiKey, bearer, basic, oauth2
Name string // header/parameter name
In string // header, query
}
Go Fluent DSL¶
import "github.com/arvenza/helix/pkg/dsl"
restDef := dsl.Rest("/api/v1").
EnableCORS(
[]string{"https://app.example.com", "https://admin.example.com"},
[]string{"GET", "POST", "PUT", "DELETE"},
[]string{"Authorization", "Content-Type", "X-Tenant-ID"},
3600, // Max-Age in seconds
).
Get("/users").To("direct:list-users").
Post("/users").To("direct:create-user")
YAML Configuration¶
rest:
- path: "/api/v1"
cors:
allowed-origins:
- "https://app.example.com"
- "https://admin.example.com"
allowed-methods:
- GET
- POST
- PUT
- DELETE
allowed-headers:
- Authorization
- Content-Type
- X-Tenant-ID
max-age: 3600
security:
- type: bearer
name: Authorization
in: header
verbs:
- method: GET
path: "/users"
target: "direct://list-users"
description: "List all users"
- method: POST
path: "/users"
target: "direct://create-user"
description: "Create a new user"
# Handler workflows for the REST endpoints
workflows:
- id: list-users
trigger:
uri: "direct://list-users"
steps:
- transform:
constant: '[]'
- set-header:
name: Content-Type
constant: "application/json"
- id: create-user
trigger:
uri: "direct://create-user"
steps:
- set-header:
name: Content-Type
constant: "application/json"
- transform:
constant: '{"status":"created"}'
AG-UI and A2UI Automatic CORS¶
The AG-UI (pkg/component/agui/) and A2UI (pkg/component/a2ui/) server
components set CORS headers automatically for frontend access. No additional
configuration is needed for those endpoints.
11. Production Security Best Practices¶
11.1 Secret Management¶
- Never hardcode secrets in Go source or YAML. Use
${vault:...},${aws-sm:...}, or${env:...}placeholders exclusively. - Use AppRole authentication for Vault instead of long-lived tokens. AppRole supports secret ID rotation and TTL-limited tokens.
- Enable caching (
CacheTTL) to reduce Vault/AWS API calls while maintaining freshness. 5 minutes is a good default. - Use file-based secrets for Kubernetes: mount Secrets as files and use
file:/var/run/secrets/helix/db-password. - Rotate secrets by updating them in Vault/AWS SM. Reduce cache TTL for faster propagation during rotation windows.
11.2 Transport Security¶
- Always enable TLS in production. Use
MinVersion: "1.3"when all clients support it. - Use mTLS for service-to-service communication.
NewServerTLSConfigautomatically setsRequireAndVerifyClientCertwhen a CA file is provided. - Never set
SkipVerify: trueortlsInsecure=truein production. - Rotate TLS certificates before expiry. Go reads certificate files at config build time, so restart or reload is required after rotation.
11.3 Authentication¶
- Prefer RS256 over HS256 for JWT when multiple services validate tokens. HS256 requires sharing the secret with every verifier, increasing the attack surface.
- Always validate
exp,iss, andaudclaims. To mandateexp, add it to theClaimsmap. - Use bcrypt cost >= 10 for Basic Auth. Cost 12 is recommended for internet-facing services.
- Set required scopes for OAuth2 introspection. Never accept tokens with no scope validation.
- API key comparison is constant-time -- no additional measures needed against timing attacks.
11.4 Rate Limiting¶
- Apply rate limiting before authentication to prevent auth-related DoS. bcrypt hashing and OAuth2 introspection are expensive operations.
- Use per-workflow rate limiters with different thresholds for public vs. authenticated vs. admin endpoints.
- Always call
rl.Stop()during shutdown to clean up background goroutines.
11.5 IP Filtering¶
- Use IP allowlisting for admin endpoints (management API, health checks with sensitive data, audit endpoints).
- Trust
X-Forwarded-Foronly behind known proxies. Untrusted clients can spoof this header. - Combine IP allowlisting with authentication for defense-in-depth.
11.6 Multi-Tenancy¶
- Set
Required: trueto ensure every exchange has a tenant identifier. - Use
AllowedTenantsto enforce a strict allowlist. - The
helix.tenantexchange property can be used in downstream routing, metrics labels, and database query scoping.
11.7 Audit and Compliance¶
- Enable the audit interceptor in production for compliance logging.
- Limit
body_previewto avoid logging sensitive data (passwords, PII, credit card numbers). 128-512 bytes is typically sufficient for debugging. - Use the
"log"store backend to forward audit logs to a centralized SIEM.
11.8 Graceful Shutdown¶
Configure drain_timeout to allow in-flight requests to complete before
the process exits:
11.9 CORS¶
- Never use
*forAllowedOriginsin production. Enumerate specific, trusted origins. - Limit
AllowedMethodsto the HTTP methods your API actually supports. - Set a reasonable
MaxAge(e.g., 3600 seconds) to reduce preflight requests.
11.10 Tool-Level Authentication¶
When agents call external services via type: a2a, type: mcp, or type: connector tools, you can attach an auth: block to each tool. This secures outbound calls without affecting the agent's own inbound authentication.
Four auth types are supported. All ${env:...} placeholders are resolved at load time via Helix's secret resolution system (environment variables, Vault, AWS Secrets Manager).
Bearer Token¶
tools:
- name: remote-agent
type: a2a
a2a_server:
url: "http://agent.internal:9000"
auth:
type: bearer
token: "${env:A2A_TOKEN}"
The token is sent as Authorization: Bearer <token> on every request to the remote server.
API Key¶
tools:
- name: vendor-tools
type: mcp
mcp_server:
url: "https://api.vendor.com/mcp"
auth:
type: api-key
header: "X-API-Key" # custom header name
key: "${env:VENDOR_API_KEY}"
The key is sent in the specified header on every request. Defaults to X-API-Key if header is omitted.
OAuth2 Client Credentials¶
tools:
- name: corp-agent
type: a2a
a2a_server:
url: "http://agent.corp.internal:9000"
auth:
type: oauth2
token_url: "https://auth.corp.internal/token"
client_id: "${env:OAUTH_CLIENT_ID}"
client_secret: "${env:OAUTH_CLIENT_SECRET}"
scopes: ["agent:invoke", "tools:read"]
Helix performs a client credentials grant to token_url before calling the remote service, caches the access token, and refreshes it on expiry.
Mutual TLS (mTLS)¶
tools:
- name: secure-tools
type: mcp
mcp_server:
url: "https://mcp.internal:3100"
auth:
type: mtls
cert_file: "/certs/client.crt"
key_file: "/certs/client.key"
ca_file: "/certs/ca.crt"
The client presents its certificate during the TLS handshake. The ca_file is used to verify the server's certificate.
Combining Tool Auth with Workflow Auth¶
Tool-level auth is independent of workflow-level auth. An agent endpoint can require JWT for inbound requests while its tools use OAuth2 for outbound calls:
agents:
- id: secure-agent
type: react
trigger: http-server://0.0.0.0:8080/api/agent
auth: # inbound: callers must present JWT
type: jwt
jwt:
secret: "${env:JWT_SECRET}"
model:
provider: openai
model: gpt-4o
tools:
- name: remote-service
type: a2a
a2a_server:
url: "http://service.internal:9000"
auth: # outbound: agent uses OAuth2
type: oauth2
token_url: "https://auth.internal/token"
client_id: "${env:CLIENT_ID}"
client_secret: "${env:CLIENT_SECRET}"
11.11 Agent-Level Security¶
When running AI agents in production, apply additional safeguards beyond transport and route security:
- Approval policies: Require human-in-the-loop confirmation before agents execute sensitive tools (e.g., database writes, payment calls). Configure per-tool approval via the agent's
tools[].approval: requiredsetting. - PII guardrails: Use a processor or interceptor to scan agent inputs and outputs for personally identifiable information before it reaches the LLM or leaves the system. Combine with
audit.body_preview: 0to avoid logging PII. - Token budgets: Set
max_tokensandmax_turnson agent definitions to cap LLM spend per request. Monitorhelix_agent_tokens_totalto detect runaway loops.
11.12 Security Processor Ordering¶
The recommended order for processors in a workflow pipeline is:
- Rate Limiting -- Protect against DoS before any expensive operations.
- IP Allowlisting -- Reject unauthorized IPs early.
- Authentication (JWT/Basic/OAuth2/API Key) -- Validate identity.
- Authorization (custom processor) -- Check roles/permissions.
- Business Logic -- Process the request.
12. Complete Examples¶
12.1 Full Secured REST API¶
package main
import (
"context"
"time"
"github.com/arvenza/helix/pkg/config"
"github.com/arvenza/helix/pkg/dsl"
"github.com/arvenza/helix/pkg/engine"
"github.com/arvenza/helix/pkg/security"
)
func main() {
ctx := context.Background()
// --- Secret Resolution at Startup ---
resolver := config.NewDefaultCompositeResolver()
configMap := map[string]any{
"jwt_secret": "${vault:secret/data/auth#jwt_secret}",
"admin_api_key": "${aws-sm:helix-admin#api_key}",
"webhook_secret": "${env:WEBHOOK_SECRET}",
}
if err := config.ResolveSecrets(ctx, resolver, configMap); err != nil {
panic(err)
}
// --- Global Interceptors ---
hc := engine.NewContext("my-app")
// Tenant isolation
hc.AddInterceptor(engine.NewTenantInterceptor(engine.TenantInterceptorConfig{
HeaderName: "X-Tenant-ID",
Required: true,
AllowedTenants: []string{"acme", "globex"},
}))
// Audit logging
auditStore := engine.NewInMemoryAuditStore(50000)
hc.AddInterceptor(engine.NewAuditInterceptor(auditStore, 256))
// --- Per-Workflow Security ---
builder := dsl.NewRoute()
// Public API: rate limited + JWT
publicRL := security.NewRateLimiter(100, time.Second)
defer publicRL.Stop()
jwtAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "jwt",
JWT: &security.JWTConfig{
PublicKey: "/etc/helix/keys/jwt-public.pem",
Issuer: "https://auth.example.com",
Audience: "api.example.com",
},
})
builder.From("http://0.0.0.0:8080/api/v1").
Process(publicRL.Processor()).
Process(jwtAuth).
To("direct:api-handler")
// Admin API: IP restricted + API key
adminIPs := security.NewIPAllowlist([]string{"10.0.0.1", "10.0.0.2"})
adminAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "apikey",
APIKey: &security.APIKeyConfig{
Header: "X-Admin-Key",
Keys: []string{configMap["admin_api_key"].(string)},
},
})
builder.From("http://0.0.0.0:8080/admin").
Process(adminIPs.Processor()).
Process(adminAuth).
To("direct:admin-handler")
// Webhook: API key via custom header
webhookAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "apikey",
APIKey: &security.APIKeyConfig{
Header: "X-Webhook-Secret",
Keys: []string{configMap["webhook_secret"].(string)},
},
})
builder.From("http://0.0.0.0:8080/webhook").
Process(webhookAuth).
To("direct:webhook-handler")
// Start engine...
hc.Start(ctx)
}
12.2 mTLS Between Services¶
package main
import (
"crypto/tls"
"github.com/arvenza/helix/pkg/security"
)
func setupMTLS() (*tls.Config, *tls.Config) {
// Server: requires client certificates signed by the CA
serverTLS, err := security.NewServerTLSConfig(security.NetworkTLSConfig{
Enabled: true,
CertFile: "/certs/server.crt",
KeyFile: "/certs/server.key",
CAFile: "/certs/ca.crt", // Triggers RequireAndVerifyClientCert
MinVersion: tls.VersionTLS13,
})
if err != nil {
panic(err)
}
// Client: presents client certificate for mTLS
clientTLS, err := security.NewClientTLSConfig(security.NetworkTLSConfig{
Enabled: true,
CertFile: "/certs/client.crt",
KeyFile: "/certs/client.key",
CAFile: "/certs/ca.crt",
ServerName: "api.internal.example.com",
})
if err != nil {
panic(err)
}
return serverTLS, clientTLS
}
12.3 OAuth2-Protected Agent Endpoint¶
agentAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "oauth2",
OAuth2: &security.OAuth2Config{
TokenURL: "https://auth.example.com/oauth2/introspect",
ClientID: "${env:OAUTH_CLIENT_ID}",
ClientSecret: "${vault:secret/data/helix#oauth_secret}",
Scopes: []string{"agent:invoke", "agent:read"},
},
})
builder.From("http://0.0.0.0:8080/agent/invoke").
Process(agentAuth).
To("agent:my-agent")
12.4 Vault with AppRole Authentication¶
vault := config.NewVaultResolver(
"https://vault.example.com:8200",
"", // No static token
"production",
5*time.Minute,
)
vault.AppRoleID = os.Getenv("VAULT_ROLE_ID")
vault.AppSecretID = os.Getenv("VAULT_SECRET_ID")
// The resolver will automatically perform AppRole login
// when Resolve() is first called.
secret, err := vault.Resolve(ctx, "secret/data/myapp#database_password")
12.5 Full Production YAML¶
helix:
name: "production-service"
shutdown:
drain_timeout: "30s"
logging:
level: "info"
format: "json"
components:
security: "debug"
tenant:
enabled: true
header_name: "X-Tenant-ID"
required: true
allowed_tenants:
- acme
- globex
audit:
enabled: true
store: "log"
max_entries: 50000
body_preview: 256
management:
enabled: true
port: 9090
health:
enabled: true
port: 8081
components:
kafka:
brokers: "${env:KAFKA_BROKERS}"
password: "${vault:secret/data/kafka#password}"
redis:
addr: "${env:REDIS_ADDR}"
password: "${aws-sm:redis-credentials#password}"
postgres:
dsn: "postgres://${vault:secret/data/pg#user}:${vault:secret/data/pg#pass}@${env:PG_HOST}:5432/myapp?sslmode=verify-full"
workflows:
- from:
uri: "http://0.0.0.0:8443/api?tls=true&tlsCert=/certs/server.crt&tlsKey=/certs/server.key&tlsCA=/certs/ca.crt&tlsMinVersion=1.3"
steps:
- target: "direct:api-handler"
rest:
- path: "/api/v1"
cors:
allowed-origins:
- "https://app.example.com"
allowed-methods:
- GET
- POST
- PUT
- DELETE
allowed-headers:
- Authorization
- Content-Type
- X-Tenant-ID
max-age: 3600
security:
- type: bearer
name: Authorization
in: header
verbs:
- method: GET
path: "/resources"
target: "direct://list-resources"
description: "List resources"
- method: POST
path: "/resources"
target: "direct://create-resource"
description: "Create a resource"
12.6 Custom Role-Based Access Control Processor¶
package main
import (
"context"
"fmt"
"github.com/arvenza/helix/pkg/engine"
"github.com/arvenza/helix/pkg/exchange"
"github.com/arvenza/helix/pkg/security"
)
// RequireRole creates a processor that checks the auth.role header
// (set by JWT validation) against a required role.
func RequireRole(role string) engine.Processor {
return engine.ProcessorFunc(func(ctx context.Context, ex *exchange.Exchange) error {
actualRole := ex.In().HeaderString("auth.role")
if actualRole != role {
return fmt.Errorf("forbidden: required role %q, got %q", role, actualRole)
}
return nil
})
}
// Usage: JWT auth sets auth.role, then RequireRole checks it.
func buildAdminRoute() {
jwtAuth := security.NewAuthProcessor(&security.AuthConfig{
Type: "jwt",
JWT: &security.JWTConfig{
PublicKey: "/keys/public.pem",
Issuer: "https://auth.example.com",
},
})
// Pipeline: rate limit -> JWT -> role check -> handler
rl := security.NewRateLimiter(50, 1)
// In route builder:
// builder.From("http://0.0.0.0:8080/admin").
// Process(rl.Processor()).
// Process(jwtAuth).
// Process(RequireRole("admin")).
// To("direct:admin-handler")
_ = rl
_ = jwtAuth
}
MCP Server Security¶
MCP servers in Helix include a full HTTP security middleware stack:
Configuration¶
mcps:
- id: my-tools
addr: 0.0.0.0:3100
api_keys: [mcp-secret-key] # Bearer or X-API-Key auth
trusted_proxies: ["127.0.0.1"] # IPs that skip auth (gateway)
rate_limit: 60 # Requests/minute
cors_origins: ["http://localhost:3000"]
max_body_bytes: 2097152 # 2MB
allowed_tools: [] # Whitelist (empty = all)
blocked_tools: [admin-tool] # Blacklist (always blocked)
Security Middleware Stack¶
Applied in order for every HTTP request to the MCP server:
- CORS — validates
Originheader, returns 403 for unlisted origins - Trusted proxy bypass — requests from
trusted_proxiesskip auth - IP allowlist — restricts client IPs (if configured)
- API key auth — constant-time
Bearer/X-API-Keyvalidation, returns 401 - Rate limiting — token-bucket per minute, returns 429
- Body size limit — rejects oversized payloads, returns 413
- Tool filtering —
blocked_toolschecked attools/calltime, returns MCPisError
Tool Access Control¶
allowed_tools: [tool-a]— onlytool-acan be called; others get"tool X is not allowed"blocked_tools: [tool-b]—tool-balways returnsisError: truewith"tool X is blocked"- If a tool is in both lists, blocked wins
- Empty lists = no filtering (all exposed tools available)
Multi-MCP Security Isolation¶
Each MCP server has independent security:
mcps:
- id: public # Port 3100, key: public-key
api_keys: [public-key]
- id: admin # Port 3101, key: admin-key
api_keys: [admin-key]
Using public-key on port 3101 → 401 Rejected (cross-key isolation).
Runtime Governance Layer (built-in)¶
Helix ships its own deterministic, app-layer governance stack under pkg/agent/governance/ that runs inside the agent dispatcher — no external gateway required. It complements (and is independent of) the SynaptX integration below.
Configure under agent.governance: in YAML:
agents:
- id: finance-agent
type: react
model: { provider: openai, name: gpt-4o-mini }
tools: [transfer, get_balance]
governance:
audit_chain: true # hash-chained tamper-evident log
rings:
assignment: standard # 4-tier: admin / standard / restricted / sandboxed
permissions:
standard: [get_balance, transfer]
admin: ["*"]
policy:
- action: transfer
effect: review # surface to /api/approvals
starlang_when: |
ctx["args"]["amount"] > 10000
- action: get_balance
effect: allow
max_calls: 100
window: 1m
- action: "*"
effect: allow
Subsystems (all optional, all composable):
| Subsystem | What it does |
|---|---|
| Hash-chained audit log | sha256(prev_hash \|\| ts \|\| agent \|\| action \|\| decision) per entry; Verify() walks the chain |
| Scoped kill switches | Global / Agent / Capability scopes, flipped at runtime via POST /api/kill-switch |
| Privilege rings | 4-tier (Admin/Standard/Restricted/Sandboxed) with prefix-wildcard permissions; default-deny |
| Policy engine | First-match-wins rules with allow / deny / review / rate_limit effects; optional Starlang predicates with ctx, agent_id, tool_name, tenant, action |
| Policy conflict resolver | deny_overrides / allow_overrides / priority_first_match / most_specific_wins for stacked global+tenant+agent engines |
| MCP security scanner | Detects typosquatting (Levenshtein-1 + Unicode confusables), tool poisoning, hidden instructions, rug-pull |
| Prompt-defense classifier | Regex catalogue for instruction override / prompt exfiltration / credential leak / approval bypass |
Evaluation order per tool call: kill switches → privilege rings → policy engine → audit. First non-allow decision wins; the dispatcher feeds a synthetic Tool call denied by governance: <reason> tool_result back to the LLM and emits a governance_block AgentEvent on the unified stream.
Runtime control (role-gated):
| Endpoint | Method | Role | Purpose |
|---|---|---|---|
/api/audit |
GET | viewer | List recent audit entries |
/api/kill-switch |
GET | viewer | Current kill-switch activations |
/api/kill-switch |
POST | admin | Activate ({"scope":"agent:finance-agent","reason":"investigation","actor":"secops"}) |
/api/kill-switch/reset |
POST | admin | Clear all switches |
/api/agents/{id}/pause |
POST | operator | Soft pause one agent |
/api/agents/{id}/resume |
POST | operator | Resume |
/api/agents/{id}/budget/reset |
POST | operator | Reset budget counters |
/api/approvals |
GET | viewer | Pending review approvals |
/api/approvals/{id} |
POST | operator | Resolve ({"approved":true,"feedback":"..."}) |
See the dedicated Governance Guide for the full Go API, conflict resolver, MCP scanner, and prompt-defense classifier with worked examples and the operational playbook.
SynaptX Security Integration¶
When Helix runs behind SynaptX gateway, two security layers work together:
Prompt Injection Guard¶
SynaptX detects 16 prompt injection patterns (per-workflow prompt_guard = true):
- "Ignore previous instructions" — blocked (403)
- DAN/jailbreak mode — blocked (403)
- System prompt exfiltration — blocked (403)
- Chat template delimiters — blocked (403)
- Encoding evasion, token smuggling — warned (header)
PII Detection¶
SynaptX detects 10 PII types (per-workflow pii_mode = "block"):
- SSN (123-45-6789) — blocked (403)
- Credit card (4111-1111-1111-1111) — blocked (403)
- AWS access key (AKIA...) — blocked (403)
- Email, phone, IBAN, passport — configurable
Modes: off (default), warn (header), redact (replace), block (403)
Virtual Keys¶
Per-agent API keys with budget, rate limit, and model ACL:
curl -X POST http://localhost:7081/virtual-keys \
-d '{"name":"code-agent","max_budget":50,"max_rpm":60,"allowed_models":["gpt-4o-mini"]}'
Governance Policies¶
ABAC/RBAC policies via admin API:
curl -X POST http://localhost:7081/governance/policies \
-d '{"id":"block-admin","effect":"deny","subjects":{"agent_ids":["admin-tool"]},"resources":{"paths":["/ai/**"]}}'
Source File Reference¶
| File | Contents |
|---|---|
pkg/security/security.go |
AuthConfig, JWTConfig, BasicAuthConfig, OAuth2Config, APIKeyConfig, TLSConfig, NewAuthProcessor, SecretResolver, RateLimiter, IPAllowlist |
pkg/security/tls.go |
NetworkTLSConfig, NewTLSConfig, NewClientTLSConfig, NewServerTLSConfig, ParseTLSParams |
pkg/config/vault.go |
VaultResolver, AWSSecretsResolver, CompositeSecretResolver, EnvResolver, ResolveSecrets |
pkg/config/vault_client.go |
VaultClient, VaultOption, ReadSecret, ResolveSecrets (struct reflection) |
pkg/config/config.go |
Config, TenantConfig, AuditConfig, ShutdownConfig, LoggingConfig |
pkg/engine/tenant.go |
TenantInterceptor, TenantInterceptorConfig |
pkg/engine/audit.go |
Workflow-level AuditInterceptor, AuditEntry, InMemoryAuditStore, SlogAuditStore (best-effort observation) |
pkg/governance/audit_chain.go |
Hash-chained tamper-evident AuditLogger, Verify, LoadAuditChain |
pkg/governance/engine_audit_bridge.go |
AuditLogger.AsEngineAuditStore() — exposes the chain via the engine.AuditStore + engine.Verifier interfaces |
pkg/governance/kill_switch.go |
KillSwitchRegistry with Global / Agent / Capability scopes |
pkg/governance/kill_switch_backend.go |
KillSwitchBackend interface + RedisKillSwitchBackend — cluster propagation via Redis pub/sub |
pkg/governance/rings.go |
RingEnforcer — 4-tier privilege rings with prefix wildcards |
pkg/governance/policy.go |
PolicyEngine, PolicyRule, Starlang predicates, fixed-window rate limit |
pkg/governance/rate_limit_backend.go |
RateLimitBackend interface + LocalRateLimitBackend / RedisRateLimitBackend |
pkg/governance/conflict.go |
PolicyConflictResolver — deny_overrides / allow_overrides / priority_first_match / most_specific_wins |
pkg/governance/mcp_scanner.go |
McpSecurityScanner — typosquatting (Levenshtein-1 + confusables), poisoning, hidden instructions, rug-pull |
pkg/governance/prompt_defense.go |
PromptDefenseEvaluator with four built-in threat classes |
pkg/governance/stack.go |
Stack.Check — orchestrates kill-switch → rings → policy → audit |
pkg/governance/interceptor.go |
NewInterceptor(stack, cfg) — adapts Stack into an engine.Interceptor for workflow-level gating |
pkg/component/governance/ |
governance://stack-name?action=X URI component for non-agent workflow steps |
pkg/engine/interfaces.go |
Interceptor, Processor, ProcessorFunc interfaces |
pkg/workflow/rest_definition.go |
CORSDefinition, SecurityDefinition |
pkg/dsl/rest.go |
RestBuilder.EnableCORS, RestBuilder.Security |
pkg/component/mcp/server.go |
securityMiddleware, validateRequestAuth, extractClientIP, rateBucket |
pkg/component/mcp/tools.go |
isInvocableRoute, sanitizeToolName, tool filtering helpers |
pkg/component/websocket/websocket.go |
ServerComponent, ServerConsumer (RFC 6455 WS server with response) |