v26.2.12
v26.2.12
Jsonnet worker is fully isolated from the filesystem
The hidden jsonnet subcommand that Kratos re-execs to evaluate Jsonnet mappers (OIDC claim mappers, courier templates,
identity-schema transforms) now applies an empty Landlock layer at startup.
The worker only ever needs the inherited stdin/stdout/stderr — every path-based filesystem access from inside the worker is now
denied by the kernel, even if a Jsonnet snippet were to bypass the in-process import barrier. Already-open file descriptors keep
working, so the parent ↔ worker IPC is unaffected. This applies to Kratos OSS, Cloud, and OEL on Linux 5.13 and later, and is a
no-op on other platforms.
This is not configurable, for simplicity and security: there is no legitimate use case to allow the Jsonnet VM to read from disk.
Landlock filesystem sandbox for kratos serve (https://docs.kernel.org/userspace-api/landlock.html)
Filesystem sandbox for kratos serve (OEL)
Kratos OEL activates the Landlock sandbox for the main kratos serve
process after initialization. The process is then restricted to the files it needs at runtime:
/dev/null(subprocess plumbing)/etc/resolv.confand/etc/hosts(Go's pure-Go DNS resolver)- the running Kratos binary itself, with read+execute, so
jsonnetsecurecan re-exec it as a sandboxed worker - TLS certificates and keys for the public and admin listeners
- the courier template directory (courier templates are loaded at runtime)
- SMTP client certificate and key files (
courier.smtp.client_cert_path,courier.smtp.client_key_path, and the equivalents undercourier.channels[].smtp_config) - every file referenced from the config via a
file://URI. Kratos walks the loaded config once at startup and allows any value that begins withfile://— identity schemas, OIDC mapper templates,web_hookbody templates, courier HTTP body templates, session tokenizer mappers and JWKS files, and anything added to the config schema later. Operators do not need to duplicate these paths undersecurity.landlock.allowed_paths. - the directory containing the SQLite database — covers the database file itself and any
-journal/-wal/-shm/ transient-mj-XXXXXsiblings - in the case of SQLite:
/tmp,/var/tmpand/usr/tmpsince they are needed by SQLite. - any paths listed in
security.landlock.allowed_paths
The system trust store at /etc/ssl is not allowed. Operators who need to trust an additional CA must point SSL_CERT_FILE /
SSL_CERT_DIR at the file and list it under security.landlock.allowed_paths. All other filesystem access is denied by the
kernel after activation. This includes the /proc and /sys virtual filesystems.
The sandbox degrades gracefully on older kernels and non-Linux platforms, so no action is required for deployments that don't
support Landlock. To opt out completely (not recommended in production), set security.landlock.disabled: true.
Configuration hot-reload still works as before with one caveat: Landlock restrictions are irrevocable for the lifetime of the OS
process. Hot-reloading a config that flips security.landlock.disabled from false to true will not lift the sandbox — the
process must be restarted for the change to take effect. Other config changes (allow list entries, courier templates, etc.) reload
normally, but newly-introduced paths are only honoured on the next process start.
Symlinks
Symlinks in any configured path — --config files, TLS paths, identity.schemas[].url, the SQLite DSN,
security.landlock.allowed_paths, and so on — are followed by the kernel when the rule is added at startup, so the rule attaches
to the inode the symlink resolves to at that moment. As long as the target does not change, accesses through the symlink keep
working transparently.
Landlock rules are irrevocable for the lifetime of the process. If a symlink target swaps at runtime — for example when
cert-manager or certbot renews a certificate by writing a new file and re-pointing the symlink — the rule still references the
original target inode. Accesses through the symlink then resolve to the new inode, which is not in the allowlist, and the kernel
denies them with EPERM. Restart kratos serve after any such swap so the rules attach to the new inodes.
Troubleshooting
A path that the sandbox does not allow surfaces in the application as EPERM ("Operation not permitted") on open(2) /
openat(2) / execve(2) — Kratos typically logs it as permission denied while loading a config file, schema, template, or TLS
material. To distinguish a Landlock denial from a regular Unix permission error, use the steps below.
-
Confirm the sandbox is the cause. Check the Kratos startup logs for the line:
level=info msg="Landlock filesystem sandbox is active."Just before it, two log lines list every path that was added to the allowlist:
level=info msg="Landlock: collected roPaths." roPaths=[...]level=info msg="Landlock: collected rwDirs." rwDirs=[...]If the path that triggered
EPERMis missing from both lists, Landlock is the cause. As a sanity check, restart withsecurity.landlock.disabled: true: if the error disappears, the denial came from the sandbox. -
Read the kernel audit log. On Linux 6.10 and later, Landlock emits a kernel audit record for every denied access. The record names the syscall, the resolved path, and the denied access right. Read it with one of:
sudo journalctl -k --since "5 minutes ago" | grep -i landlocksudo dmesg -T | grep -i landlocksudo ausearch -m LANDLOCK_DENY -ts recent # auditd-based distrosA typical record looks like:
audit: type=1334 audit(...): domain=2 op=fs blockers=fs.read_file path="/etc/kratos/schemas/fragments/address.json" dev="vda1" ino=131072The
path=field is exactly what to add tosecurity.landlock.allowed_paths. Older kernels (5.13 – 6.9) do not emit these records — fall back to step 3 there. -
Trace the syscall directly. When the audit log is unavailable or the path is templated, attach
straceto the running process and watch forEPERMon the relevant syscalls:sudo strace -f -p "$(pgrep -f 'kratos serve')" -e trace=openat,execve -e status=failedLines ending in
= -1 EPERM (Operation not permitted)show the exact path the kernel rejected, even when Kratos's own log message has been swallowed by a wrapper. -
Fix the configuration. Once the offending path is known, add it to the allowlist (a directory grants every file underneath; a file grants only itself), then restart
kratos serve— Landlock rules are immutable for the lifetime of the process, so a hot reload will not lift the denial.security:landlock:allowed_paths:- /etc/kratos/schemas/fragments/address.json
Normalize phone trait values to E.164 in webhook payloads
When a phone trait is configured as a credential identifier (code strategy via sms), or as a recovery or verification address
via sms, Kratos now rewrites the trait value to its E.164 form during validation.
Previously, only the credential identifier and the recovery and verification address tables stored the normalized value, while the
traits JSON kept the raw user input. Webhook payloads templated against identity.traits.phone therefore saw a different value
than the one Kratos used internally as the identifier. The trait is now consistent with the canonical form, so webhook consumers
and Kratos see the same phone number.
Existing identities are not rewritten by this change. Use the kratos cleanup normalize-phones command to migrate stored
identifiers; the trait values are corrected the next time the identity passes through validation (for example, on a settings or
recovery flow that updates the trait).
