?tab=policies); authoring happens on Security → Topology in author mode (?mode=author), where the graph you are reasoning about is the same graph the policy will act on. Both call the endpoints below.
The list used to be reachable both from Networking and as a mode inside Flow Visibility — two answers to “where do I edit a policy”, and two places to keep correct. It was consolidated onto Security in September 2026. Old links still work:
/networking?tab=policies and /security/policies forward here and carry their query string with them, so a bookmark that had scoped itself to a cluster stays scoped.The loop
1
Draft
Build the policy from the graph — pick a workload, pick the edges you want to keep, and the editor renders CNP or CCNP YAML. The applications endpoint backs the picker so you can select a logical app rather than enumerating pods.
2
Dry-run
POST .../security/policies/dry-run scores the draft against the window’s real edges. Nothing is applied.3
Apply
POST .../security/policies writes it. Enforcement is immediate — the moment the apiserver accepts the object, Cilium acts on it.4
Verify
Coverage says which workloads the policy now selects; flow matches proves it is matching real traffic; the topology graph repaints those edges as
policy-protected.5
Watch for drift
Drift compares what policies declare against what the network actually did, and is the thing to check after any change to labels or workloads.
Author mode
Authoring was a separate screen — Policy Studio — until it merged into the Topology tab in August 2026. It is the same graph in both modes; author mode adds the editor, and constrains the scope so that what the editor renders is answerable./s/{supervisor}/policy-studio still works and forwards here with ?tab=topology&mode=author, carrying the rest of its query string — ?ns=, ?since=, ?cluster= — because those are the scope a shared link existed to set.
Three constraints are worth knowing before a link surprises you:
- A namespace must be chosen explicitly. Author mode never starts on the “all namespaces” wildcard, and a link that arrives carrying
ns=*is treated as having chosen nothing. A policy drafted against every namespace at once is not a policy anyone can reason about, so the mode re-asks. ?ns=locks the namespace on arrival when it can be inferred — following the author link from a policy list that holds a single namespace lands you already scoped to it.- It authors against the supervisor, not a guest. A link that carries
mode=authorwhile pointed at a guest cluster falls back to explore mode, because the authoring routes only exist at the supervisor address. From a guest-scoped policy list the author link therefore points at the supervisor the guest hangs off, rather than pretending the editor can be aimed at the guest.
Dry-run
The request carries the draft YAML, the workload you are reasoning about, and the window to score against:wouldAllow (matched by the selector and permitted by a rule), wouldDeny (matched but no rule fits), wouldBlock (removed by an explicit deny rule — access another policy grants, kept separate because taking access away is categorically different from an allow-list not covering an edge), and unaffected (the selector does not match) — each edge carrying direction, peer, port, protocol, bytes and its current observedAction. A summary reports edgesAnalysed and whether the policy acts on ingress, egress or both.
Three response fields matter more than the buckets:
parseErroris set while the YAML is incomplete. The editor polls as you type, so a half-written policy returns a hint rather than an error, and the verdict lists stay empty.modesays which analysis ran, and reading the buckets without it is the most dangerous mistake available here.verdictsmeans anaffectedWorkloadwas given and the edges were scored against its observed traffic;lintmeans none was, so what came back is a whole-namespace blast-radius advisory andedgesAnalysedis 0 by design. An empty verdict list fromlintreads as “this policy affects nothing” — the opposite of what it means.warningssurface blast radius that per-edge verdicts cannot. Each has aseverityofwarnordanger, a title, detail, and the notable workloads involved.
security:Read, so anyone who can look at flows can evaluate a draft without being able to apply it.
Apply and delete
POST .../security/policies takes namespace, name and yaml. Semantics worth being explicit about:
- Replace, not merge. The YAML in the body is the new state of the object. There is no merge with what is live, so a field you removed from the draft is removed from the cluster.
- Server-side apply with a stable field manager, so repeated applies update in place and Cilium’s own
managedFieldskeep the change attributable alongside the platform’s audit log. - Two kinds only.
CiliumNetworkPolicyandCiliumClusterwideNetworkPolicyare accepted; anything else is rejected rather than silently widening what this endpoint can write. - CCNP is expressed by an empty namespace (the UI sends the placeholder
cluster).
DELETE .../security/policies?name=<name>&namespace=<namespace> removes one, with the same empty-namespace convention for cluster-wide policies. Both writes require security:Write.
Coverage
GET .../security/policies/coverage returns the map from workload to the policies covering it — name, namespace and kind, so a namespaced CNP is distinguishable from a cluster-wide CCNP at a glance. This is the same index the topology graph uses to paint an edge policy-protected instead of unprotected, and the same one that fills coveredBy on a node.
Drift
GET .../security/policies/drift compares what policies declare against what the datapath did, over the analysis window:
policiesChecked and edgesAnalysed are the reason to trust an empty result: zero drifts with 13 policies and 361 edges analysed means the policies agree with reality, whereas zero drifts with edgesAnalysed: 0 means nothing was compared and you should be looking at freshness instead.
The common driftType is policy-blocks-cilium-allows — the policy’s rules would deny an edge that was observed as allow or unprotected. Nine times out of ten that is the coverage gap below rather than a broken datapath: the policy is correct, but the workload is not being recognized as one it selects.
Per-policy detail
Four endpoints answer “what does this specific policy do”, all keyed byname plus namespace (empty or cluster for a CCNP):
flow-matches is the proof-of-life check: a policy with matched: 0 over a busy window either selects nothing or guards a path nobody uses. history reads the audit log, so it requires the platform database to be configured and only covers changes made through Celum — a kubectl apply by hand will not appear.
Identities — what selectors actually match
A policy selector matches Cilium labels, not Kubernetes objects, and the two are easy to confuse.GET .../security/identities/by-workload?namespace=…&workload=… returns the identity behind a workload with the exact label set:
security/identities/{id} does the same lookup from a numeric identity, which is what topology edges carry.
Coverage blind spots
Coverage, drift and the topology paint all rest on the same workload-identity index, and it has three documented limits. All three make policies look less effective than they are:
In every case
security/policy/flow-matches is the authority: it counts flows the policy matched, independent of the index.
Permissions
Read access covers every namespace on the supervisor — see the permissions note — while writing needs
security:Write. There is no per-namespace policy-write scope; if you need one, model it as separate supervisors. See Permissions model and Projects for how tenancy is expressed elsewhere.
Celum AI can drive the safe half of this loop directly:
dry_run_policy evaluates a draft without applying it, list_security_policies and get_policy_detail inspect what exists, and get_policy_drift reports where declarations and observed traffic disagree. Applying and deleting stay manual.