readonly = true in the config disables every mutating action - delete, edit,
scale, shell, mutating plugins, uploads. The --readonly and --write flags set
the mode for the whole session and win over the config value, including over
per-cluster and per-context overrides, on every :ctx switch.
With no flag, switching into a context whose config sets readonly = true
enables read-only mode (shown as [read-only] in the header). Switching away
restores write mode. A common pattern is a per-context override that makes prod
read-only and repaints it in a light skin - see
per-context overrides.
[[guardrails]] turn rules like "never delete in prod", "always confirm drains",
and "no more than 5 at a time" into enforced rules instead of things you have to
remember.
Each rule matches on contexts, namespaces, resources, and actions globs
(all optional - an omitted glob matches everything). Rules can set deny
(block the action), confirmation (require confirmation), and max_bulk
(a row limit for one action). The gated actions are the destructive verbs sofka
performs directly: delete, force-delete, drain, restart, shell (exec),
debug, node-debug, transfer (a file upload into a pod), pvc-explore
(creating a helper pod to mount an unmounted PVC, and the :pvc-clean sweep
that deletes them - both matched against persistentvolumeclaims), and
pvc-upload (a file upload into a volume, matched the same way). The PVC
shell and the PVC upload also pass the shell and transfer rules for the
pod they reach the volume through, exactly like s and t on that pod's row -
a rule that already blocks shells or uploads in prod is not defeated by
reaching the same pod through a claim it mounts.
Measuring a copy's progress runs du in the pod, and the two directions cost
very different things. A download of a single file from the browser adds no
exec at all - its size is already on screen, from the listing, though a t
download of a typed path has no listing and so runs the probe - and a download of a directory
adds one du, which carries its own timeout inside the container. An
upload adds more: one long-lived exec that measures the destination once a
second for as long as the copy runs, because nothing else can see a volume
being written to from outside. That exec ends when sofka closes it, but a
sofka that is killed cannot close anything, and the loop then runs on in the
pod until its own bound expires - see PVC explore.
All of it is gated no further than the copy it belongs to, and recorded as
that one action in :journal, on the reasoning that a du reaches nothing a
permitted copy could not already reach: kubectl cp has always run tar
there.
sofka combines the restrictions from all matching rules:
- Any matching rule with
deny = trueblocks the action. - The smallest matching
max_bulklimit applies. If the number of targets exceeds this limit, sofka blocks the action. - The strongest matching
confirmationlevel applies:type-context-nameis stronger thantype-resource-name, which is stronger than a plain y/N confirmation. A rule cannot reduce the action's default confirmation level. For a bulk action,type-resource-namerequires the target count.
Denial takes priority over the bulk limit and confirmation. Rule order does not
change these restrictions. For a denial, sofka shows the reason from the last
matching rule with deny = true, if that rule has a non-empty reason. Bulk-limit
warnings and confirmation prompts do not show a rule's reason.
[[guardrails]]
contexts = ["*prod*"]
actions = ["delete", "force-delete", "drain"]
deny = true
reason = "Destructive actions on prod go through GitOps, not the TUI."
[[guardrails]]
contexts = ["*prod*"]
actions = ["shell"]
# "type-resource-name" | "type-context-name"; any other value = a plain y/N
confirmation = "type-context-name"
reason = "Confirm the exact pod before shelling into prod."
[[guardrails]]
namespaces = ["kube-system"]
actions = ["delete"]
max_bulk = 1 # no bulk deletes in kube-systemBefore you edit, delete, scale, or otherwise change an object that Flux, ArgoCD, (or another controller) owns, sofka warns you that the next reconcile will revert the change or recreate the object. The point is to send you to the source instead of fighting the controller.
:can-i runs a SelfSubjectRulesReview and shows what you can do in the current
namespace. :can-i <verb> <resource> [ns] checks a single action before you try
it. Same answer as kubectl auth can-i, inside the TUI.
The empty command-palette browse list hides kinds you cannot list. An explicit
search includes every discovered kind because delegated authorizers can return
incomplete rule reviews. The API still enforces access when you open the kind.
:journal (or :audit) is a session-local in-memory log of every mutating
action you took - the action, the target, the context, the time - newest first.
Entries record actions started, not confirmed results. They contain identifiers
only, never secret input or decoded values. Optional journal file
settings save entries to disk. Application
logging at info or above also records these actions.
Guardrails match plugin actions with plugin:<palette>.
If a plugin has no palette command, use plugin:<name>.
The pattern plugin:* matches all plugins.
Read-only mode blocks mutating plugins and plugins with network_load = true.
Network-load plugins require confirmation, even when they do not change Kubernetes resources.
See Plugin safety controls.
A drain first cordons the node, then checks its pods. It stops for that node
if an eligible pod has no controller, uses an emptyDir volume, or has no UID.
The error identifies the pod and the reason. No pods on that node are evicted
when this check fails. The node remains cordoned. Other selected nodes can
still be processed.
Drain uses the eviction API with the listed pod's UID. It does not use direct
pod deletion if eviction fails. This preserves PodDisruptionBudget checks and
prevents eviction of a replacement pod with the same name. A pod-specific
not-found response means the original pod is already gone. Other API errors
are reported, including an unavailable eviction endpoint. Drain has no override
for unmanaged pods or emptyDir data loss.