Skip to main content

AI Gateway inference Helm values

Configure the AI Gateway inference chart with these Helm values.

This reference lists values declared in the chart's default values.yaml. Templates can also accept optional settings and inherited global values.

Chart: ai-gateway-v2. Chart version: 0.22.0. Generated from the platform source at e71926418e98c7598815d0abf07039d3c1646f21.

Values​

replicaCount​

Type: int

Default: 2

Number of replicas for the deployment. Ignored (the replicas: field is OMITTED entirely from the rendered Deployment) when replicas.managedExternally is true, so an external autoscaler's writes to replicas survive helm upgrade.

replicas.managedExternally​

Type: bool

Default: false

When true, the chart never writes a replicas: field on the Deployment at all — not even the last value it rendered. Helm's own three-way merge would otherwise re-assert replicaCount on every upgrade and fight an external scaler (HPA/KEDA) that manages replicas out of band.

image.repository​

Type: string

Default: "image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/sprout"

Container image repository. Enterprise build produced by _release-image.yml (ko, -tags=enterprise), then promoted to the platform tag by release.yml. Served to customers via Replicated's image proxy. task version-bump rewrites the trailing :<tag> via a regex anchored on ghcr.io/stacklok/stacklok-enterprise/<name>: — that anchor still matches inside this longer proxy URL.

image.tag​

Type: string

Default: ""

Image tag (defaults to appVersion if empty)

image.pullPolicy​

Type: string

Default: "IfNotPresent"

Image pull policy

imagePullSecrets​

Type: list

Default: []

Extra image pull Secrets to apply to the sprout pod. On Replicated installs, the SDK injects global.replicated.dockerconfigjson and creates an enterprise-pull-secret Secret in the release namespace; the sprout.imagePullSecrets helper auto-detects that injection and merges the SDK secret in front of anything listed here, so customers do NOT need to add enterprise-pull-secret themselves. Use this list only for additional, customer-supplied registries.

nameOverride​

Type: string

Default: ""

Override the name of the chart

fullnameOverride​

Type: string

Default: ""

Override the full name of the resources

serviceAccount.create​

Type: bool

Default: true

Specifies whether a service account should be created. Sprout uses its ServiceAccount only as the identity behind the projected tokens it presents to the directory and budgets services — it never calls the Kubernetes API itself.

serviceAccount.automount​

Type: bool

Default: false

Automatically mount a ServiceAccount's API-server credentials. Sprout never calls the Kubernetes API, so this stays false; the directory/budgets tokens below are separate, audience-bound PROJECTED volumes, not the automounted API-server token.

serviceAccount.annotations​

Type: object

Default: {}

Annotations to add to the service account

serviceAccount.name​

Type: string

Default: ""

The name of the service account to use (if not set and create is true, a name is generated using the fullname template)

podAnnotations​

Type: object

Default: {}

Annotations to add to the pod

podLabels​

Type: object

Default: {}

Labels to add to the pod

podSecurityContext​

Type: object

Default value
fsGroup: 65532
runAsGroup: 65532
runAsNonRoot: true
runAsUser: 65532
seccompProfile:
type: RuntimeDefault

Security context for the pod (ko/Chainguard nonroot UID = 65532)

securityContext​

Type: object

Default value
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL
readOnlyRootFilesystem: true
runAsNonRoot: true
runAsUser: 65532

Security context for the container

service.type​

Type: string

Default: "ClusterIP"

Kubernetes service type

service.port​

Type: int

Default: 8080

Service port

service.targetPort​

Type: int

Default: 8080

Container target port. Matches -addr's default (":8080") in cmd/sprout/main.go. The Service exposes ONLY this port — never the dedicated /metrics listener (telemetry.metrics.port below), which serves on its own listener specifically so it is never reachable through this customer-facing Service.

telemetry​

Type: object

Default value
headerLabels: []
metrics:
port: 9464
otlp:
enabled: false
endpoint: ''
insecure: false
prometheus:
enabled: false
tracing:
enabled: false
samplingRate: 0

Metrics and tracing export (opt-in). Both metrics exporters and tracing default off — no OTLP push, no Prometheus pull, no /metrics route, no spans anywhere. Field names mirror cmd/sprout/main.go's telemetryConfig/otlpConfig/prometheusConfig/tracingConfig exactly (Enabled/Insecure/Endpoint on otlp; Enabled on prometheus; Enabled/ SamplingRate on tracing) — sprout's strict decoder rejects any field these structs don't declare, so this chart must never invent one. otlp composes with global.stacklok.telemetry.otlp (#4409) field by field: endpoint coalesces (this chart's own value wins when set, else the global endpoint is used); enabled/insecure are ADDITIVE with their global counterpart (true if either is true) — there is no local setting that forces either off while the global default is on. tracing has no global counterpart of its own; it shares whichever otlp.endpoint (local-or-global) that composition resolves.

telemetry.otlp.enabled​

Type: bool

Default: false

Push metrics to an OTLP collector.

telemetry.otlp.endpoint​

Type: string

Default: ""

Collector endpoint (e.g. "otel-collector.observability:4318"). REQUIRED whenever otlp.enabled OR tracing.enabled is true (locally or, for otlp.enabled, via global.stacklok.telemetry.otlp.enabled) — the chart render fails when no endpoint is set, locally or via global.

telemetry.otlp.insecure​

Type: bool

Default: false

Disable TLS on the OTLP connection. Only for a collector reachable over a private, trusted network.

telemetry.prometheus.enabled​

Type: bool

Default: false

Expose a pull-scrapeable /metrics route on its own dedicated listener (metrics.port below).

telemetry.tracing​

Type: object

Default: {"enabled":false,"samplingRate":0}

Push traces to the SAME OTLP collector as otlp.endpoint above — tracing and metrics are independent signals sharing one transport. Off by default.

telemetry.tracing.enabled​

Type: bool

Default: false

Export spans via OTLP. Requires otlp.endpoint to be set (shared with metrics.otlp above), independently of otlp.enabled.

telemetry.tracing.samplingRate​

Type: int

Default: 0

Root-span sampling ratio (0.0-1.0). A sampled inbound traceparent is always honored regardless of this ratio (parent-based sampling). Zero (the default) is treated as "sample every root span" — leaving it unset when enabled is true doesn't silently sample nothing.

telemetry.metrics​

Type: object

Default: {"port":9464}

Dedicated /metrics listener, bound whenever prometheus.enabled is true. 9464 is OTel's conventional Prometheus-exporter port.

telemetry.headerLabels​

Type: list

Default: []

Bounded header-to-metric-attribute mappings the token-usage metric carries in addition to model/provider/token_type (telemetryConfig .HeaderLabels). permitted is the CLOSED set of values that pass through verbatim; any other observed value folds to "other", which is what keeps an operator-controlled header from becoming an unbounded-cardinality label. Absent by default: no header contributes a label.

Example:
headerLabels:
- header: x-stacklok-team
attribute: team
permitted: [platform, research]

resources​

Type: object

Default: {"limits":{"memory":"256Mi"},"requests":{"cpu":"50m","memory":"128Mi"}}

Resource limits and requests for the container. No CPU limit: an LLM gateway request holds its goroutine mostly blocked on upstream I/O, so a CPU ceiling only risks throttling a burst of concurrent streams without bounding anything the memory limit does not already bound.

readinessProbe​

Type: object

Default value
failureThreshold: 2
httpGet:
path: /readyz
port: http
initialDelaySeconds: 0
periodSeconds: 5
successThreshold: 1
timeoutSeconds: 3

Readiness probe. Hits /readyz, which answers 200 while serving and {"status":"not_ready","reason":"draining"} once a graceful drain has started.

livenessProbe​

Type: object

Default value
failureThreshold: 32
httpGet:
path: /healthz
port: http
initialDelaySeconds: 0
periodSeconds: 30
successThreshold: 1
timeoutSeconds: 5

Liveness probe. Hits /healthz, a static 200 that never rolls up a dependency. Its failureThreshold/periodSeconds are sized against the pod-grace inequality: (failureThreshold - 1) * periodSeconds > drain-deadline + completion-webhook budget At the shipped lifecycle.maxStreamDuration (15m) ceiling, drain-deadline is 900s and the completion-webhook budget is 5s, so the right-hand side is 905s. (32 - 1) * 30 = 930s > 905s satisfies it. templates/_helpers.tpl's sprout.validateLivenessBound guard recomputes this from whatever lifecycle.maxStreamDuration and livenessProbe values an operator sets, and fails the render naming both sides if the inequality breaks.

runtime​

Type: object

Default: {"drainDeadline":"","drainWindow":"","jwksWarmupTimeout":""}

Process flags cmd/sprout reads at startup, which are NOT config-file fields (the binary takes them on argv). Each is optional; leaving one empty renders no flag and the binary keeps its own compiled-in default.

runtime.drainWindow​

Type: string

Default: ""

-drain-window: how long a draining pod keeps ACCEPTING connections after SIGTERM, buying time for its EndpointSlice removal to propagate through kube-proxy/CNI. Go duration string. Binary default 5s.

runtime.drainDeadline​

Type: string

Default: ""

-drain-deadline: overrides the deadline otherwise DERIVED from lifecycle.maxStreamDuration (drainDeadlineFor in cmd/sprout/main.go). Leave empty to keep the derived value, which is what the rendered terminationGracePeriodSeconds is sized against — setting this without also raising lifecycle.maxStreamDuration decouples the two. Go duration string.

runtime.jwksWarmupTimeout​

Type: string

Default: ""

-jwks-warmup-timeout: per-issuer budget for the blocking first JWKS fetch at startup. An issuer that misses it is fatal (the process exits rather than serving 401s with a valid token). Go duration string. Binary default 10s.

lifecycle.maxStreamDuration​

Type: string

Default: "15m"

The upstream stream ceiling every other timing in this chart derives from: the derived terminationGracePeriodSeconds, the liveness-bound guard above, the ConfigMap's lifecycle.max_stream_duration (which the binary's own validateDrainBudgetFitsGrace re-checks at startup), and httpRoute.timeouts below. Go duration string.

providers​

Type: list

Default: []

Upstream providers. Each entry mirrors catalog.ProviderConfig (enterprise/sprout/catalog/catalog.go): kind, baseUrl, models, and the per-kind fields a kind actually reads (region for bedrock-anthropic/vertex; project for vertex; awsProfile for bedrock-anthropic). credentialSecretRef names the Secret+key backing that provider's credential; the chart mounts it and writes the matching __file field (api_key_file / aws_credentials_file / gcp_service_account_file) — never an inline plaintext credential, which templates/_helpers.tpl's sprout.validateConfig REFUSES outright (the ConfigMap is plaintext; only the __file indirection keeps the credential bytes in a Secret). A provider naming no credentialSecretRef renders no mount and no *_file key — the shipped Bedrock ambient-credential-chain posture (EKS Pod Identity) keeps working.

Example:
providers:
- kind: openai
baseUrl: https://api.openai.com
models: ['gpt-5.5']
credentialSecretRef:
name: ai-gateway-v2-openai-credentials
key: api-key
- kind: bedrock-anthropic
baseUrl: https://bedrock-runtime.us-east-1.amazonaws.com
region: us-east-1
models: ['us.anthropic.claude-opus-4-8']
# No credentialSecretRef: resolves the AWS default credential chain
# (EKS Pod Identity) at runtime.

auth​

Type: object

Default: {"issuers":[]}

Trusted OIDC issuers for the JWT authentication path (authConfig.Issuers in cmd/sprout/main.go): issuer/audience/bindingClaim/tokenUse/jwksUri.

directory.endpoint​

Type: string

Default: ""

Directory gRPC endpoint (host:port), e.g. "enterprise-server.toolhive-system:9091". Empty disables virtual-key resolution and group-label telemetry.

directory.insecure​

Type: bool

Default: false

Local development only: dial the directory over plaintext with no caller credential projected.

directory.groupsResolutionIssuer​

Type: string

Default: ""

The iss the virtual-key path presents to ResolveIdentity so the directory can bind the resolved uid to an issuer and return its groups.

directory.groupLabels​

Type: list

Default: []

Operator-chosen allowlist of directory GroupRef.Labels names (e.g. department, cost_center; see #3895 for how a group acquires a label) this gateway resolves onto Principal.Labels, their own governance header (X-<Label>, e.g. "department" -> X-Department, "cost_center" -> X-Cost-Center), and the audit trail. Empty by default: no label is resolved or forwarded. Each entry must be a bounded lower-case underscore-separated identifier and must not derive a header colliding with a reserved governance header or a telemetry.headerLabels mapping — the gateway refuses to start otherwise.

directory.tokenProjection.enabled​

Type: bool

Default: false

Project a ServiceAccount token for directory.token_file, mounted at <mountPath>/token. Required whenever directory.endpoint is set and directory.insecure is false, or the gateway has no caller credential to present and the directory's caller gate refuses it.

directory.tokenProjection.mountPath​

Type: string

Default: "/var/run/secrets/directory"

Directory the projected token is mounted into.

directory.tokenProjection.audience​

Type: string

Default: "directory-service"

Audience the directory's caller-auth gate expects.

directory.tokenProjection.expirationSeconds​

Type: int

Default: 3600

Requested lifetime of the projected token. Minimum 600 — the API server REJECTS a Pod spec below that floor rather than clamping to it.

budgets.endpoint​

Type: string

Default: ""

Budget webhook receiver base URL, e.g. "https://enterprise-server.toolhive-system:443". Empty leaves the gateway UNGOVERNED (no admission, no charge) — see acknowledgeUnmetered below.

budgets.caBundle.secretName​

Type: string

Default: ""

Secret holding the PEM bundle that verifies the budgets receiver's serving certificate. Required when endpoint is set: falls back to global.webhooks.caBundleSecret, and the render fails when neither names a Secret. Paired with tokenProjection below.

budgets.caBundle.key​

Type: string

Default: "ca.crt"

Key within that Secret holding the PEM bundle.

budgets.mountPath​

Type: string

Default: "/etc/budgets-tls"

Directory the CA bundle is mounted into. Each rendered budgets hook entry's ca_file is <mountPath>/<caBundle.key>.

budgets.tokenProjection.enabled​

Type: bool

Default: false

Project a ServiceAccount token for the budgets hook entries' token_file, validated by the receiver as an audience-bound Bearer. Required when endpoint is set.

budgets.tokenProjection.mountPath​

Type: string

Default: "/var/run/secrets/budgets"

Directory where the projected budgets ServiceAccount token is mounted. Budgets hooks read the token file beneath this path.

budgets.tokenProjection.audience​

Type: string

Default: "budgets-webhook"

Audience requested for the projected budgets ServiceAccount token. Match the audience accepted by Enterprise Manager's budgets receiver.

budgets.tokenProjection.expirationSeconds​

Type: int

Default: 3600

Requested lifetime, in seconds, of the projected budgets ServiceAccount token. Kubernetes rotates the token before expiry.

budgets.capture​

Type: bool

Default: false

Opt this gateway into conversation capture (forwards the client request body to the receiver). Default false. Capture fires after the required hooks, so with scanning on it carries the redacted body. With scanning off it carries the raw body, and the install notes warn about it.

budgets.sessionHeaders​

Type: list

Default: []

Ordered session-correlation candidate headers this gateway projects. INHERITED from the umbrella's global.stacklok.sessionHeaders when left empty, so one setting covers whichever data plane is installed and the gateway cannot drift from the receiver that keys on it. Set here only for the rare install whose gateway must forward a different list than its receiver keys on. An empty list at BOTH levels renders the shipped defaults (x-claude-code-session-id, x-mecatl-root-session-id, x-mecatl-session-id, session-id) onto each budgets hook entry.

budgets.acknowledgeUnmetered​

Type: bool

Default: false

REQUIRED (true) to render a chart with no budgets.endpoint configured. The unified webhook lifecycle makes the budgets receiver's 200 the definition of "tracked" — a gateway with no budgets endpoint is unmetered, and templates/_helpers.tpl's sprout.validateConfig refuses that render unless an operator explicitly acknowledges it here, so "no budgets configured" can never be an accident.

modelAccess.enabled​

Type: bool

Default: false

Enforce directory model access on this gateway. Inference is refused (HTTP 403, before any provider call) for a model the caller's grants do not cover, and /v1/models lists only the models the caller may use. Both halves fail closed. Needs directory.endpoint (the discovery filter dials the directory's ResolveModelAccess RPC). Reuses budgets.tokenProjection (its audience must equal the receiver's budgets audience) and budgets.caBundle / global.webhooks.caBundleSecret for the receiver's token and CA, so it also works on a gateway that is deliberately unmetered.

modelAccess.endpoint​

Type: string

Default: ""

Receiver base URL, e.g. "https://enterprise-server.toolhive-system:443". Defaults to budgets.endpoint, which is the same listener.

audit.enabled​

Type: bool

Default: false

Enable audit logging (auditConfig.Enabled). ADDITIVE with global.stacklok.audit.enabled (#4409, the umbrella's fleet-wide audit default): audit is on if EITHER this is true OR the global default is. There is no way to force this OFF while the global default is on.

capture​

Type: object

Default: {}

Inline capture config passthrough (sprout.CaptureConfig), rendered verbatim into the ConfigMap's "capture" key when non-empty. ABSENT BY DEFAULT and never enabled implicitly: capture snapshots record full request and response bodies.

networkPolicy.enabled​

Type: bool

Default: false

Enable a NetworkPolicy for the sprout pod. Default off: regulated customers running a default-deny CNI policy engine opt in explicitly.

networkPolicy.ingress.gatewayNamespaces​

Type: list

Default: ["envoy-gateway-system"]

Namespaces whose pods may reach the gateway port. The shared Envoy Gateway's data plane is the only thing that should: sprout sits behind it and has no ingress of its own. An EMPTY list with allowAllSources false FAILS the render rather than silently admitting every pod in the cluster, because a NetworkPolicy that looks locked down but is not is worse than none at all.

networkPolicy.ingress.gatewayPodSelector​

Type: object

Default: {"app.kubernetes.io/name":"envoy"}

Optional narrowing WITHIN those namespaces. The default names the Envoy pods owning the shared eg Gateway, so the Envoy Gateway control plane and the rate-limit pod beside them are not admitted. Set to {} to admit the whole namespace.

networkPolicy.ingress.monitoringNamespaces​

Type: list

Default: ["monitoring"]

Namespaces whose pods may scrape the dedicated /metrics listener, applied only when telemetry.prometheus.enabled is true. Scoped to the metrics port alone, so admitting a monitoring namespace never becomes a second way into the gateway's ingress listener. The listener is UNAUTHENTICATED, so this list is the only thing bounding who reads it.

networkPolicy.ingress.allowAllSources​

Type: bool

Default: false

Admit every source instead of the lists above. An explicit, auditable opt-out for an install whose namespace topology the defaults do not fit; it is not a default, because an allow-all ingress rule leaves the unauthenticated /metrics listener readable by every pod in the cluster.

networkPolicy.governanceEgress​

Type: object

Default: {"enabled":true,"namespaces":[],"podSelector":{"app.kubernetes.io/name":"enterprise-manager"}}

When allowExternalEgress is false, emit egress rules to the governance services this install actually dials (directory, budgets, and the enterprise-manager the Gateway connectivity poll reads), with the ports taken from directory.endpoint and budgets.endpoint, and enterprise-manager admitted by its named http container port, so a port change cannot orphan the rule. Without them a locked-down install fails CLOSED at request time — budget admission is fail-closed, so every request 503s — with nothing in the Helm or pod state to point at.

networkPolicy.governanceEgress.namespaces​

Type: list

Default: []

Namespaces the governance services run in. Empty means the release namespace, which is where the umbrella installs enterprise-manager. If directory.endpoint or budgets.endpoint instead point at a service in a DIFFERENT namespace, this must be set to match, or the emitted egress rule scopes to the wrong namespace and admits nothing — the same silent fail-closed 503 as a port mismatch, since NetworkPolicy has no visibility into what a hostname resolves to, only which namespace/pod selector it's scoped to.

networkPolicy.governanceEgress.podSelector​

Type: object

Default: {"app.kubernetes.io/name":"enterprise-manager"}

Pods within those namespaces. Set to {} to admit the whole namespace.

networkPolicy.allowExternalEgress​

Type: bool

Default: true

Allow ALL egress traffic. NOTE: this GRANTS allow-all egress, overriding any cluster-wide default-deny egress policy. Set to false and populate egress below to actually lock this pod down.

networkPolicy.egress​

Type: list

Default: []

Additional egress rules (Kubernetes NetworkPolicyEgressRule shape), applied when allowExternalEgress is false. Must cover DNS (UDP/TCP 53) plus every provider/directory/budgets/OTLP destination this install actually dials. When a configured bedrock-anthropic provider carries no static credentials and no aws_credentials_file (the ambient EKS-Pod-Identity chain), the chart automatically adds the Pod Identity Agent's link-local egress rule (169.254.170.23/32 and fd00:ec2::23/128, TCP 80) — no need to list it here.

extraEnv​

Type: list

Default: []

Additional environment variables for the sprout container.

extraVolumes​

Type: list

Default: []

Additional volumes to attach to the pod.

extraVolumeMounts​

Type: list

Default: []

Additional volume mounts to attach to the sprout container.

nodeSelector​

Type: object

Default: {}

Node labels for pod assignment

tolerations​

Type: list

Default: []

Tolerations for pod assignment

affinity​

Type: object

Default: {}

Affinity rules for pod assignment

topologySpreadConstraints​

Type: list

Default: [{"maxSkew":1,"topologyKey":"kubernetes.io/hostname","whenUnsatisfiable":"ScheduleAnyway"}]

Keep the replicas off one node, so a single node loss cannot take the whole data plane with it. ScheduleAnyway, not DoNotSchedule: a pod that cannot spread must still run, since refusing to schedule would trade a redundancy goal for an outage. Set to [] to disable spreading entirely.

httpRoute.enabled​

Type: bool

Default: false

Render a Gateway API HTTPRoute attaching this Service to the shared Envoy Gateway. Default off: the chart cannot guess the shared Gateway's name/namespace, so a bare helm install would otherwise fail the render on an empty parentRefs. Set true and populate parentRefs below once you know the target Gateway.

httpRoute.parentRefs​

Type: list

Default: []

parentRefs (Gateway API HTTPRouteSpec.ParentRefs shape): name, namespace, and optional sectionName of the shared Gateway to attach to. Required when httpRoute.enabled is true.

httpRoute.hostname​

Type: string

Default: ""

Hostname this route serves. Optional; omitted from the rendered route when empty (matches every hostname the parent Gateway listener accepts).

config.mountPath​

Type: string

Default: "/etc/ai-gateway-v2/config"

Directory the config ConfigMap is mounted into.

config.fileName​

Type: string

Default: "config.json"

File name of the rendered config JSON within mountPath. Sprout takes ONE -config flag naming this single file (unlike connector-gateway's two-file OSS/enterprise split) — the full path is passed via the -config container arg.

config.credentialsRoot​

Type: string

Default: "/var/run/secrets/ai-gateway-v2"

Directory every provider credential file (api_key_file, aws_credentials_file, gcp_service_account_file) must resolve inside. Passed via the -credentials-root container arg — a FLAG, not a config key, so the ConfigMap's writer cannot widen it (cmd/sprout/main.go's readCredentialUnderRoot). Matches defaultCredentialsRoot.

scanning​

Type: object

Default value
adapter:
image:
digest: ''
pullPolicy: IfNotPresent
repository: image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor
tag: ''
logLevel: ''
maxReplicas: null
metrics:
enabled: false
port: 9090
nodeSelector: {}
podAnnotations: {}
replicas: 2
resources:
limits:
cpu: 500m
memory: 256Mi
requests:
cpu: 50m
memory: 64Mi
targetCPUUtilization: null
tolerations: []
topologySpreadConstraints: []
allowlist: []
allowlistMatch: exact
audience: ''
enabled: false
entities: []
entityActions: []
hook:
caMountPath: /etc/scanning-tls
timeout: ''
tokenExpirationSeconds: 3600
tokenMountPath: /var/run/secrets/scanning
ner:
circuitBreaker:
failureThreshold: 5
resetTimeout: 30s
concurrency: 8
failureAction: fail-closed
timeout: 5s
presidio:
autoscaling:
mode: HPA
image:
digest: ''
pullPolicy: IfNotPresent
repository: image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/presidio-analyzer
tag: ''
language: en
maxReplicas: null
nodeSelector: {}
podAnnotations: {}
recognizers: []
replicas: 2
resources:
limits:
cpu: '2'
memory: 2Gi
requests:
cpu: 500m
memory: 1Gi
scoreThreshold: 50
targetCPUUtilization: null
tolerations: []
topologySpreadConstraints: []
requestAction: block
resultCache:
enabled: false
macSecret:
key: mac-secret
name: ''
redis:
clusterMode: null
existingSecret: ''
existingSecretKey: ''
host: ''
port: null
tls: null
username: ''
ttl: ''
tls:
caBundleKey: ca.crt
caBundleSecret: ''
issuerRef:
kind: ''
name: ''

PII scanning. Enabling it renders the Presidio Analyzer stack and a scanning-adapter (the main-processor image in scanning-adapter mode) that turns Presidio detections into an allow/redact/block verdict, and wires the gateway's scan hook to that adapter. The gateway's scanning config block is derived from these values: endpoint, CA file, projected token and timeout all follow the adapter the chart renders, so scanning.enabled=true plus an issuer and a CA bundle is enough for a working scan.

scanning.enabled​

Type: bool

Default: false

Render the Presidio stack and the scanning-adapter (each with its Deployment, Service, ServiceAccount and NetworkPolicy, plus the adapter's ClusterRoleBinding, serving Certificate and config) and wire the gateway's scan hook to the adapter. Off by default; when false nothing scanning-related renders, including the cluster-scoped ClusterRoleBinding.

scanning.audience​

Type: string

Default: ""

Audience of the token the gateway presents to the adapter. The adapter requires exactly this audience (401 otherwise). Empty derives https://<adapter-service>.<release-namespace>.svc.

scanning.requestAction​

Type: string

Default: "block"

Global action when Presidio finds an entity: block (403), redact (replace the match with a placeholder) or log-only.

scanning.entityActions​

Type: list

Default: []

Per-entity overrides of requestAction. Each entry: entity (Presidio entity name), action (block|redact|log-only) and an optional placeholder used when the action is redact.

scanning.entities​

Type: list

Default: []

Presidio entities to detect. Empty means the default list in files/presidio/default_entities.json (PERSON, LOCATION, DATE_TIME, CREDIT_CARD, US_SSN, PHONE_NUMBER, EMAIL_ADDRESS, IBAN_CODE, IP_ADDRESS, US_ITIN, UK_NHS). The supportedEntity of every custom recognizer under presidio.recognizers is always added. Editing this list (or adding a recognizer with a new entity) rolls the adapter, so an invalid entity fails the rollout instead of being ignored. That includes a built-in whose recognizer the bundled Presidio registry ships disabled (most country-specific ones, e.g. IN_AADHAAR, AU_TFN), which Presidio would otherwise skip without detecting anything.

scanning.allowlist​

Type: list

Default: []

Strings Presidio never reports as a detection.

scanning.allowlistMatch​

Type: string

Default: "exact"

How allowlist entries match: exact or regex.

scanning.ner.timeout​

Type: string

Default: "5s"

Budget for one Presidio call, as a single-unit Go duration. Must be positive.

scanning.ner.concurrency​

Type: int

Default: 8

Concurrent Presidio calls within one request. The adapter scans a request's text nodes in waves of this size. It is not a pod-wide cap: every in-flight request gets its own limit.

scanning.ner.failureAction​

Type: string

Default: "fail-closed"

What the adapter does when Presidio is unreachable or slow: fail-closed blocks the request, fail-open admits it.

scanning.ner.circuitBreaker.failureThreshold​

Type: int

Default: 5

Consecutive Presidio failures that trip the breaker.

scanning.ner.circuitBreaker.resetTimeout​

Type: string

Default: "30s"

How long the breaker stays open before probing again.

scanning.hook.timeout​

Type: string

Default: ""

Budget the gateway gives the adapter, as a single-unit Go duration. Empty derives ner.timeout + 2s, so a slow Presidio surfaces the adapter's fail-closed 403 rather than the gateway's own 504. That covers one wave of Presidio calls. The adapter has no request-wide deadline, so a request with more than ner.concurrency text nodes runs several waves, and a slow Presidio that still answers within ner.timeout can push it past this budget into a gateway 504. Raise it if large requests need more than one wave under load. An explicit value must exceed ner.timeout.

scanning.hook.tokenExpirationSeconds​

Type: int

Default: 3600

Requested lifetime of the projected token, minimum 600.

scanning.hook.tokenMountPath​

Type: string

Default: "/var/run/secrets/scanning"

Directory the projected token is mounted into on the gateway pod.

scanning.hook.caMountPath​

Type: string

Default: "/etc/scanning-tls"

Directory the adapter CA bundle is mounted into on the gateway pod.

scanning.tls.issuerRef​

Type: object

Default: {"kind":"","name":""}

cert-manager issuer for the adapter's serving certificate. Empty name falls back to global.webhooks.issuerRef; one of the two must name an issuer.

scanning.tls.caBundleSecret​

Type: string

Default: ""

Secret holding the PEM CA that verifies the adapter's serving certificate, in the release namespace. Empty falls back to global.webhooks.caBundleSecret; one of the two is required.

scanning.tls.caBundleKey​

Type: string

Default: "ca.crt"

Key within that Secret holding the PEM bundle.

scanning.adapter.image.repository​

Type: string

Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor

Scanning-adapter image: the ai-gateway main-processor, run with PROCESSOR_MODE=scanning-adapter.

scanning.adapter.image.tag​

Type: string

Default: ""

Image tag (defaults to appVersion if empty).

scanning.adapter.image.digest​

Type: string

Default: ""

Optional image digest, appended as @<digest> after the tag.

scanning.adapter.image.pullPolicy​

Type: string

Default: "IfNotPresent"

Image pull policy.

scanning.adapter.replicas​

Type: int

Default: 2

Replica count. Also the HPA's minReplicas when one renders. A PodDisruptionBudget (maxUnavailable 1) renders at 2 or more, and the pods prefer separate nodes. The scan hook is fail-closed, so a single adapter is a single point of failure for every request.

scanning.adapter.maxReplicas​

Type: string

Default: nil

Upper bound for the HPA. Unset means no HPA, and replicas is written on the Deployment.

scanning.adapter.targetCPUUtilization​

Type: string

Default: nil

HPA target average CPU utilization, 1-100. Requires maxReplicas above 1, the threshold at which an HPA renders. Defaults to 75 when unset.

scanning.adapter.podAnnotations​

Type: object

Default: {}

Annotations added to the adapter pods. The chart's own checksum and scrape annotations win over a same-named entry.

scanning.adapter.nodeSelector​

Type: object

Default: {}

Node labels for adapter pod assignment. The scan hook is fail-closed, so set this (and tolerations) to follow the gateway onto dedicated nodes.

scanning.adapter.tolerations​

Type: list

Default: []

Tolerations for adapter pod assignment.

scanning.adapter.topologySpreadConstraints​

Type: list

Default: []

Topology spread constraints for the adapter pods. The chart injects the adapter's selector labels into each entry that sets no labelSelector.

scanning.adapter.resources​

Type: object

Default: {"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}

Adapter container resources.

scanning.adapter.logLevel​

Type: string

Default: ""

Adapter log level (debug|info|warn|error). Empty keeps the binary default.

scanning.adapter.metrics.enabled​

Type: bool

Default: false

Serve the adapter's Prometheus metrics on port, and admit scrapes to that port in its NetworkPolicy from networkPolicy.ingress.monitoringNamespaces (or from any source under networkPolicy.ingress.allowAllSources).

scanning.resultCache.enabled​

Type: bool

Default: false

Cache Presidio results in Redis/Valkey, keyed by request content and the detection inputs (recognizers checksum, Presidio image, PSL snapshot). While the Presidio Deployment is mid-rollout, old and new pods answer side by side, so the adapter withholds cache writes until every Presidio pod runs the detection inputs its key encodes and that state has held for 30s. Reads continue throughout. The adapter reads the Deployment through a namespaced Role the chart grants; if it cannot, the cache stays read-only. Withheld writes are counted on stacklok_ai_gateway_ner_cache_withheld_writes_total, so a rate that never drops means the cache has gone read-only. This release's entries live under aigw:ner:v1:<release-namespace>:<fullname>:. Requires a Redis host here or in global.redis.

scanning.resultCache.ttl​

Type: string

Default: ""

Entry lifetime as a Go duration. Empty keeps the runtime default (24h).

scanning.resultCache.redis.host​

Type: string

Default: ""

Redis host. Empty falls back to global.redis.host.

scanning.resultCache.redis.port​

Type: string

Default: nil

Redis port. Empty falls back to global.redis.port.

scanning.resultCache.redis.clusterMode​

Type: string

Default: nil

Speak the Redis Cluster protocol. Unset falls back to global.redis.clusterMode.

scanning.resultCache.redis.username​

Type: string

Default: ""

ACL username. Empty falls back to global.redis.username.

scanning.resultCache.redis.existingSecret​

Type: string

Default: ""

Secret holding the Redis password, in the release namespace. Empty falls back to global.redis.existingSecret.

scanning.resultCache.redis.existingSecretKey​

Type: string

Default: ""

Key within that Secret. Empty falls back to global.redis.existingSecretKey.

scanning.resultCache.redis.tls​

Type: string

Default: nil

Dial Redis over TLS. Unset falls back to global.redis.tls.enabled.

scanning.resultCache.macSecret.name​

Type: string

Default: ""

Bring your own Secret holding the cache's HMAC key, in the release namespace (e.g. one managed by External Secrets or sealed-secrets). The adapter then only reads it and creates nothing, which is what makes the arrangement safe: a second writer at the same name would race the manager that owns it and replace the value, cold-starting the cache.

Empty makes the adapter provision one itself, named <fullname>-ner-cache-mac: at startup it gets the Secret and creates it when absent, and replicas that race on the create converge on one key. Provisioning is create-if-absent on every start, so it works on a fresh install and an upgrade alike, and a restart never replaces a live value. The value is 48 bytes of crypto/rand, base64-encoded so it is safe to carry as an env var, and it never passes through Helm values. The chart renders a Role and RoleBinding granting the adapter get (pinned to that Secret) and create in the release namespace; without that grant the adapter logs a WARN and runs uncached. The Secret is not owned by the release, so it survives uninstall and a Helm or Argo prune.

A supplied Secret's value must be at least 32 bytes of printable text, not raw bytes: it reaches the pod as an env var, and the kubelet cannot ship a non-UTF-8 env value over CRI (the pod sticks in CreateContainerError). A missing Secret leaves the cache off.

Rotation, either way, is to replace the Secret's value and restart the adapter. Every existing entry then fails verification and is re-scanned: a cache cold start, never a correctness event, and the whole incident response for a leaked secret.

scanning.resultCache.macSecret.key​

Type: string

Default: "mac-secret"

Key within that Secret. Applies to a supplied Secret and to the one the adapter provisions.

scanning.presidio.image.repository​

Type: string

Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/presidio-analyzer

Presidio Analyzer image, built from the pinned upstream 2.2.364 source and published at the platform tag.

scanning.presidio.image.tag​

Type: string

Default: ""

Image tag (defaults to appVersion if empty).

scanning.presidio.image.digest​

Type: string

Default: ""

Optional image digest, appended as @<digest> after the tag.

scanning.presidio.image.pullPolicy​

Type: string

Default: "IfNotPresent"

Image pull policy.

scanning.presidio.replicas​

Type: int

Default: 2

Replica count. Also the HPA's minReplicas when one renders. A PodDisruptionBudget renders at 2 or more.

scanning.presidio.maxReplicas​

Type: string

Default: nil

Upper bound for the HPA. Above 1, with autoscaling.mode HPA, the chart renders an HPA and omits replicas from the Deployment. Unset means no HPA.

scanning.presidio.targetCPUUtilization​

Type: string

Default: nil

HPA target average CPU utilization, 1-100. Requires an HPA to render (autoscaling.mode HPA and maxReplicas above 1). Defaults to 75 when unset.

scanning.presidio.autoscaling.mode​

Type: string

Default: "HPA"

Who owns the replica count. HPA: the chart renders an HPA when maxReplicas > 1. External: an out-of-band scaler (e.g. KEDA) owns it, so the chart renders no HPA and omits replicas. None: the chart pins replicas and renders no HPA. On a live install, the upgrade that first omits replicas (setting maxReplicas, or switching to External) makes Helm's three-way merge remove the field, so the Deployment drops to 1 analyzer until the scaler acts, and that pod must pass the warmup startupProbe before it serves. Make that switch in a low-traffic window.

scanning.presidio.podAnnotations​

Type: object

Default: {}

Annotations added to the Presidio pods. The chart's own checksum annotations win over a same-named entry.

scanning.presidio.nodeSelector​

Type: object

Default: {}

Node labels for Presidio pod assignment.

scanning.presidio.tolerations​

Type: list

Default: []

Tolerations for Presidio pod assignment.

scanning.presidio.topologySpreadConstraints​

Type: list

Default: []

Topology spread constraints for the Presidio pods. The chart injects the Presidio selector labels into each entry that sets no labelSelector.

scanning.presidio.resources​

Type: object

Default: {"limits":{"cpu":"2","memory":"2Gi"},"requests":{"cpu":"500m","memory":"1Gi"}}

Analyzer container resources.

scanning.presidio.language​

Type: string

Default: "en"

Analysis language, stamped onto every custom recognizer and used by the startup warmup request. Only en is supported.

scanning.presidio.scoreThreshold​

Type: int

Default: 50

Minimum Presidio confidence, 0-100, for a detection to count. Read by the scanning-adapter; it does not change the Presidio pods. 0 is honoured and counts every detection.

scanning.presidio.recognizers​

Type: list

Default: []

Custom Pattern recognizers, appended after the vendored default recognizers. Each entry: name (unique across the custom and vendored default recognizers), supportedEntity (an UPPER_SNAKE entity name that is not one of Presidio's built-in entities), and at least one of patterns (name unique within the recognizer, regex in RE2 syntax, score 0-100, default 50) or denyList; optional contextWords. A regex that does not compile, or a recognizers ConfigMap over 1 MiB, fails the render. Editing this list rolls the Presidio pods.

guardrails.enabled​

Type: bool

Default: false

Render the guardrails-adapter (Deployment, Service, ServiceAccount, NetworkPolicy, plus its ClusterRoleBinding and serving Certificate) and wire the gateway's guardrails hook to it. The adapter screens each request's user-authored text with Amazon Bedrock Guardrails. Off by default; when false nothing guardrails-related renders, including the cluster-scoped ClusterRoleBinding. The hook runs after scanning, so it screens the redacted body.

guardrails.mode​

Type: string

Default: "Enforce"

Enforce blocks a request the guardrail intervenes on. Monitor screens off the request path and never blocks: the hook is an observation entry, so the request is forwarded without waiting for the verdict. A screen Enforce would have blocked is counted on stacklok.ai_gateway.webhook.invocations (webhook_name="guardrails", webhook_outcome="deny") and logged at WARN with the guardrail's annotations. The counts are best-effort: a fire shed under queue pressure is counted on stacklok.ai_gateway.webhook.invocations_dropped instead.

guardrails.failurePolicy​

Type: string

Default: "Fail"

What happens when the adapter is unreachable, slow or errors, under Enforce only: Fail blocks the request, Ignore admits it. Monitor never blocks.

guardrails.unscreenableContentPolicy​

Type: string

Default: "Deny"

What happens to a request whose user content carries no screenable text (media only, or an unrecognized shape): Deny blocks it, Admit lets it through unscreened.

guardrails.audience​

Type: string

Default: ""

Audience of the token the gateway presents to the adapter. The adapter requires exactly this audience (401 otherwise). Empty derives https://<adapter-service>.<release-namespace>.svc.

guardrails.bedrock.guardrailId​

Type: string

Default: ""

Bedrock guardrail identifier. Required.

guardrails.bedrock.guardrailVersion​

Type: string

Default: ""

Guardrail version: DRAFT or a positive integer, written as a string ("1", not 1). Required.

guardrails.bedrock.region​

Type: string

Default: ""

AWS region of the guardrail, e.g. us-east-1. Required.

guardrails.bedrock.promptWhitelist​

Type: object

Default: {"prompts":[],"regexes":[]}

Prompts excluded from screening. prompts are exact matches on a complete user turn. regexes are RE2 expressions that match anywhere in the turn unless anchored, so anchor both ends (^ping$, not ^ping).

guardrails.bedrock.credentialsSecretRef​

Type: object

Default: {"name":""}

Secret holding a shared AWS credentials file under the key credentials, mounted into the adapter. Empty uses the pod's ambient AWS identity: EKS Pod Identity (associate the role with the adapter's ServiceAccount, <fullname>-guardrails-adapter) or IRSA (annotate it through adapter.serviceAccount.annotations).

guardrails.bedrock.endpoint​

Type: string

Default: ""

Bedrock Runtime base URL override, for a PrivateLink endpoint or a test double. Empty uses the SDK's regional endpoint.

guardrails.hook.timeout​

Type: string

Default: "5s"

Budget the gateway gives the adapter, as a single-unit Go duration of at most 120s. The adapter's own Bedrock call must finish inside it.

guardrails.hook.tokenExpirationSeconds​

Type: int

Default: 3600

Requested lifetime of the projected token, minimum 600.

guardrails.hook.tokenMountPath​

Type: string

Default: "/var/run/secrets/guardrails"

Directory the projected token is mounted into on the gateway pod.

guardrails.hook.caMountPath​

Type: string

Default: "/etc/guardrails-tls"

Directory the adapter CA bundle is mounted into on the gateway pod.

guardrails.tls.issuerRef​

Type: object

Default: {"kind":"","name":""}

cert-manager issuer for the adapter's serving certificate. Empty name falls back to global.webhooks.issuerRef; one of the two must name an issuer.

guardrails.tls.caBundleSecret​

Type: string

Default: ""

Secret holding the PEM CA that verifies the adapter's serving certificate, in the release namespace. Empty falls back to global.webhooks.caBundleSecret; one of the two is required.

guardrails.tls.caBundleKey​

Type: string

Default: "ca.crt"

Key within that Secret holding the PEM bundle.

guardrails.adapter.image.repository​

Type: string

Default value
image-proxy.stacklok.com/proxy/stacklok-enterprise/ghcr.io/stacklok/stacklok-enterprise/ai-gateway-main-processor

Guardrails-adapter image: the ai-gateway main-processor, run with PROCESSOR_MODE=guardrails-adapter.

guardrails.adapter.image.tag​

Type: string

Default: ""

Image tag (defaults to appVersion if empty).

guardrails.adapter.image.digest​

Type: string

Default: ""

Optional image digest, appended as @<digest> after the tag.

guardrails.adapter.image.pullPolicy​

Type: string

Default: "IfNotPresent"

Image pull policy.

guardrails.adapter.replicas​

Type: int

Default: 2

Replica count. Also the HPA's minReplicas when one renders. A PodDisruptionBudget (maxUnavailable 1) renders at 2 or more, and the pods prefer separate nodes. Under failurePolicy: Fail a single adapter is a single point of failure for every request.

guardrails.adapter.maxReplicas​

Type: string

Default: nil

Upper bound for the HPA. Unset means no HPA, and replicas is written on the Deployment.

guardrails.adapter.targetCPUUtilization​

Type: string

Default: nil

HPA target average CPU utilization, 1-100. Requires maxReplicas above 1, the threshold at which an HPA renders. Defaults to 75 when unset.

guardrails.adapter.podAnnotations​

Type: object

Default: {}

Annotations added to the adapter pods. The chart's own checksum annotation wins over a same-named entry.

guardrails.adapter.nodeSelector​

Type: object

Default: {}

Node labels for adapter pod assignment.

guardrails.adapter.tolerations​

Type: list

Default: []

Tolerations for adapter pod assignment.

guardrails.adapter.topologySpreadConstraints​

Type: list

Default: []

Topology spread constraints for the adapter pods. The chart injects the adapter's selector labels into each entry that sets no labelSelector.

guardrails.adapter.serviceAccount.annotations​

Type: object

Default: {}

Annotations on the adapter's ServiceAccount, e.g. the eks.amazonaws.com/role-arn of an IRSA role.

guardrails.adapter.resources​

Type: object

Default: {"limits":{"cpu":"500m","memory":"256Mi"},"requests":{"cpu":"50m","memory":"64Mi"}}

Adapter container resources.