Configuration
The Remediator Agent is configured through three Kubernetes custom resources:
| Resource | Purpose |
|---|---|
| ToolConfig | Git provider credentials and PR defaults |
| LLMConfig | AI provider settings (Nirmata AI, AWS Bedrock, Azure OpenAI) |
| Remediator | What to scan, when to run, and what actions to take |
Nirmata Platform Authentication
The Helm chart’s nirmata.auth value selects how — or whether — the agent authenticates to Nirmata Control Hub.
nirmata.auth | Credential injected | Use when |
|---|---|---|
serviceAccountToken (default) | SERVICE_ACCOUNT_TOKEN from nirmata.serviceAccountTokenSecret | Standard install |
apiToken | API_TOKEN from nirmata.apiTokenSecret | You use an API token instead of a service account |
none | None | You supply your own LLM provider and your own Git credentials |
Set nirmata.auth=none to run the agent with no Nirmata platform credential at all — for example an install with no Control Hub connectivity, using AWS Bedrock plus a GitHub PAT. The agent still reaches your LLM provider and Git provider, so this removes the dependency on Control Hub, not on outbound network access. The following Remediator features require a credential and are unavailable with none:
- The
nirmataAILLM provider - The
nirmata-appGit authentication method - Policy exception requests (the
@nirmatabot request-exceptioncommand)
The chart validates this at install time rather than letting it fail at runtime. Combining nirmata.auth=none with llm.provider=nirmataAI or tool.credentials.method=nirmata-app aborts the install with an explanatory error.
ToolConfig
ToolConfig defines how the agent authenticates with your Git provider and sets defaults for the pull requests it creates.
GitHub — Personal Access Token
kubectl create secret generic github-pat-token \
--from-literal=token=GITHUB_PAT_TOKEN \
--namespace nirmata
kubectl apply -f - <<EOF
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: ToolConfig
metadata:
name: toolconfig-sample
namespace: nirmata
spec:
type: github
credentials:
method: pat
pat:
tokenSecretRef:
name: github-pat-token
namespace: nirmata
key: token
defaults:
git:
pullRequests:
branchPrefix: "remediation-"
titleTemplate: "[Auto-Remediation] Fix policy violations: "
commitMessageTemplate: "Auto-fix: Remediate policy violations: "
customLabels:
- "auto-remediation"
- "security"
systemLabels:
- "clusterName"
- "namespace"
EOF
```text
### GitHub — Nirmata GitHub App (Recommended)
Using the Nirmata GitHub App avoids managing secrets manually and provides automatic token rotation. Follow the [GitHub Authentication guide](../github-authentication/) to set up the App integration first.
```yaml
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: ToolConfig
metadata:
name: toolconfig-sample
namespace: nirmata
spec:
type: github
credentials:
method: nirmata-app
defaults:
git:
pullRequests:
branchPrefix: "remediation-"
titleTemplate: "[Auto-Remediation] Fix policy violations: "
commitMessageTemplate: "Auto-fix: Remediate policy violations: "
customLabels:
- "auto-remediation"
systemLabels:
- "clusterName"
- "namespace"
```text
### GitHub — Custom GitHub App
Use your own GitHub App when it must be owned by your organization, or when the agent runs with `nirmata.auth=none`. See the [GitHub Authentication guide](../github-authentication/#3-custom-github-app) for creating the App and the permissions it needs.
```bash
kubectl create secret generic github-app-private-key \
--from-file=private-key.pem=/path/to/your-app.private-key.pem \
--namespace nirmata
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: ToolConfig
metadata:
name: github-app-tool
namespace: nirmata
spec:
type: github
credentials:
method: app
app:
appId: 123456 # integer, not a string
# installationId: 78901234 # optional — auto-discovered when omitted
privateKeySecretRef:
name: github-app-private-key
key: private-key.pem
defaults:
git:
pullRequests:
branchPrefix: "remediation-"
The private key Secret must be in the same namespace as the ToolConfig.
GitLab
kubectl create secret generic gitlab-pat-token \
--from-literal=token=GITLAB_PAT_TOKEN \
--namespace=nirmata
kubectl apply -f - <<EOF
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: ToolConfig
metadata:
name: toolconfig-sample
namespace: nirmata
spec:
type: gitlab
credentials:
method: pat
pat:
tokenSecretRef:
name: gitlab-pat-token
namespace: nirmata
key: token
EOF
```text
### Private Certificate Authorities
The agent reaches your Git provider at `github.com` or `gitlab.com` — there is no ToolConfig setting for a GitHub Enterprise Server or self-managed GitLab API endpoint. Use `spec.tls` when that traffic passes through a TLS-intercepting proxy or egress gateway presenting a corporate certificate. Without the CA bundle, the agent fails with a certificate verification error.
```bash
kubectl create secret generic git-ca-bundle \
--from-file=ca.crt=/path/to/corporate-ca.pem \
--namespace nirmata
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: ToolConfig
metadata:
name: toolconfig-sample
namespace: nirmata
spec:
type: gitlab
credentials:
method: pat
pat:
tokenSecretRef:
name: gitlab-pat-token
namespace: nirmata
key: token
tls:
caBundleSecretRef:
name: git-ca-bundle
namespace: nirmata
key: ca.crt
spec.tls.insecureSkipVerify: true disables certificate verification entirely. Use it only to confirm that a failure is certificate-related — never in production.
Pull Request Labels
ToolConfig supports two label types:
customLabels— static labels always applied to every PR (e.g.,auto-remediation,security)systemLabels— dynamic labels computed at runtime from remediation context:
| System Label | Value |
|---|---|
branch | The Git branch being remediated |
clusterName | The cluster where violations were found |
appName | ArgoCD application name (Hub Mode only) |
namespace | Kubernetes namespace containing violations |
LLMConfig
LLMConfig specifies the AI provider used to generate remediation plans.
Nirmata AI (Default)
The Helm chart creates this automatically. Authentication uses the Service Account token injected at deployment time — no additional secrets needed.
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: LLMConfig
metadata:
name: nirmata-agent-llm
namespace: nirmata
spec:
type: nirmataAI
nirmataAI:
model: "" # empty string uses the current default model
```text
### AWS Bedrock
For EKS clusters with Pod Identity Agent:
```yaml
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: LLMConfig
metadata:
name: nirmata-agent-llm
namespace: nirmata
spec:
type: bedrock
bedrock:
model: MODEL_ARN_OR_INFERENCE_ARN
region: AWS_REGION
```text
Bedrock also accepts `credentialsSecretRef` (a Secret holding static AWS credentials), `roleArn`, and `externalId` if Pod Identity is not available.
<details>
<summary>Full AWS IAM setup</summary>
```bash
aws iam create-role \
--role-name remediator-agent-role \
--assume-role-policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Principal": { "Service": "pods.eks.amazonaws.com" },
"Action": ["sts:AssumeRole", "sts:TagSession"]
}]
}'
aws iam put-role-policy \
--role-name remediator-agent-role \
--policy-name BedrockInvokePolicy \
--policy-document '{
"Version": "2012-10-17",
"Statement": [{
"Effect": "Allow",
"Action": ["bedrock:InvokeModel", "bedrock:InvokeModelWithResponseStream"],
"Resource": "arn:aws:bedrock:<REGION>:<ACCOUNT_ID>:application-inference-profile/<PROFILE>"
}]
}'
aws eks create-pod-identity-association \
--cluster-name <CLUSTER_NAME> \
--namespace nirmata \
--service-account nirmata-agent \
--role-arn arn:aws:iam::<ACCOUNT_ID>:role/remediator-agent-role
```text
</details>
### Azure OpenAI
```bash
kubectl create secret generic azure-openai-credentials \
--from-literal=api-key=AZURE_API_KEY \
-n nirmata
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: LLMConfig
metadata:
name: nirmata-agent-llm
namespace: nirmata
spec:
type: azure-openai
azureOpenAI:
endpoint: https://YOUR_RESOURCE_NAME.openai.azure.com/
deploymentName: DEPLOYMENT_NAME
apiKeySecretRef:
name: azure-openai-credentials
key: api-key
namespace: nirmata
```yaml
### Anthropic API
Calls the Anthropic API directly with your own API key.
```bash
kubectl create secret generic anthropic-credentials \
--from-literal=api-key=ANTHROPIC_API_KEY \
-n nirmata
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: LLMConfig
metadata:
name: nirmata-agent-llm
namespace: nirmata
spec:
type: anthropic
anthropic:
model: claude-sonnet-5 # required — no default
apiKeySecretRef:
name: anthropic-credentials
namespace: nirmata
key: api-key
# baseURL: https://ai-gateway.internal
model is required and has no default, so the model in use never changes silently on an agent upgrade. Set baseURL — including the scheme — to route requests through an internal AI gateway or proxy instead of https://api.anthropic.com.
Remediator
The Remediator resource ties everything together: it defines the environment mode, which clusters or applications to target, the schedule, and what actions to take.
Environment Modes
| Mode | Use Case |
|---|---|
localCluster | Scan the cluster where the agent is installed |
argoHub | Use ArgoCD to manage violations across multiple clusters |
fluxHub | Use FluxCD to manage violations across multiple clusters |
Direct repository scanning (target.vcs) is not an environment type — it is configured alongside any of the three modes above. See VCS Target Mode.
ArgoCD Hub Mode (Multi-Cluster)
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: Remediator
metadata:
name: remediator-argo-hub
namespace: nirmata
spec:
environment:
type: argoHub
target:
argoHub:
appSelector:
allApps: true # or specify names / labelSelector
remediation:
triggers:
- schedule:
crontab: "0 */6 * * *"
llmConfigRef:
name: nirmata-agent-llm
namespace: nirmata
gitCredentials:
name: toolconfig-sample
namespace: nirmata
eventPolling:
enabled: true
intervalMinutes: 5
actions:
- type: CreatePR
toolRef:
name: toolconfig-sample
namespace: nirmata
Targeting specific clusters or apps:
target:
argoHub:
clusterNames:
- production-cluster
- staging-cluster
appSelector:
names:
- nginx-demo
- web-app
labelSelector:
matchLabels:
team: platform
environment: production
FluxCD Hub Mode (Multi-Cluster)
Targets workloads managed by Flux. The agent selects Flux Kustomization and/or HelmRelease objects, then follows each one’s sourceRef to its GitRepository to discover the repository URL and branch to open the PR against — so no repo-to-namespace mapping is needed.
apiVersion: serviceagents.nirmata.io/v1alpha1
kind: Remediator
metadata:
name: remediator-flux-hub
namespace: nirmata
spec:
environment:
type: fluxHub
target:
fluxHub:
fluxNamespace: flux-system # where Flux controllers run; reference only
kustomizationSelector:
names:
- my-app
- backend-api
remediation:
triggers:
- schedule:
crontab: "0 */6 * * *"
llmConfigRef:
name: nirmata-agent-llm
namespace: nirmata
gitCredentials:
name: toolconfig-sample
namespace: nirmata
actions:
- type: CreatePR
toolRef:
name: toolconfig-sample
namespace: nirmata
Set at least one of kustomizationSelector or helmReleaseSelector — the Helm chart refuses to install without one, and a Remediator applied directly with neither is admitted but selects no targets. Set both to remediate both object types. Each selector accepts:
| Field | Behavior |
|---|---|
names | Select specific objects by name |
namespaces | Restrict which namespaces are searched. In hub-driven setups each spoke usually has a dedicated namespace on the hub. Empty means all namespaces |
labelSelector | Select by labels, e.g. nirmata.io/remediation: "enabled" |
allKustomizations / allHelmReleases | Select everything found in the searched namespaces; other fields are ignored |
target:
fluxHub:
kustomizationSelector:
namespaces:
- production
- staging
labelSelector:
matchLabels:
nirmata.io/remediation: "enabled"
helmReleaseSelector:
allHelmReleases: true
Flux API versions are detected automatically, covering Flux 0.x through 2.7+ (v1/v1beta2/v1beta1 for Kustomization and GitRepository, v2/v2beta2/v2beta1 for HelmRelease).
Prerequisite
The agent’s ClusterRole must be able to read Flux resources. Verify before creating the Remediator:
kubectl auth can-i list kustomizations.kustomize.toolkit.fluxcd.io \
--as=system:serviceaccount:nirmata:nirmata-agent -A
kubectl auth can-i get gitrepositories.source.toolkit.fluxcd.io \
--as=system:serviceaccount:nirmata:nirmata-agent -A
If either returns no, grant get/list/watch on kustomizations, helmreleases, and gitrepositories to the agent’s ClusterRole. Without it, the agent finds no targets.
Local Cluster Mode
See the example in Getting Started.
VCS Target Mode (Direct Repository Scanning)
Scan Git repositories directly without requiring a running cluster:
spec:
environment:
type: localCluster
target:
vcs:
policies:
- name: pod-security-policy
repo: https://github.com/your-org/policies
path: kyverno/pod-security.yaml
ref: main
resources:
- name: web-app-deployment
repo: https://github.com/your-org/manifests
path: deployments/web-app.yaml
ref: main
policyRefs:
- pod-security-policy
remediation:
triggers:
- schedule:
crontab: "0 */6 * * *"
llmConfigRef:
name: nirmata-agent-llm
namespace: nirmata
actions:
- type: CreatePR
toolRef:
name: toolconfig-sample
namespace: nirmata
Remediation Modes
spec.remediation.remediationMode selects how fixes are generated.
| Mode | Behavior |
|---|---|
ai (default) | An LLM generates the remediation. llmConfigRef is required |
prescriptive | Fixes are computed deterministically with no LLM call anywhere in the path. llmConfigRef is not required and is ignored if set |
Prescriptive mode is currently scoped to resource-optimization violations: it right-sizes container CPU and memory from VerticalPodAutoscaler recommendations and from bounds defined in the policy itself. Existing Remediators are unaffected — the field defaults to ai.
Scoping reuses the standard Kyverno category annotation, so nothing new has to be labelled on your workloads:
spec:
remediation:
remediationMode: prescriptive
resourceOptimization:
policyCategories:
- "Resource Management" # default
triggers:
- schedule:
crontab: "0 */6 * * *"
gitCredentials:
name: toolconfig-sample
namespace: nirmata
actions:
- type: CreatePR
toolRef:
name: toolconfig-sample
namespace: nirmata
A violation is remediated only when its policy’s policies.kyverno.io/category annotation appears in policyCategories. Which workloads are in scope is decided by the policy’s own match block — there is nothing to configure here for that.
Prerequisites
A Kyverno policy annotated with a matching category:
metadata: annotations: policies.kyverno.io/category: Resource ManagementFor usage-based right-sizing, a
VerticalPodAutoscalerin recommendation-only mode for each workload:apiVersion: autoscaling.k8s.io/v1 kind: VerticalPodAutoscaler metadata: name: nginx namespace: my-app-ns spec: targetRef: apiVersion: apps/v1 kind: Deployment name: nginx updatePolicy: updateMode: "Off" # recommendation only — never restarts podsBoundary policies (for example “the CPU limit must not exceed 1”) are satisfied from the policy bound alone and need no VPA.
Current limits
- Workloads with neither a VPA recommendation nor a computable policy bound are skipped and recorded — never guessed.
- Helm charts and kustomize-overlay value indirection are skipped in this phase.
- The Helm chart always renders
llmConfigRefand has noremediationModevalue, so apply a prescriptive Remediator as a manifest rather than through the chart.
Actions
| Action | Behavior |
|---|---|
CreatePR | Opens a pull/merge request with the fix |
DryRun | Logs the planned file changes and creates nothing. Useful for a first evaluation. Raise logging.verbosity to 2 to log the proposed file contents |
toolRef is required by the schema for both action types.
CI Checks
Every remediation PR the agent opens on GitHub also gets two check runs, so a reviewer can see whether the proposed fix is valid before merging. Nothing needs to be configured — they are created automatically alongside the PR.
| Check | Validates |
|---|---|
| Kubernetes Resource Validation | The remediated manifest against the Kubernetes OpenAPI schema — that the change is structurally valid |
| Policy Compliance | The remediated manifest against the Kyverno policies, locally — that the change actually resolves the violation |
Each check reports one of three conclusions:
| Conclusion | Meaning |
|---|---|
success | Validation passed |
failure | The proposed change is invalid, or still violates a policy. Review before merging |
neutral | Validation was skipped — for example the policy could not be evaluated locally |
Both checks are advisory: they annotate the PR but never block it, and a failure to create them is logged without affecting the PR. Policy Compliance runs the Kyverno validator directly, so it involves no LLM call and works the same in prescriptive mode.
Check runs are a GitHub API feature. GitLab merge requests get no equivalent, and checks are also skipped when no commit SHA is available.
Confidence-Based Actions
Control when automated PRs are created based on the AI’s confidence in its fix:
remediation:
actions:
- type: CreatePR
confidence:
- high # only create PRs when AI is highly confident
toolRef:
name: toolconfig-sample
namespace: nirmata
| Confidence | Meaning |
|---|---|
high | The configured AI provider is highly confident the fix is correct and safe |
low | A potential fix was found but human review is recommended |
Confidence is set by the AI provider, so gating applies to ai mode. Prescriptive plans are always high.
Filtering by Severity
remediation:
filters:
policySelector:
matchSeverity:
- high
- critical
```yaml
---
## Split PR
The Split PR feature lets you split a pull request containing fixes for multiple policies into separate PRs — useful when different policies need different reviewers or approval workflows.
### How to Split
Add a comment to the PR mentioning `@nirmatabot`:
```text
@nirmatabot split-pr require-run-as-non-root disallow-privileged-containers
- One or more policy names (space-separated) after
split-pr - Use the exact policy names as they appear in the PR
What Happens
- Original PR — updated to contain only the remaining policies; a comment is added with a link to the new PR
- New PR — created on a new branch (prefixed
splitpr-) with only the specified policies; links back to the original PR - Both PRs are tracked independently by the agent
Request a Policy Exception
When a violation should not be fixed, request an exception from the PR instead. This raises a Policy Exception Request in Nirmata Control Hub for approval.
@nirmatabot request-exception <policy-name> [<policy-name> ...] [duration] [reason]
| Argument | Required | Description |
|---|---|---|
policy-name | Yes | One or more Kyverno policy names to except. Use the names as they appear in the PR |
duration | No | A number of days (7d, 30d) or permanent. Defaults to permanent when omitted |
reason | No | Free-text justification — all text after the duration token. Shown in the Control Hub approval UI |
@nirmatabot request-exception disallow-capabilities-strict
@nirmatabot request-exception disallow-capabilities-strict 30d
@nirmatabot request-exception disallow-host-ports restrict-apparmor-profiles 7d Approved by security team
A reason is only parsed after a duration token, so include a duration whenever you want to record a justification.
What Happens
- The named policies are removed from the remediation PR, and the agent comments with the request name and the policies still covered by the PR.
- A Policy Exception Request is created in Control Hub and approvers are notified by email. Track it under Policies → Exception Requests.
- On approval, Control Hub opens a second PR containing a Kyverno
PolicyExceptionmanifest scoped to that exact resource. Merge that PR to activate the exception.
If a request already exists for a policy, the agent reports it and skips that policy rather than filing a duplicate.
Requires Nirmata Control Hub credentials
This command calls the Control Hub API, so it is unavailable when the agent is installed withnirmata.auth=none. See Nirmata Platform Authentication.