Documentation

API reference

Every field below exists in the CRD the API server serves, because this document is generated from the Go types rather than written alongside them.

The version described here is policy.pahlevan.io/v1beta1, which is what the API server stores. v1alpha1 is still served so existing objects and clients keep working, and the API server converts between the two. The v1alpha1 fields with no v1beta1 counterpart are listed, with the reason each one is gone, in IntentionallyDropped in pkg/apis/policy/v1alpha1/conversion.go.

That matters more than it sounds. The previous version of this file was hand-written and described an API that had never existed - learning: instead of learningConfig:, enforcement: instead of enforcementConfig:, plus whole subsystems the CRD has never had. A Kubernetes API server does not reject an unknown field in a custom resource; it prunes it. So anyone who copied from it got a policy that applied cleanly and did a fraction of what they asked for, with no error anywhere.

Fields marked inert are accepted by the API and acted on by nothing. They are listed rather than hidden, because finding one in a cluster and not knowing is worse than being told. pahlevan policy explain -f names every one in a given policy.

PahlevanPolicy

is the Schema for the pahlevanpolicies API

Field Type Required Description
metadata metav1.ObjectMeta
spec PahlevanPolicySpec
status PahlevanPolicyStatus

PahlevanPolicySpec

defines the desired state of PahlevanPolicy

Field Type Required Description
selector WorkloadSelector yes Selector specifies the target workloads for this policy
learningConfig LearningConfig LearningConfig controls the learning phase behavior
enforcementConfig EnforcementConfig EnforcementConfig controls enforcement behavior
syscallPolicy *SyscallPolicy SyscallPolicy defines syscall-specific policies
networkPolicy *NetworkPolicy NetworkPolicy defines network-specific policies
filePolicy *FilePolicy FilePolicy defines file access policies
selfHealing SelfHealingConfig SelfHealing enables automatic policy rollback on failures
observabilityConfig ObservabilityConfig Inert - the agent's flags configure telemetry, per node rather than per policy. ObservabilityConfig controls observability exports

LearningConfig

controls the learning phase

Field Type Required Description
duration *metav1.Duration Duration specifies how long to run in learning mode
windowSize *metav1.Duration Inert - the controller observes continuously, not in sampling windows. WindowSize specifies the minimum learning window size
minSamples *int32 MinSamples specifies minimum number of samples before transitioning. Bounded below by 1: zero means "transition with no evidence at all", which is a policy that enforces an empty baseline and kills the workload on its first syscall. Negative was accepted and compared against a count that can never be less than it, so it silently meant the same thing.
autoTransition bool AutoTransition enables automatic transition to enforcement
lifecycleAware bool Inert - stored and displayed; nothing acts on it. LifecycleAware enables lifecycle-based learning transitions
requireReview bool RequireReview holds a container in learning once its window and grace period have elapsed, until ReviewedAt is set. A workload already compromised when learning starts has its malicious behaviour baselined; this is the point at which an operator can look at the learned baseline before it becomes the thing that is enforced. Checked once, at the moment the container would otherwise transition: a baseline that changes after review is not re-reviewed.
reviewedAt *metav1.Time ReviewedAt is set by an operator after reviewing the learned baseline, clearing the RequireReview hold. Nil means not yet reviewed. Ignored when RequireReview is false.
expectedBehavior *ExpectedBehavior ExpectedBehavior declares operations the operator knows the workload performs but which may not happen during the learning window. Learning is a window of wall-clock time, so anything the workload does once a day is simply absent from the baseline: a nightly batch, a weekly certificate renewal, a log rotation, a backup that opens a path nothing else opens. Under Blocking the kernel then refuses it, and from the kernel's side that refusal is correct - the only evidence against the operation is that the workload has never done it before, which is exactly what an attacker produces too. Without this field the operator's only options are to guess a longer duration, or to let self-healing roll enforcement back after the job has already been denied at 03:00. Declarations are additive. Every entry is merged into the allow-set alongside what was learned, none can remove a learned entry, and an entry that cannot be represented exactly is refused with a warning naming the field rather than widened into something broader.

EnforcementConfig

controls enforcement behavior

Field Type Required Description
mode EnforcementMode Mode specifies the enforcement mode. The enum is load-bearing rather than decorative. Without it the API server accepts any string and the controller maps whatever it does not recognize onto Monitoring, so a typo produces a policy that looks applied and enforces nothing. The trap that found this: mode: Off unquoted is a YAML 1.1 boolean, so it arrives as false and silently became Monitoring - the opposite of switching a policy off. Quote it, or the API server now says so. One of: Off, Monitoring, Blocking.
gracePeriod *metav1.Duration GracePeriod specifies grace period before strict enforcement
alertOnly bool AlertOnly enables alert-only mode for testing. It downgrades Blocking to Monitoring and is kept as its own field because it is a temporary override an operator flips during an incident and flips back, without losing the mode the policy is meant to run in.
blockUnknown *bool BlockUnknown blocks behavior outside the learned baseline. Nil means "the default for the mode", which is true under Blocking: default-deny of unlearned behavior is the only enforcement the data plane performs, so a Blocking policy that did not block it would enforce nothing. Explicitly false downgrades the policy to Monitoring. A bare bool could not express the difference between unset and false.
exceptions []EnforcementException Exceptions defines enforcement exceptions

EnforcementException

defines enforcement exceptions. v1alpha1 had both temporary: true and expiresAt, and enforcement applied an expiry only when both were set. So expiresAt on its own was accepted, displayed, and never acted on: an operator who wrote a deadline got a permanent hole in the policy and no warning, which is the worst possible outcome for a field whose entire purpose is to close itself. The expiry is now the only statement of the fact, and an exception is temporary exactly when it has one.

Field Type Required Description
type ExceptionType yes Type specifies exception type One of: Syscall, Network, File.
patterns []string yes Patterns specifies patterns to match
reason string Reason provides human-readable reason
expiresAt *metav1.Time ExpiresAt is when this exception stops being applied. Unset means the exception is permanent.

FilePolicy

defines file access policies

Field Type Required Description
allowedPaths []string AllowedPaths explicitly allows specific paths. Paths must be fully resolved and exact. Enforcement keys on the path the kernel resolves, which follows symlinks, so "/etc/os-release" grants nothing where it links to /usr/lib/os-release. Wildcards are not supported and are matched literally.
deniedPaths []string DeniedPaths explicitly denies specific paths, removing them from the learned baseline. The same resolution rules as AllowedPaths apply.
defaultAction PolicyAction Inert - redundant: default-deny is what enforcement is. DefaultAction specifies default action for unknown paths One of: Allow, Deny, Alert, Audit.
readOnlyPaths []string ReadOnlyPaths specifies read-only paths
writeAllowedPaths []string WriteAllowedPaths specifies write-allowed paths
executableFilter *ExecutableFilter ExecutableFilter controls executable access

ExecutableFilter

defines executable access controls

Field Type Required Description
allowedExecutables []string AllowedExecutables specifies allowed executables
deniedExecutables []string DeniedExecutables specifies denied executables
requireSignature bool Inert - nothing verifies executable signatures. RequireSignature requires signed executables

SyscallPolicy

defines syscall enforcement policies

Field Type Required Description
allowedSyscalls []string AllowedSyscalls explicitly allows specific syscalls
deniedSyscalls []string DeniedSyscalls explicitly denies specific syscalls
defaultAction PolicyAction Inert - redundant: default-deny is what enforcement is. DefaultAction specifies default action for unknown syscalls One of: Allow, Deny, Alert, Audit.
capabilityFilter []string CapabilityFilter filters based on Linux capabilities
processFilter *ProcessFilter ProcessFilter filters based on process attributes

ProcessFilter

defines process-based filtering

Field Type Required Description
commands []string Commands specifies allowed command patterns
users []string Users specifies allowed users
groups []string Groups specifies allowed groups
parentProcesses []string ParentProcesses specifies allowed parent processes

NetworkPolicy

defines network enforcement policies

Field Type Required Description
egressRules []NetworkRule EgressRules defines allowed egress traffic
ingressRules []NetworkRule IngressRules defines allowed ingress traffic
defaultAction PolicyAction Inert - redundant: default-deny is what enforcement is. DefaultAction specifies default action for unknown connections One of: Allow, Deny, Alert, Audit.
allowLoopback bool AllowLoopback allows loopback traffic
allowDNS bool AllowDNS allows DNS traffic

NetworkRule

defines a network access rule

Field Type Required Description
protocols []string Protocols specifies allowed protocols
ports []NetworkPort Ports specifies allowed ports
peers []NetworkPeer Peers specifies allowed peers
action PolicyAction Action specifies the action to take One of: Allow, Deny, Alert, Audit.

NetworkPeer

defines network peer specifications

Field Type Required Description
ipBlock *IPBlock IPBlock specifies IP CIDR blocks
namespaceSelector *LabelSelector NamespaceSelector selects namespaces by their labels.
podSelector *LabelSelector PodSelector selects pods by their labels.

NetworkPort

defines port specifications. The bounds are not decoration. A port is a uint16 on the wire and this field is an int32, so v1alpha1 accepted 0, -1 and 70000 and the translation truncated them into whatever the low sixteen bits happened to be. Port 70000 became port 4464, and the rule looked applied.

Field Type Required Description
port *int32 Port specifies the port number
startPort *int32 StartPort specifies start of port range
endPort *int32 EndPort specifies end of port range
protocol string Protocol specifies the protocol

IPBlock

defines IP CIDR block

Field Type Required Description
cidr string yes CIDR specifies the IP range
except []string Except specifies exceptions within the CIDR

SelfHealingConfig

controls self-healing behavior

Field Type Required Description
enabled bool Enabled enables self-healing
rollbackThreshold int32 RollbackThreshold specifies failure threshold for rollback
rollbackWindow *metav1.Duration RollbackWindow specifies time window for failure counting
recoveryStrategy RecoveryStrategy RecoveryStrategy specifies recovery strategy One of: Rollback, Relax, Maintenance.

ObservabilityConfig

controls observability exports

Field Type Required Description
metrics MetricsConfig Metrics controls metrics export
tracing TracingConfig Tracing controls distributed tracing
logging LoggingConfig Logging controls structured logging
visualization VisualizationConfig Visualization controls attack surface visualization

PahlevanPolicyStatus

defines the observed state of PahlevanPolicy

Field Type Required Description
phase PolicyPhase Phase indicates the current phase of the policy One of: Initializing, Learning, Transition, Enforcing, Failed, RollingBack.
conditions []PolicyCondition Conditions represents the latest available observations. Declared a map list keyed by type, which is what the controller has always meant: updateCondition searches for the entry with a matching type and replaces it. The schema did not say so, so server-side apply merged the array by position instead, and two writers updating different conditions overwrote each other's entries rather than merging them.
learningStatus *LearningStatus LearningStatus provides learning phase status
enforcementStatus *EnforcementStatus EnforcementStatus provides enforcement status
attackSurface *AttackSurfaceStatus AttackSurface provides current attack surface analysis
targetWorkloads []WorkloadReference TargetWorkloads lists target workloads
lastUpdated *metav1.Time LastUpdated indicates when status was last updated

LearningStatus

provides learning phase status

Field Type Required Description
startTime *metav1.Time StartTime indicates when learning started
endTime *metav1.Time EndTime indicates when learning ended
samplesCollected int64 SamplesCollected indicates samples collected
syscallsLearned int32 SyscallsLearned indicates unique syscalls learned
networkFlowsLearned int32 NetworkFlowsLearned indicates network flows learned
filePathsLearned int32 FilePathsLearned indicates file paths learned
progress *int32 Progress indicates learning progress percentage. Bounded, because it is printed verbatim in the Learning column of kubectl get pahlevanpolicy. An unbounded int32 there reads as "413" next to a header that says a percentage, and nothing rejects it.

EnforcementStatus

provides enforcement status. v1alpha1 carried a blockedSyscalls counter that was always zero by construction: syscalls are confined by the generated seccomp profile, whose denials the kernel does not report back to this agent, and the BPF syscall program is observation only. A counter that can only read zero answers "are we blocking anything?" with "no" forever, so it is gone rather than documented again.

Field Type Required Description
startTime *metav1.Time StartTime indicates when enforcement started
blockedNetworkConnections int64 BlockedNetworkConnections indicates connect() calls denied in-kernel.
blockedFileAccess int64 BlockedFileAccess indicates file opens denied in-kernel.
blockedExecs int64 BlockedExecs indicates execve calls denied in-kernel.
blockedCapabilities int64 BlockedCapabilities indicates capability checks denied in-kernel.
blockedTotal int64 BlockedTotal is the sum of every in-kernel denial across the containers this policy governs. It exists so the printed column reports the whole picture rather than one signal.
enforcingContainers int32 EnforcingContainers and TotalContainers describe how many of the containers this policy selects have actually reached enforcement.
totalContainers int32
alertsGenerated int64 AlertsGenerated indicates alerts generated count
rollbackCount int32 RollbackCount indicates number of rollbacks performed

AttackSurfaceStatus

provides attack surface analysis

Field Type Required Description
exposedSyscalls []string ExposedSyscalls lists exposed syscalls
exposedPorts []int32 ExposedPorts lists exposed network ports
writableFiles []string WritableFiles lists writable file paths
capabilities []string Capabilities lists effective capabilities
riskScore *int32 RiskScore provides overall risk score, 0 to 100. Bounded for the same reason as Progress: it is the Risk printer column, and the analyzer's arithmetic has no upper clamp of its own.
lastAnalysis *metav1.Time LastAnalysis indicates when analysis was last performed

Other types

Reachable from the types above; listed so a new one cannot go undocumented by omission.

  • ExpectedBehavior
  • ExpectedDestination
  • ExpectedFile
  • LabelSelector
  • LabelSelectorRequirement
  • LogOutput
  • LoggingConfig
  • MetricsConfig
  • MetricsExporter
  • NamespaceSelector
  • PahlevanPolicyList
  • PolicyCondition
  • TracingConfig
  • TracingExporter
  • VisualizationConfig
  • VisualizationExporter
  • WorkloadReference
  • WorkloadSelector

Metrics, events and the CLI

The sections below are not generated: they describe surfaces outside the CRD.

Metrics API

Pahlevan exposes Prometheus metrics on port 8080.

Metrics Endpoint

GET /metrics

Available Metrics

Policy Metrics

Metric Type Description
pahlevan_policies_total Counter Total number of policies
pahlevan_policies_by_phase Gauge Policies by phase
pahlevan_policy_violations_total Counter Total policy violations
pahlevan_enforcement_actions_total Counter Total enforcement actions

Learning Metrics

Metric Type Description
pahlevan_learning_progress_ratio Gauge Learning progress (0.0-1.0)
pahlevan_learning_samples_total Counter Total learning samples
pahlevan_learning_duration_seconds Histogram Learning duration

eBPF Metrics

Metric Type Description
pahlevan_ebpf_programs_loaded Gauge Number of eBPF programs loaded
pahlevan_ebpf_events_total Counter Events decoded from the ring buffers, by kind
pahlevan_ebpf_denials_total Counter Operations denied in-kernel with EPERM, by kind
pahlevan_ebpf_decode_errors_total Counter Ring-buffer records that could not be decoded, by kind
pahlevan_ebpf_handler_errors_total Counter Event handlers that returned an error, by kind
pahlevan_ebpf_breakouts_total Counter Execs whose working directory was outside the process's own mount namespace
pahlevan_ebpf_program_load_errors_total Counter eBPF program load errors

pahlevan_ebpf_handler_errors_total is the one to alert on if you export events. Enforcement happens in the kernel and is unaffected by a handler failure, so the event and denial counters keep climbing normally while an export file, webhook or gRPC subscriber quietly receives nothing.

System Metrics

Metric Type Description
pahlevan_containers_monitored Gauge Containers being monitored
pahlevan_operator_info Info Operator version and build info

Metric Labels

Common labels across metrics:

Label Description
policy Policy name
namespace Policy namespace
container Container ID
violation_type Type of violation
action Enforcement action taken

Event stream

Security events leave the agent in four ways, all carrying the same versioned envelope (pahlevan.io/v1alpha1):

Route Flag Notes
JSON lines to a file --export-file What pahlevan events reads by default. Size-based rotation.
HTTP webhook --export-webhook Batched POSTs, retried on 5xx/408/429.
OTLP log records --otlp-endpoint Reaches Loki through the same collector as the metrics and traces.
gRPC stream --grpc-bind-address pahlevan.v1alpha1.EventService; server-side filtering. Requires TLS, mTLS or a bearer token: a plaintext, unauthenticated listener refuses to start unless --grpc-insecure is passed deliberately.

Those four carry the envelope, which is the right shape for a collector and the wrong shape for a person. For getting a denial in front of somebody who can act on it there are three more, which send formatted messages rather than JSON:

Route Flag Notes
Slack --notify-slack-webhook One Block Kit message per batch, with a fallback text so a phone notification says something useful.
PagerDuty --notify-pagerduty-key Events API v2. One incident per distinct finding, keyed so the same problem re-triggers rather than opening another. --notify-pagerduty-severity sets critical/error/warning/info.
Anything else --notify-template-url + --notify-template A Go text/template rendered into the POST body: Teams, Discord, Opsgenie, an internal ticket API. Helpers: summary, workload, subject, json, truncate, upper, lower, join.

All three send denials only by default, because an observation stream is not an alert stream and a channel receiving one message per file open gets muted within the hour. --notify-all-events opts in.

All three deduplicate by finding over --notify-dedupe-window (five minutes by default, negative to disable). A finding is the workload, the kind of operation and the subject - deliberately not the pid or the timestamp, which are exactly what make two occurrences of one problem look different. It keys on the owning workload rather than the pod, so a Deployment rolling out does not produce one message per replica for a single misconfiguration.

A template that fails to parse is a startup error naming the mistake, not an error logged once per batch while notifications silently never arrive:

--notify-template-url=https://example.webhook.office.com/... --notify-template='{"text":{{ .Summary | json }}}'

The envelope is defined in pkg/export/event.go and mirrored on the wire by api/v1alpha1/events.proto; the two are asserted to agree in pkg/grpcapi/convert_test.go.

Seeing what a policy does before applying it

pahlevan policy explain -f policy.yaml [--strict]

Translates the policy offline, prints what will be written into the kernel allow-sets, and names every part that will not be enforced - an ingress rule, a CIDR too wide to enumerate, a glob, an inert field. --strict exits non-zero, so a policy that quietly does less than it says can fail a CI gate.

It also rejects a field the CRD does not have, which the API server would otherwise prune without an error.