SYDTUTORIAL(7) Miscellaneous Information Manual SYDTUTORIAL(7)

sydtutorial - A tutorial introduction to Syd

syd [-acefhlmpqtxEPV] [--] {command [arg...]}

Syd intercepts system calls made by Linux processes and decides, according to a set of rules, whether each call should proceed, be denied, or be emulated. It does this without kernel modules, without setuid binaries, and without eBPF, using only seccomp(2) user notification, ptrace(2), landlock(7), and namespaces(7).

Run Syd with no arguments and it drops you into a login shell. Run it with a command and it sandboxes that command:

$ syd -poff -- echo hello
hello

The -poff selects the "off" profile, which disables all sandboxing. Without -poff, Syd denies everything by default including exec:

$ syd -- true
syd: exec error: Permission denied
$ echo $?
13

Exit code 13 is EACCES ("Permission denied"). This is what "secure by default" looks like in practice: you must opt in to every operation the sandboxed process is allowed to perform.

This tutorial walks through Syd's sandbox rules, starting from the simplest case ("allow everything and run") through incrementally tighter configurations. It is written for someone who has used the Linux command line and has heard of system calls, but has never touched seccomp(2), landlock(7), or any sandboxing tool.

The examples are tested against Syd 3.51.0 on Linux 6.19. You can type them verbatim on your own system.

Syd requires a Linux kernel with seccomp(2) user notification support. The following kernel features are required, listed with the minimum kernel version that introduced each one:

  • Linux 5.0: SECCOMP_RET_USER_NOTIF, allowing a supervisor process to intercept system calls and respond on behalf of the caller.
  • Linux 5.5: SECCOMP_USER_NOTIF_FLAG_CONTINUE, needed to let intercepted system calls proceed unmodified after inspection.
  • Linux 5.6: pidfd_getfd(2) and pidfd_send_signal(2), needed for file descriptor operations and signal delivery via process file descriptors. openat2(2) is also required for safe path resolution with RESOLVE_BENEATH, RESOLVE_NO_SYMLINKS, and RESOLVE_NO_MAGICLINKS.
  • Linux 5.9: SECCOMP_IOCTL_NOTIF_ADDFD, needed to inject file descriptors into the address space of a sandboxed process during system call emulation.
  • Linux 5.19: SECCOMP_FILTER_FLAG_WAIT_KILLABLE_RECV, which places the intercepted thread in a killable wait state during notification handling; this eliminates a class of unkillable-process bugs and is required for production use.
  • Linux 6.2 (optional): ALG_SET_KEY_BY_KEY_SERIAL, needed only for Crypt sandboxing; not required for general use.

The following kernel configuration options must be enabled:

  • CONFIG_SECCOMP and CONFIG_SECCOMP_FILTER are required for system call interception via seccomp(2).
  • CONFIG_SECURITY_LANDLOCK is required for landlock(7) filesystem and network access control. This option must be set to y at kernel build time, and landlock must appear in the boot-time CONFIG_LSM list (or be appended via the lsm= kernel command line parameter). Most major distributions enable landlock(7) by default, including Ubuntu (since 20.04), Fedora (since 35), Arch Linux, and Debian Sid.
  • CONFIG_UNIX_DIAG is required for UNIX domain socket diagnostics, which Syd uses to identify peer processes on UNIX sockets.
  • CONFIG_CROSS_MEMORY_ATTACH is recommended; enables process_vm_readv(2) and process_vm_writev(2) for reading and writing process memory. Unlike proc_pid_mem(5), cross memory attach honours the address space permissions of the target process, providing a safer mechanism for inspecting system call arguments. If CONFIG_CROSS_MEMORY_ATTACH is not available, Syd falls back to proc_pid_mem(5) automatically when the SYD_PROC_PID_MEM_FALLBACK environment variable is set, refer to syd(1) manual page for details.
  • CONFIG_KCMP is recommended; enables kcmp(2), which Syd uses to determine whether two file descriptors refer to the same open file description across processes and to check whether two processes share the same address space.

Syd is written in Rust. Building from source requires a Rust toolchain (edition 2024, Rust 1.83 or later) and libseccomp headers.

The quickest path to a working Syd installation is Cargo, the Rust package manager:

$ cargo install --locked syd

For OCI container runtime support (currently available on x86_64 and aarch64), enable the oci feature:

$ cargo install --locked --features oci syd

If you are working from a git checkout, run:

$ make install

This compiles an optimized release build of Syd and all companion utilities and installs them, along with man pages and Vim syntax files, under ~/.local. The resulting binaries are statically linked by default and can be copied to other systems without additional dependencies. Ensure that ~/.local/bin is in your PATH.

To build with OCI support from a git checkout:

$ make CARGOFEATS=oci install

After installation, run syd --check to print a diagnostic summary of your system's sandboxing capabilities:

$ syd --check
syd 3.51.0 (Crazy Goldberg)
Rock solid application kernel
...
LibSeccomp: v2.9.9 api:7
Landlock ABI 7 is fully enforced.
User namespaces are supported.
Cross memory attach is supported.
Memory sealing is supported.
...
LSMs: capability, landlock, lockdown, yama, bpf.

This output lists the seccomp(2) API level, the landlock(7) ABI version, namespaces(7) support, which Linux Security Modules (LSMs) are active, the set of vDSO calls available, open file descriptor limits, and the kernel version together with its supported features. If Syd depends on a kernel capability that is absent, this command will tell you.

To query the landlock(7) ABI version in isolation:

$ syd-lock -V

When invoked with no positional arguments, Syd enters login shell mode. It loads the builtin user profile and spawns a restricted bash(1) session:

$ syd
bash-5.3$

The shell Syd starts is not an ordinary bash session. As defined in src/config.rs, the default command is:

/usr/bin/env HISTFILE= /usr/bin/bash --login --noprofile --norc --restricted

Several properties of this invocation are worth noting. First, HISTFILE is set to the empty string, which disables command history. No record of the session is written to disk. Second, the --noprofile and --norc flags suppress ~/.bash_profile, ~/.bashrc, and /etc/profile, preventing user and system startup scripts from modifying the sandbox environment. Third, the --restricted flag activates restricted shell mode (rbash), which among other things prohibits changing directories with cd, redirecting output, and modifying PATH. Together, these flags produce a minimal, hardened shell with minimal capabilities.

The login shell applies the user profile, which enables sandbox rules for common interactive use. Try a few commands to see what the profile permits:

bash-5.3$ pwd
/proc/42/fdinfo
bash-5.3$ ls -la
ls: cannot open directory '.': No such file or directory
bash-5.3$ echo hello
hello
bash-5.3$ ls /
ls: cannot open directory '/': Permission denied
bash-5.3$ cat /etc/hostname
cat: /etc/hostname: No such file or directory
bash-5.3$ exit
logout

Several things happened here. First, pwd reports a path under proc_pid_fdinfo(5). This is Syd's own proc(5) directory, the sandbox manager's process ID. Syd restricts access to its own proc(5) entries to prevent sandboxed processes from inspecting or interfering with the sandbox itself (refer to the SECURITY section of syd(7)). Consequently, ls -la cannot open the directory: it returns ENOENT ("No such file or directory") because the path is hidden by proc(5) restrictions. The shell effectively starts in a location that exists in the kernel's VFS but is invisible to the sandboxed process.

The echo builtin works because builtins do not invoke execve(2); they run inside the shell process itself. The ls / command is an external binary whose execution the user profile permits, but reading the root directory is denied by the profile's read sandbox rules. The cat /etc/hostname result is more subtle: it reports ENOENT ("No such file or directory") rather than EACCES ("Permission denied"). This happens because Syd's Stat Sandboxing hides the file entirely, stat(2) returns ENOENT ("No such file or directory") and getdents64(2) omits the entry from directory listings, so from the process's perspective the file does not exist.

The SYD_SHELL environment variable overrides the default shell command:

$ SYD_SHELL=/bin/sh syd
$

Syd's command line parsing follows POSIX conventions (options first, then positional arguments), so the -- separator is not required. You can sandbox a single command by providing it directly:

$ syd true
syd: exec error: Permission denied
$ echo $?
13

Without a profile, Syd denies execve(2) and returns exit code 13 aka EACCES ("Permission denied"). The syd-sys(1) utility can translate between numbers and names for system calls, errno(3) values, ioctl(2) requests, open(2) flags, and signal(7) numbers. It can also list UNIX domain socket inodes via netlink(7). For example:

$ syd-sys -e 13
13      EACCES  Permission denied
$ syd-sys 1
write   1

This is the default: every operation is forbidden unless a rule explicitly permits it. To run a command that actually executes, select a profile:

$ syd -poff echo hello
hello

The -poff flag loads the off profile, which disables all sandbox categories. This is useful for verifying that Syd itself is working before adding restrictions.

Profiles are pre-defined sets of sandbox rules compiled into the Syd binary. Each profile configures which sandboxing categories are active and which paths, addresses, and system calls are allowed or denied. The PROFILES section of syd(5) manual page documents the full set of available profiles and their intended use.

To list the available profiles:

$ syd-cat -p list
chrome
container
core
cwd
debug
enforce
firefox
fs
gui
hide
immutable
landlock
lang
ldd
lib
linux
ltp
nix
nixstore
...

Some profiles serve as building blocks for others. For example, the linux profile provides a common set of rules for Linux systems and is included by the user, paludis, and oci profiles.

The user profile is the default for the login shell and is suitable for general interactive use. The immutable profile treats the entire root filesystem as read-only, permitting writes only to explicitly allowed locations. The off profile disables all sandboxing.

To examine the rules that a profile contains:

$ syd-cat -p user

Multiple profiles can be combined on the command line; later profiles override rules from earlier ones:

$ syd -pimmutable -mallow/write+/var/cache/*** make install

The -m flag passes individual sandbox commands on the command line. Each -m takes one command as documented in syd(2). All -p, -P, and -m flags are processed in the order they are given on the command line. Because Syd uses a "last match wins" rule resolution strategy (documented in syd(2)), later flags override earlier ones for the same sandbox category regardless of type.

A basic example enables Write Sandboxing atop the off profile:

$ syd -poff -msandbox/write:on -mallow/write+/tmp/*** touch /tmp/hello
$ echo $?
0

Here -poff disables all sandboxing, -msandbox/write:on re-enables Write Sandboxing, and -mallow/write+/tmp/*** adds /tmp and everything below it to the write allowlist. Because /tmp/hello matches the allow rule, touch(1) succeeds.

Multiple -m flags for the same category layer in order. You can first allow a broad directory tree and then deny a subtree within it:

$ mkdir -p /tmp/secret
$ syd -poff -msandbox/write:on -mallow/write+/tmp/*** -mdeny/write+/tmp/secret/*** touch /tmp/secret/plans
{"ctx":"access","cap":"write","act":"deny","sys":"openat", "path":"/tmp/secret/plans", "tip":"configure `allow/write+/tmp/secret/plans'"}
touch: cannot touch '/tmp/secret/plans': Permission denied
$ echo $?
1

Syd logs the denied access as a JSON object on standard error, including the system call that was denied (openat), the path, and a tip field suggesting how to allow it. The deny rule for /tmp/secret comes after the allow rule for /tmp, so the deny wins. Reversing the order would produce the opposite result, the allow would override the deny.

The -m rules layer atop the selected profile. Without -poff or another profile that allows execution, the default sandbox denies execve(2) before any write rule has a chance to take effect:

$ syd -msandbox/write:on touch /tmp/hello
syd: exec error: Permission denied

This is a common mistake when first using Syd. Always start from a profile that permits execution, then layer restrictions with -m. The off profile followed by selective sandbox enables is one approach; the user profile with additional deny rules is another.

The previous section introduced profiles through the -p flag and individual commands through -m. This section covers the full configuration machinery: what profiles contain, how configuration files work, how rules are resolved, and how patterns match paths.

A profile is a named set of sandbox commands compiled into the Syd binary. To inspect its contents, pass its name to syd-cat(1):

$ syd-cat -poff
# Syd profile: Off
# Number of rules: 2
# Copyright (c) 2023, 2024 Ali Polatel <alip@chesswob.org>
# SPDX-License-Identifier: GPL-3.0
sandbox/all:off
sandbox/fs,ioctl,lock,net,mem,pid,pty,force,tpe:off

The off profile consists of exactly two commands: one that turns off all primary sandbox categories, and one that turns off every secondary category. Compare this with the user profile:

$ syd-cat -puser
# Syd profile: User "user"
# Number of rules: 18
include_profile linux
include_profile landlock
include_profile local
include_profile nomagic
include_profile rand
include_profile tty
sandbox/lpath:${SYD_USER_LPATH:-on}
trace/allow_safe_syslog:true
tpe/negate:1
tpe/user_owned:1
tpe/gid:${SYD_GID}
trace/force_umask:7177
allow/lock/all+${SYD_HOME}
allow/all+${SYD_HOME}/**
allow/lpath,rpath+${SYD_HOME}/***
deny/all+${SYD_HOME}/**/.*/***
allow/all+${SYD_HOME}/**/._history_
append+${SYD_HOME}/.*history

Several features are visible here. The include_profile directive includes other profiles by name: linux, landlock, local, nomagic, rand, and tty are all pulled in, making the user profile a composition of lower-level building blocks. Environment variables such as ${SYD_HOME} and ${SYD_GID} are expanded at parse time; Syd sets these automatically before loading the profile. Refer to the ENVIRONMENT section of syd(5) manual page. The notation ${SYD_USER_LPATH:-on} provides a default value: if the variable is unset, the value on is used.

Multiple -p flags can appear on the command line. Profiles are loaded in order, and because Syd uses a last-match-wins strategy, later profiles override rules from earlier ones. This allows incremental refinement:

$ syd -puser -pimmutable ls /
ls: cannot open directory '/': Permission denied
$ echo $?
2

The user profile permits reading most of the filesystem, but the immutable profile, loaded second, remounts system directories read-only inside a mount_namespaces(7) and applies stricter access rules that override the user defaults.

Some profiles have one-character shortcuts. These shortcuts can be combined into a single -p argument:

$ syd -puiq ...

This stacks the user (u), immutable (i), and quiet (q) profiles. The full list of profiles and their shortcuts is documented in the PROFILES section of syd(5), and can always be queried with syd-cat -plist.

Configuration files provide the same commands as -m flags, one per line. Comments begin with #; blank lines are ignored. The file extension must be .syd-3, reflecting the current API version.

A minimal configuration file that confines writes to /tmp:

# /tmp/example.syd-3: Allow writes under /tmp only
sandbox/write:on
allow/write+/tmp/***

Load it with the -P flag:

$ syd -poff -P/tmp/example.syd-3 touch /tmp/syd_test_file
$ echo $?
0

Multiple -P flags can be specified. All -p, -P, and -m arguments are processed strictly in the order they appear on the command line--there is no precedence between them. A -m that appears before a -P takes effect first.

The syd-cat(1) utility can parse and validate configuration files independently of Syd itself. Pass one or more file paths and it will report syntax errors or print the resolved sandbox state:

$ syd-cat /tmp/example.syd-3
Syd:
Sandbox ID: ?
...
Glob Rules: (1.66K, total 1, highest precedence first)
1. Action: allow, Capability: write, Pattern: `/tmp'
...

If the file contains errors, syd-cat(1) exits with a non-zero status and an error message, making it useful for testing configuration before deploying it.

Configuration files support two inclusion directives:

  • include path includes another configuration file. Relative paths are resolved from the directory of the including file, not the current working directory. The included file must not be writable by group or others for security. Circular includes are detected by caching device and inode numbers.
  • include_profile name includes a built-in profile by name, exactly as if -p had been specified.

Environment variables are expanded in all arguments using shellexpand syntax. If a variable is unset, Syd aborts with an error rather than expanding to the empty string. This prevents accidental over-permissive rules. Use ${VAR:-default} to supply fallback values.

Path rules use glob(3p) patterns. The standard wildcards apply: * matches any sequence of characters within a single path component, ? matches a single character, and [...] matches a character class.

Syd extends standard globbing with the triple-star pattern ***, which matches the prefix directory itself and everything below it to arbitrary depth. A pattern like /tmp/*** first matches the directory /tmp on its own, then matches any path beneath it. The three wildcard levels are:

  • /tmp/* matches /tmp/foo but not /tmp/foo/bar.
  • /tmp/** matches files in immediate subdirectories of /tmp.
  • /tmp/*** matches /tmp itself, /tmp/foo, /tmp/foo/bar, and so on to arbitrary depth.

Syd evaluates rules in the order they appear. For multiple rules that match the same path, the last matching rule determines the outcome. All -p, -P, and -m arguments are processed strictly in command-line order; there is no precedence between them.

This means you can start with a broad allow and carve out exceptions with later deny rules, or start restrictive and add targeted allows.

In addition to startup-time configuration, Syd supports runtime reconfiguration through magic stat(2) calls. A sandboxed process can issue:

test -c /dev/syd/sandbox/read:on

This stat(2) call on the virtual path /dev/syd/sandbox/read:on enables read sandboxing at runtime. The stat(2) interface accepts the same commands as -m and is documented in syd(2). Runtime configuration is permitted when the sandbox lock is off, exec, ipc, or drop. With lock:off, any process in the sandbox can issue runtime commands. With lock:exec, only the initial exec child retains this ability. With lock:ipc, commands must be sent through the IPC socket. With lock:drop, commands can only reduce privileges, commands relaxing the sandbox policy aren't permitted. This mode is similar to OpenBSD pledge(2). When the lock is on or read, runtime changes are not accepted.

The user profile, loaded by default in login shell mode, searches for two additional configuration files at startup:

  • /etc/user.syd-3 -- system-wide rules applied to all users.
  • ~/.user.syd-3 -- per-user rules.

These files are parsed after the user profile itself. Because last-match-wins semantics apply, rules in ~/.user.syd-3 override rules in /etc/user.syd-3, which in turn override the built-in user profile defaults.

To lock the system-wide configuration so that per-user files cannot weaken it, place lock:on or lock:drop at the end of /etc/user.syd-3. After the lock is set, no further configuration changes are accepted, neither from subsequent files nor from runtime stat(2) calls.

The sandbox lock controls whether and how sandbox rules can be modified after Syd starts executing the sandboxed process. It is set with the lock command (documented in syd(2)) and has six possible states: on, off, exec, ipc, drop, and read. Single-character abbreviations are also accepted: 1, 0, x, i, d, and r. Specifying lock without a value is equivalent to lock:on.

lock:on seals the sandbox policy entirely. No runtime configuration is accepted from any source:

$ syd -poff -mlock:on sh -c 'test -c /dev/syd/sandbox/write:on && echo "enabled" || echo "locked out"'
locked out

lock:off leaves the sandbox fully open to runtime changes. Any process in the sandbox can issue stat(2) commands on /dev/syd/ paths to modify the policy.

lock:exec sets the lock to on for all processes except the initial exec child. This allows the initial process to configure the sandbox at startup and then seal it:

$ syd -poff -mlock:exec sh -c 'test -c /dev/syd/sandbox/write:on && echo "write on"; test -c /dev/syd/lock:on && echo "locked"; test -c /dev/syd/sandbox/read:on && echo "read on" || echo "config rejected after lock"'
write on
locked
config rejected after lock

The initial shell enables write sandboxing at runtime, then transitions to lock:on. After that, the attempt to enable read sandboxing is rejected.

lock:ipc restricts runtime commands to the IPC socket. The IPC socket is a UNIX domain socket whose accessibility depends on the sandbox ACL rules. Processes that cannot reach the socket cannot modify the policy.

lock:drop permits commands that further restrict the sandbox but rejects commands that would loosen it. This is useful for processes that need to progressively tighten their own confinement:

$ syd -poff -mlock:drop sh -c 'test -c /dev/syd/sandbox/write:on && echo "write on"'
write on

Enabling a sandbox category counts as dropping privileges, so the command is accepted.

lock:read makes the syd(2) virtual system call API available in read-only mode. The sandboxed process can query the current policy state through the open(2) hooks but cannot modify it.

If no lock command appears in any profile, configuration file, or -m argument, Syd defaults to lock:on at the moment it executes the initial sandbox process. This ensures that the sandbox policy is sealed by default.

Transitions from lock:off, lock:exec, lock:ipc, and lock:drop into lock:on or lock:read are one-way. Once the lock reaches on or read, the sandbox policy is sealed in memory using mseal(2) and cannot be changed. Transitions between lock:on and lock:read are not permitted.

Syd intercepts system calls that operate on filesystem paths and checks them against per-category allow and deny lists. Each sandbox category corresponds to a class of file operations and can be enabled or disabled independently.

The primary path sandbox categories are:

  • read -- open(2) with O_RDONLY or O_RDWR, getxattr(2) and related extended attribute reads.
  • write -- open(2) with O_WRONLY or O_RDWR.
  • exec -- execve(2), execveat(2), mmap(2) with PROT_EXEC, and dynamic library loading.
  • stat -- stat(2), statx(2), access(2), readlink(2), getdents64(2), and related metadata calls.
  • walk -- Path traversal during canonicalization, split from stat to prevent unhiding of hidden paths.

Syd also provides fine-grained categories for specific operations:

  • create -- creat(2), open(2) with O_CREAT, memfd_create(2).
  • delete -- unlink(2), unlinkat(2) without AT_REMOVEDIR.
  • rename -- rename(2), renameat(2), link(2), linkat(2).
  • symlink -- symlink(2), symlinkat(2).
  • truncate -- truncate(2), fallocate(2), open(2) with O_TRUNC.
  • chdir -- chdir(2), fchdir(2).
  • readdir -- open(2) on existing directories.
  • mkdir -- mkdir(2), mkdirat(2).
  • rmdir -- rmdir(2), unlinkat(2) with AT_REMOVEDIR.
  • chown, chgrp -- chown(2), fchownat(2) and variants.
  • chmod -- chmod(2), fchmodat(2), fchmodat2(2).
  • chattr -- setxattr(2), removexattr(2) and variants.
  • chroot -- chroot(2).
  • notify -- fanotify_mark(2), inotify_add_watch(2).
  • utime -- utimensat(2), utimes(2).
  • mkdev -- mknod(2) for block devices.
  • mkfifo -- mknod(2) for FIFOs.
  • mktemp -- open(2) with O_TMPFILE.

Refer to syd(7) manual page for the complete list of system calls filtered by each category.

Working with individual categories can be verbose. Syd provides shorthand names inspired by the promise names of OpenBSD's pledge(2) and FreeBSD's capsicum rights(4freebsd). These names group related categories into sets that can be used anywhere a category name is accepted:

  • rpath -- read, readdir. Named after the pledge(2) rpath promise. In pledge(2), rpath also covers stat(2), access(2), readlinkat(2), and chdir(2); Syd separates those into the lpath set.
  • wpath -- write, truncate. Named after the pledge(2) wpath promise.
  • cpath -- create, delete, rename. Named after the pledge(2) cpath promise, which also covers mkdir(2) and rmdir(2); Syd separates those into the tpath set.
  • fattr -- chmod, chattr, utime. Named after the pledge(2) fattr promise. In pledge(2), fattr also covers chown(2) and fchown(2); Syd separates ownership changes into the fown set.
  • fown -- chown, chgrp. A Syd-specific set that splits ownership changes out of the pledge(2) fattr promise.
  • dpath -- mkbdev, mkcdev. Named after the pledge(2) dpath promise, which covers mknod(2) and mkfifo(2). Syd narrows this set to block and character device creation only; FIFOs are in the separate spath set.
  • spath -- mkfifo, symlink. A Syd-specific set; pledge(2) places mkfifo(2) under dpath and symlink(2) under cpath.
  • tpath -- mkdir, rmdir. A Syd-specific set that splits directory creation and removal out of the pledge(2) cpath promise.
  • lpath -- walk, stat, chdir, notify. A Syd-specific set with no pledge(2) analogue. It corresponds to the path visibility controls of unveil(2) and groups the categories responsible for path lookup, metadata access, and directory change.
  • net -- net/bind, net/connect, net/sendfd.
  • inet -- net/bind, net/connect. Named after the pledge(2) inet promise.
  • all -- every category.
  • all-x -- every category except exec.

Each set also has a lock/ variant that controls the corresponding landlock(7) access rights rather than the seccomp(2) sandbox rules. For instance, lock/rpath controls landlock(7) read and readdir access rights, while rpath controls the seccomp(2) read and readdir sandbox categories.

These sets make rules more concise. Compare the two equivalent rules:

deny/read,readdir,write,truncate,create,delete,rename+${HOME}/.ssh/***
deny/rpath,wpath,cpath+${HOME}/.ssh/***

Categories are enabled with the sandbox/ command and can be grouped with commas:

sandbox/read,write,exec:on

The shorthand sandbox/all:on enables every primary category at once. Individual categories can then be turned off selectively.

Once a category is enabled, all operations in that category are denied by default. The default action can be changed per category with the default/ command. The available actions are:

  • allow -- Permit the system call.
  • deny -- Deny the system call with EACCES (default).
  • filter -- Deny the system call silently, without logging.
  • warn -- Allow the system call but log a warning (learning mode, used by pandora(1)).
  • kill -- Deny the system call and terminate the offending process with SIGKILL (see signal(7)).
  • stop -- Deny the system call and send SIGSTOP to the offending process (see signal(7)).
  • abort -- Deny the system call and send SIGABRT to the offending process (see signal(7)). Unlike SIGKILL, SIGABRT can be caught, so this action should only be used for debugging in trusted environments where a core(5) dump file is useful.
  • panic -- Deny the system call and panic the Syd emulator thread. Currently equivalent to deny.
  • exit -- Log a warning and exit Syd immediately with the deny errno(3) as exit value. All sandbox processes are terminated: direct children receive SIGKILL via the parent death signal (see PR_SET_PDEATHSIG(2const)), traced processes are killed via PTRACE_O_EXITKILL (see ptrace(2)), and closing the seccomp(2) notification file descriptor causes any pending system calls to fail.

Refer to syd(7) for the full description of sandbox actions and syd(2) for the default/ command documentation.

Allow rules open specific paths; deny rules close them. Both use glob(3p) patterns as described in the Pattern Matching section.

The following example enables write sandboxing, allows writes under /tmp, and then attempts to write outside the allowed area:

$ syd -poff -msandbox/write:on -mallow/write+/tmp/*** -mallow/read+/*** touch /tmp/pathtest
$ echo $?
0
$ syd -poff -msandbox/write:on -mallow/read+/*** touch /home/alip/forbidden
{"ctx":"access","cap":"write","act":"deny","sys":"openat","path":"/home/alip/forbidden","tip":"configure `allow/write+/home/alip/forbidden'"}
touch: cannot touch '/home/alip/forbidden': Permission denied

The first touch(1) succeeds because /tmp/pathtest matches the allow rule. The second is denied because no allow rule covers /home/alip/forbidden.

The stat and walk categories can hide files and directories from the sandboxed process entirely. When a stat(2) call is denied, Syd returns ENOENT ("No such file or directory") to the caller, making the path appear non-existent:

$ syd -poff -msandbox/stat:on -mallow/stat+/*** -mdeny/stat+/etc/shadow ls -la /etc/shadow
ls: cannot access '/etc/shadow': No such file or directory

The process receives no indication that /etc/shadow exists. Programs that enumerate directory contents via getdents64(2) also have the hidden entries filtered out.

The walk category complements stat by preventing hidden paths from being discovered during path canonicalization. Without walk, a process could detect a hidden path by traversing through it (e.g. resolving /etc/shadow/../passwd). Together, stat and walk provide a complete path hiding mechanism analogous to unveil(2) on OpenBSD.

Where hiding makes a path invisible, masking replaces its contents. The mask command redirects open(2) calls on matching paths to a different file--by default /dev/null. At the stat(2) boundary, a masked path returns the metadata of the mask target, not the original file.

A masked /etc/hostname reads as empty:

$ syd -poff -mmask+/etc/hostname -mallow/read+/*** cat /etc/hostname
$ echo $?
0

The cat(1) call succeeds but produces no output because open(2) returns a file descriptor to /dev/null. A stat(2) call on the same path returns the mask target's metadata:

$ syd -poff -msandbox/lpath:on -mallow/lpath+/*** -mmask+/etc/hostname -mallow/read+/*** stat /etc/hostname

File: /etc/hostname
Size: 0 Blocks: 0 IO Block: 4096 character special file Device: 0,6 Inode: 4 Links: 1 Device type: 1,3 Access: (0666/crw-rw-rw-) Uid: ( 0/ root) Gid: ( 0/ root)

The file name still reads /etc/hostname, but the metadata reports a character special file with device 1,3 which are the attributes of /dev/null.

The default mask target can be changed by appending a colon-separated path. For example, masking with /dev/zero causes reads to return zero bytes:

$ syd -poff -mmask+/etc/hostname:/dev/zero -mallow/read+/*** sh -c 'head -c 8 /etc/hostname | xxd'
00000000: 0000 0000 0000 0000                      ........

For directories, a second colon-separated path specifies the directory target:

mask+/proc/acpi/***:/dev/null:/var/empty

This masks regular files under /proc/acpi with /dev/null and subdirectories with /var/empty. Mask targets must be fully canonicalized paths without symbolic links.

Masked paths are also protected against filesystem writes in the same manner as append-only paths. The file cannot be truncated, overwritten, renamed, deleted, or have its metadata changed.

The mask command does not require creating a mount_namespaces(7), providing a non-privileged alternative to bind mounts. Mask commands can also be specified dynamically after startup using the syd(2) API, allowing for incremental confinement.

The append command marks paths as append-only, providing protection equivalent to the Linux inode append-only attribute (set with chattr(1) +a). Unlike a simple O_APPEND flag, the protection covers all modification paths:

  • open(2) with a writable access mode is denied with EPERM ("Operation not permitted") unless O_APPEND is set. open(2) with O_TRUNC is denied regardless of the access mode.
  • Metadata and namespace operations are denied with EPERM ("Operation not permitted"): chmod(2), chown(2), rename(2), link(2), unlink(2), truncate(2), setxattr(2), removexattr(2), utime(2), and their variants.
  • Clearing O_APPEND via fcntl(2) F_SETFL is denied with EPERM ("Operation not permitted").
  • Writable shared memory mappings via mmap(2) with MAP_SHARED and PROT_WRITE are denied with EPERM ("Operation not permitted").
  • pwritev2(2) with the RWF_NOAPPEND flag is denied with EOPNOTSUPP ("Operation not supported") via a seccomp(2) filter.

The result is that an append-only path can only grow by appending data; it cannot be truncated, overwritten, renamed, deleted, or have its metadata changed. This is useful for log files and shell history. The user profile uses this to protect shell history files:

append+${SYD_HOME}/.*history

The glob pattern matches ~/.bash_history, ~/.zsh_history, ~/.python_history, and similar files.

Practical Example

The following configuration sandboxes a build tool. It allows reading and stat access everywhere, restricts writes to the build directory, terminates with SIGKILL any process that attempts to access ~/.ssh or ~/.gnupg, hides /etc/shadow from stat, masks /etc/hostname with /dev/null, protects shell history as append-only, and permits execution only from /usr:

include_profile tty
sandbox/read,write,exec,stat:on
allow/read+/***
allow/stat+/***
allow/write+/home/alip/project/build/***
allow/exec+/usr/***
kill/rpath,wpath,cpath+${HOME}/.ssh/***
kill/rpath,wpath,cpath+${HOME}/.gnupg/***
kill/stat+/etc/shadow
mask+/etc/hostname
append+${SYD_HOME}/.*history

Any attempt to write outside /home/alip/project/build, execute a binary from outside /usr, or access ~/.ssh is terminated with SIGKILL. A stat(2) call on /etc/shadow returns ENOENT ("No such file or directory"), and reading /etc/hostname yields empty output.

Lock Sandboxing uses the Landlock Linux Security Module (landlock(7)) for kernel-enforced unprivileged access control. Unlike the seccomp(2) based sandbox, Landlock rules are enforced entirely in kernel space and apply to the Syd process itself. A compromised Syd process is still confined by the Landlock sandbox, making Lock sandboxing a second layer of defence.

Lock Sandboxing is enabled by default. The off profile disables it with sandbox/lock:off. Paths and port ranges are populated using lock/ categories:

allow/lock/read+/usr
allow/lock/read,write+/tmp
allow/lock/exec+/usr/bin
allow/lock/bind+8080
allow/lock/connect+0-65535

The available lock/ categories are: lock/read, lock/write, lock/exec, lock/ioctl, lock/create, lock/delete, lock/rename, lock/symlink, lock/truncate, lock/readdir, lock/mkdir, lock/rmdir, lock/mkdev, lock/mkfifo, and lock/bind. The shorthand lock/all stands for the union of all these categories, and lock/all-x stands for all except lock/exec.

Lock category sets group related lock/ categories, mirroring the structure of the seccomp(2) category sets but covering only the operations that landlock(7) can enforce:

  • lock/rpath -- lock/read, lock/readdir.
  • lock/wpath -- lock/write, lock/truncate.
  • lock/cpath -- lock/create, lock/delete, lock/rename.
  • lock/dpath -- lock/mkbdev, lock/mkcdev.
  • lock/spath -- lock/mkfifo, lock/symlink.
  • lock/tpath -- lock/mkdir, lock/rmdir.
  • lock/net -- lock/bind, lock/connect.
  • lock/inet -- lock/bind, lock/connect.
  • lock/bnet -- lock/bind.
  • lock/cnet -- lock/connect.

Notably, there are no lock/fattr, lock/fown, or lock/lpath sets because landlock(7) does not govern ownership, attribute changes, or path traversal.

As of version 3.29.0, Landlock network confinement is supported. Use allow/lock/bind+port and allow/lock/connect+port to allowlist specific ports. A closed range port1-port2 is also accepted:

allow/lock/bind+8080-8090
allow/lock/connect+443

UNIX domain socket creation, renames, and links can be confined using the lock/bind category with an absolute path:

allow/lock/bind+/run/user/${SYD_UID}

The default compatibility level is kill (since version 3.35.0), which maps to Hard Requirement: paths specified in lock/ rules must exist, and missing paths cause a fatal ENOENT error. The level can be changed at startup with default/lock:

  • kill -- Hard Requirement; missing paths are fatal (default).
  • deny -- Soft Requirement; missing paths produce a warning but are skipped.
  • warn -- Best Effort; log a warning for missing paths and skip them, apply whatever the running kernel ABI supports.

Refer to syd(2) manual page for the full default/lock documentation.

The syd-lock(1) utility runs a single command under a Landlock sandbox without Syd's full seccomp(2) machinery. The -l flag specifies categories and paths in the same syntax as lock/ commands:

$ syd-lock -l read,exec+/ -l write+/tmp wget -O /tmp/file https://example.com

This confines wget(1) to read and execute from /, write only to /tmp, and deny all other filesystem access at the Landlock level. Use syd-lock -V to print the Landlock ABI version supported by the running kernel.

Syd confines network operations through three categories:

Supported socket families are UNIX, IPv4, IPv6, Netlink, and KCAPI. The option trace/allow_unsupp_socket:1 passes through sockets of unsupported types.

Network rules use a simple address scheme. UNIX and abstract UNIX socket addresses use glob(3p) patterns. IPv4 and IPv6 addresses use CIDR notation followed by a port range separated by !:

allow/net/connect+192.168.1.0/24!80-443
deny/net/bind+0.0.0.0/0!0-1023
allow/net/bind+/run/user/${SYD_UID}/***

A port range can be a single port (80) or a closed range (1024-65535). UNIX domain socket paths always start with /, abstract sockets are prefixed with @, and unnamed sockets use the dummy path !unnamed.

Syd provides aliases for common address ranges to avoid hardcoding CIDR blocks:

  • any -- 0.0.0.0/0 + ::/0 (all IPv4 and IPv6).
  • loopback -- 127.0.0.0/8 + ::1/128.
  • local -- RFC 1918 private ranges: 10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, and IPv6 equivalents.
  • linklocal -- 169.254.0.0/16 + fe80::/10.
  • multicast -- 224.0.0.0/4 + ff00::/8.

Each alias also has 4 and 6 variants (e.g. loopback4, loopback6) to target a single address family.

Practical Example

The following enables network sandboxing and allows only outbound connections to loopback on port 80. A connection to an external address is denied:

$ syd -poff -msandbox/net:on -mallow/read+/*** -mallow/net/connect+loopback!80 

curl -so /dev/null http://1.1.1.1 {"cap":"net/connect","act":"deny","sys":"connect","addr":"1.1.1.1!80",
"tip":"configure `allow/net/connect+1.1.1.1!80'"}

The curl(1) connection to 1.1.1.1 is denied because only loopback port 80 is allowed. Allowing HTTPS outbound to any address is as simple as:

allow/net/connect+any!443

Refer to syd(7) manual page for the full network sandboxing documentation and syd(2) manual page for the address matching syntax.

The block command maintains a set of IP networks that are blocked on connect(2), sendto(2), sendmsg(2), sendmmsg(2), and checked against source addresses returned by accept(2) and accept4(2). Use block+ and block- to add and remove networks. Syd can import IP blocklists in ipset and netset formats directly from configuration:

include /usr/src/blocklist-ipsets/feodo.ipset
include /usr/src/blocklist-ipsets/dshield.netset
block!

The block! command aggregates the imported networks to reduce memory consumption and improve matching performance. Use block^ to clear the blocklist. Refer to syd(2) manual page for the full block command documentation.

The exec category confines binary execution and dynamic library loading. The filtered system calls are execve(2), execveat(2), mmap(2), mmap2(2), and memfd_create(2). For scripts, both the script and its interpreter are checked. Dynamic libraries linked to ELF executables are checked at exec time, and mmap(2) calls with PROT_EXEC (typically dlopen(3)) are checked at runtime. Enable it with sandbox/exec:on and allowlist trusted paths:

sandbox/exec:on
allow/exec+/usr/***

Any attempt to execute a binary outside the allowed paths is denied with EACCES ("Permission denied"):

$ syd -poff -msandbox/exec:on -mallow/exec+/usr/*** \

-mallow/read+/*** /tmp/test_echo hello {"cap":"exec","act":"deny","sys":"execve","path":"/tmp/test_echo",
"tip":"configure `allow/exec+/tmp/test_echo'"} syd: exec error: Permission denied

The default action for exec violations can be changed with default/exec, for example default/exec:kill terminates the process with SIGKILL on any exec violation.

Trusted Path Execution (TPE) restricts execution to binaries that reside in trusted directories. Enable it with sandbox/tpe:on. A binary is trusted if both the file and its parent directory satisfy:

  • Not writable by group or others.
  • Owned by root (optional, enable with tpe/root_owned:1).
  • Owned by the current user or root (optional, enable with tpe/user_owned:1).
  • On the root filesystem (optional, enable with tpe/root_mount:1).

If these criteria are not met, execution is denied with EACCES ("Permission denied"). The default action can be changed with default/tpe, for example default/tpe:kill terminates the offending process with SIGKILL (see signal(7)).

TPE checks at three stages:

  • execve(2) / execveat(2) system call entry to check scripts.
  • ptrace(2) exec event to check the ELF executable and dynamic loader.
  • mmap(2) when dynamic libraries are mapped, typically via dlopen(3).

By default, TPE applies to all users. To restrict it to a specific group, set tpe/gid to the untrusted group ID. The tpe/negate option inverts this logic, making the specified group trusted instead.

Syd's TPE implementation is based on HardenedBSD's, which is inspired by GrSecurity's TPE. Refer to syd(2) manual page for the full list of tpe/ options.

Force sandboxing verifies binary integrity at execution time. Enable it with sandbox/force:on. The force command populates an Integrity Force map that associates file paths with checksums:

force+/usr/bin/curl:sha256:a1b2c3...hexdigest...:deny

The format is force+/path:algorithm:hashhex:action where :action is optional and defaults to deny. Available algorithms are any ahash or shash listed in proc_crypto(5), e.g. sha256, sha3-512, blake2b-256, crc32c. Available actions are warn, filter, deny (the default), panic, stop, abort, kill, and exit. Use force-/path to remove an entry, or force^ to clear the map.

Upon execve(2), Syd computes the checksum of the target binary and compares it against the map. A mismatch triggers the configured action. Beyond execve(2), Force sandboxing also checks:

  • Dynamic libraries linked to ELF executables.
  • Libraries loaded at runtime via mmap(2) with PROT_EXEC (typically dlopen(3)).

Helper tools:

  • syd-sum(1) calculates checksums of files.
  • syd-path(1) generates integrity force rules for all binaries under PATH.

Refer to syd(2) manual page for the full force command documentation.

SegvGuard blocks execution of binaries that crash repeatedly, mitigating brute-force exploitation attacks. Inspired by HardenedBSD's implementation with identical defaults:

  • segvguard/maxcrashes -- maximum crashes before suspension (default: 5).
  • segvguard/expiry -- time window for counting crashes in seconds (default: 120, i.e. 2 minutes).
  • segvguard/suspension -- suspension duration in seconds (default: 600, i.e. 10 minutes).

If a sandboxed process receives a crash signal segvguard/maxcrashes times within segvguard/expiry seconds, subsequent attempts to execute the same binary are denied for segvguard/suspension seconds. Disable SegvGuard by setting segvguard/expiry:0.

The trigger signals are SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGIOT, SIGKILL, SIGQUIT, SIGSEGV, SIGSYS, SIGTRAP, SIGXCPU, and SIGXFSZ (see signal(7)). SIGKILL is intentionally included even though it does not produce a core(5) dump, so that kill sandbox rules trigger SegvGuard.

SegvGuard depends on ptrace(2) and can be disabled by setting trace/allow_unsafe_ptrace:1. Refer to syd(2) for the full list of segvguard/ options and syd(7) for further reading.

Syd enforces W^X (Write XOR Execute) memory protection by default using PR_SET_MDWE (see PR_SET_MDWE(2const)) and seccomp(2) filters on mmap(2), mmap2(2), mprotect(2), pkey_mprotect(2), and shmat(2). Memory mappings that are simultaneously writable and executable are rejected by the kernel-level seccomp(2) filter, which terminates the offending process with SIGSYS (see signal(7)).

Syd also validates file descriptor writability during executable memory mapping to prevent a W^X bypass where writable file descriptors could modify executable code after mapping.

To relax this restriction, use trace/allow_unsafe_exec_memory:1 at startup. Even with this option, Syd still calls PR_SET_MDWE but sets PR_MDWE_NO_INHERIT to prevent propagation to child processes on fork(2).

The standalone tool syd-mdwe(1) applies MDWE protection to a single command without the full Syd sandbox.

Memory sandboxing limits per-process memory consumption by checking allocations on brk(2), mmap(2), mmap2(2), and mremap(2):

  • mem/max -- Maximum physical memory per process. The default action is deny, return ENOMEM ("Out of memory"); change it with default/mem, e.g. default/mem:kill to terminate with SIGKILL.
  • mem/vm_max -- Maximum virtual memory per process.

Memory use is estimated from /proc/pid/smaps_rollup summing Pss, Private_Dirty, and Shared_Dirty.

PID sandboxing limits the number of tasks by checking fork(2), vfork(2), clone(2), and clone3(2):

pid/max -- maximum concurrent tasks. The default action is kill (terminate with SIGKILL); change it with default/pid.

Best coupled with unshare/pid:1 so the count applies per PID namespace. Both memory and PID sandboxing are not alternatives to cgroups(7); use cgroups(7) when available.

Refer to syd(2) manual page for mem/ and pid/ option documentation.

Syd isolates sandboxed processes using Linux namespaces(7). Enable namespaces with unshare/ commands:

  • unshare/user:1 -- user_namespace(7).
  • unshare/mount:1 -- mount_namespaces(7).
  • unshare/pid:1 -- pid_namespaces(7).
  • unshare/net:1 -- network_namespaces(7).
  • unshare/uts:1 -- uts_namespaces(7) (hostname).
  • unshare/ipc:1 -- ipc_namespaces(7)
  • unshare/cgroup:1 -- cgroup_namespaces(7)
  • unshare/time:1 -- time_namespaces(7) (resets boot clock).

The bind command creates bind mounts inside the mount namespace. The format is bind+source:target:options where options is a comma-separated list of ro, noexec, nosuid, nodev, nosymfollow, noatime, nodiratime, and relatime. If the source is not an absolute path, it is interpreted as a filesystem type:

# Read-only bind mount of / onto itself
bind+/:/:ro
# Private tmpfs on /tmp
bind+tmpfs:/tmp:noexec,size=16M
# Cgroup filesystem
bind+cgroup2:/sys/fs/cgroup:nodev,noexec,nosuid
# Overlay mount
bind+overlay:/mnt:lowerdir=/lower,upperdir=/upper,workdir=/work

The root command changes the root mount at startup using pivot_root(2). Use root:tmpfs (or root:ramfs) to build an empty mount namespace from a private temporary filesystem mounted with nodev, noexec, nosuid, nosymfollow, noatime, and mode=700. Destination paths of bind commands are interpreted relative to the root directory.

Private proc(5) is mounted with hidepid=4 and subset=pid for process hiding.

Namespace creation by sandboxed processes is denied by default to prevent path sandboxing bypass. Use trace/allow_unsafe_namespace to selectively allow specific namespace types. Similarly, mount(2) and umount2(2) are denied unless a mount namespace is active.

Refer to syd(7) manual page for the full namespace isolation documentation and syd(2) manual page for the bind and root command reference.

SafeSetID controls UID and GID transitions. To allow a specific transition, e.g. root to nobody:

setuid+0:65534
setgid+0:65534

All setuid and setgid system calls with target UID <= 11 (typically the operator user) or GID <= 14 (typically the uucp group) are denied by a kernel-level seccomp(2) filter, even if Syd itself is compromised. After the first successful transition, Syd drops CAP_SETUID / CAP_SETGID so only one transition is permitted per Syd lifetime. Subsequent transitions in the sandbox process continue to the UID/GID that Syd transitioned to, supporting daemons like nginx(1) that spawn unprivileged workers.

Refer to syd(2) manual page for the full setuid and setgid command documentation.

PTY Sandboxing runs the target process inside a dedicated pseudoterminal managed by syd-pty(1), isolating terminal I/O from the host TTY. I/O is proxied via an edge-triggered epoll(7) loop with zero-copy splice(2). A seccomp(2) filter allows only safe PTY ioctls (e.g. TIOCGWINSZ, TIOCSWINSZ) and denies dangerous ones such as TIOCSTI (terminal input injection). landlock(7) further restricts filesystem and network access for the PTY helper.

PTY Sandboxing is enabled by default (sandbox/pty:on) but only activates when both standard input and standard output are terminals (see isatty(3)). In non-interactive contexts such as pipes or cron(8) jobs, PTY sandboxing is silently skipped. Syd is a multicall binary: it re-executes itself via proc_pid_exe(5) with argv[0] set to syd-pty to spawn the helper process. Disable PTY Sandboxing with sandbox/pty:off.

Crypt Sandboxing provides transparent file encryption using AES-CTR with HMAC-SHA256 authentication. When sandbox/crypt:on is set, files matching glob(3p) patterns specified by crypt+ are encrypted on write and decrypted on read. Configuration:

sandbox/crypt:on
crypt/key:${SYD_KEY_ID}
crypt+${HOME}/Documents/***

Encryption keys are managed via keyrings(7). Use syd-key(1) to generate keys and save them to a keyring. The environment variable holding the key serial ID must start with SYD_ to avoid leaking into the sandbox. Encryption uses zero-copy splice(2) and tee(2) to keep plaintext out of user-space memory. The syd_aes threads are confined by a strict seccomp(2) filter that denies read(2), open(2), and socket(2).

Each encrypted file has the format:

  • \x7fSYD3 -- 5-byte magic header.
  • HMAC tag -- 32-byte SHA256 authentication tag.
  • IV -- 16-byte random initialisation vector.
  • Ciphertext -- AES-CTR encrypted content.

Decryption uses memfd_create(2) with MFD_NOEXEC_SEAL (Linux >= 6.3) to prevent decrypted content from bypassing exec, force, or TPE sandboxing. For large files, set crypt/tmp to a secure backing directory.

Utilities: syd-aes(1) for standalone encrypt/decrypt, syd-key(1) for key generation. Refer to syd(7) manual page for the full Crypt Sandboxing documentation.

Proxy Sandboxing routes all network traffic through a designated SOCKS proxy. Enable it with sandbox/proxy:on, which implies unshare/net:1 to isolate the network namespace. Syd re-executes itself via proc_pid_exe(5) with argv[0] set to syd-tor to spawn the proxy helper.

sandbox/proxy:on
proxy/port:9050
proxy/ext/host:127.0.0.1
proxy/ext/port:9050

As of version 3.34.1, an external UNIX domain socket may be used instead: proxy/ext/unix:/path/socks5.sock. Traffic is proxied using zero-copy transfers and edge-triggered epoll(7).

Sandboxed processes communicate with Syd at runtime through virtual paths under /dev/syd/. The stat(2) system call on these paths delivers sandbox commands, while open(2) and read(2) retrieve sandbox state:

/* Set a sandbox command at runtime */
struct stat buf;
stat("/dev/syd/allow/read+/tmp/***", &buf);
/* Lock the sandbox */
stat("/dev/syd/lock:on", &buf);
/* Query the sandbox configuration (read-only) */
int fd = open("/dev/syd", O_RDONLY);

The interface is only available when the sandbox lock permits it. Refer to syd(2) manual page for the complete virtual path reference.

The sandbox lock modes (on, off, exec, ipc, read, drop) are described in the PATH SANDBOXING section above. Refer to syd(2) manual page for the full lock command documentation.

Ghost mode is an irreversible transition to near-seccomp strict mode. A sandboxed process enters Ghost mode by calling stat(2) on /dev/syd/ghost. Syd then closes the seccomp_unotify(2) file descriptor, elevating all previously hooked system calls to a kernel-level deny with ENOSYS ("Function not implemented"). The monitor and emulator threads exit, and the main thread simply waits for the sandbox process to terminate.

Ghost mode cannot be entered once the sandbox lock is set to on or read, but it works with lock:drop. As an alternative, setting the process dumpable attribute to zero via PR_SET_DUMPABLE(2const) achieves a similar effect because Syd can no longer access the per-process proc(5) directory.

syd-oci(1) is an OCI container runtime built on top of youki(1). It integrates Syd's sandbox into standard container workflows and is compatible with docker(1) and podman(1). Build Syd with the oci Cargo feature to obtain syd-oci.

To use with docker(1), add the runtime to /etc/docker/daemon.json:

{

"runtimes": { "syd-oci": { "path": "/bin/syd-oci" } },
"default-runtime": "syd-oci" }

Then run containers with docker run --runtime=syd-oci alpine. For podman(1), pass --runtime=/bin/syd-oci.

syd-oci(1) searches for sandbox configuration in the following order, using the first file it finds:

1.
${hostname}.${domainname}.syd-3
2.
${domainname}.syd-3
3.
${hostname}.syd-3
4.
default.syd-3
5.
The built-in oci profile.

The configuration directory is /etc/syd/oci for system-wide containers, or ${XDG_CONFIG_HOME}/syd/oci for rootless containers. Set SYD_OCI_NO_CONFIG to skip file lookup and fall through to the built-in oci profile.

The include directives in these files are resolved within the container image. This allows storing Force sandboxing checksums of executables and their dynamic libraries inside the image itself for binary verification at runtime.

Use syd-cat -p oci to view the built-in OCI profile. The profile is designed to be combined with pandora and learning mode.

Syd's -x flag enables trace mode (dry run) by applying the built-in trace profile. This profile turns off the sandbox lock, enables Force and ioctl sandboxing, and sets the default action for all sandbox categories to warn: system calls that would normally be denied are allowed, but Syd logs a detailed JSON warning for each violation. Use syd-cat -p trace to view the full list of rules in the trace profile.

pandora(1) is Syd's log inspector and profile writer. It has two subcommands:

pandora profile executes a command under Syd's trace mode, reads the violation log through an internal pipe, and writes a sandbox profile:

$ pandora profile -o app.syd-3 -- ./my-application

The -s flag passes options to Syd during init and may be repeated. Each -s value is forwarded to Syd as a single dash-prefixed argument. This maps to Syd's -m (inline config), -p (profile), and -P (config file) flags:

$ pandora profile -s mtrace/allow_unsafe_exec_memory:1 

-o app.syd-3 -- ./my-application $ pandora profile -s P./base.syd-3
-o app.syd-3 -- ./my-application $ pandora profile -s puser
-o app.syd-3 -- ./my-application

pandora inspect reads an existing Syd log and produces a profile. The input source is set with -i: a file path, - for standard input, or syslog to read from Syd's syslog(2) ring buffer via dmesg(1):

$ pandora inspect -i violations.log -o app.syd-3
$ pandora inspect -i syslog -o app.syd-3

The generated profile is a valid syd(5) configuration file. Load it with syd -P ./app.syd-3 -- ./my-application. If new violations appear under the generated profile, repeat the profiling step to refine.

See https://lib.rs/pandora_box for the project homepage.

Syd has eight log levels: emerg, alert, crit, error, warn, notice, info, and debug. The level is set with SYD_LOG or the log/level command. Logs go to standard error by default; set SYD_LOG_FD to redirect to another file descriptor (negative values disable logging).

Syd maintains its own syslog(2) ring buffer where all log messages are stored in kernel format (<LEVEL>[boottime] message). Access to /dev/kmsg and /proc/kmsg is denied with EPERM ("Operation not permitted"), so dmesg(1) falls back to the syslog(2) system call, which Syd intercepts via seccomp(2) notify and serves from its ring buffer. This enables tools such as pandora to read Syd's access violation logs from inside the sandbox using standard dmesg(1). Enable this emulation at startup with trace/allow_safe_syslog:1. The default ring buffer is stack-allocated with an architecture-dependent size that mirrors Linux CONFIG_LOG_BUF_SHIFT (256K on x86_64, 16K on aarch64, 8K on arm); set SYD_LOG_BUF_LEN to a human-readable size (e.g. 64K, 1M) to allocate a larger heap-based ring buffer.

Syd logs in JSON lines. Key fields in access violation entries:

  • id -- Sandbox ID (128 hex characters).
  • sid -- Sandbox name (human-readable).
  • ctx -- Context: access, safesetid, segvguard, etc.
  • cap -- Sandbox capability (e.g. read, write, exec).
  • act -- Sandbox action: allow, warn, deny, kill, etc.
  • sys -- System call name.
  • pid -- Process ID.
  • path -- Path argument of the system call.
  • addr -- Network address (e.g. 127.0.0.1!22).
  • cmd -- Process command line.
  • cwd -- Current working directory.
  • uid -- User ID.
  • time -- ISO 8601 timestamp (YYYYMMDDThhmmssZ).
  • tip -- Suggested sandbox command to allow the access.

Syd exits with the same code as the sandbox process. If the sandbox process is killed by a signal, Syd exits with 128 plus the signal number. If Syd itself encounters an error, it exits with the corresponding errno(3) value. Sandbox timeout produces exit code 124.

Use syd -Epfc to print Syd's seccomp(2) filters in human-readable Pseudo Filter Code (PFC). Use syd -Ebpf for raw Berkeley Packet Filter format.

Syd ships with a suite of utilities. Each utility has its own manual page. The utilities are grouped by function below.

syd(1), syd(2), syd(5), syd(7)

syd homepage: https://sydbox.exherbo.org

Maintained by Ali Polatel. Up-to-date sources can be found at https://gitlab.exherbo.org/sydbox/sydbox.git and bugs/patches can be submitted to https://gitlab.exherbo.org/groups/sydbox/-/issues. Discuss in #sydbox on Libera Chat or in #sydbox:mailstation.de on Matrix.

2026-04-11