Inspect and Test Linux Capabilities Safely with capsh

capsh decodes the capability masks in /proc and lets you run a deliberately limited child shell to test them. These are observation and test tasks: none of the examples below change your account or the host.

You need a Linux system with the libcap2-bin package installed. The examples here were checked with libcap2-bin 1:2.66-5ubuntu2.4, which provides the installed capsh command on this machine. Allow about 15 minutes. You do not need root for the inspection examples, although some capability changes require the relevant privilege.

1. Confirm the installed command

Check which executable your shell will run and read the installed option list. capsh's interface is a sequence of options processed in the order supplied, which matters later when you build a child environment:

$ command -v capsh
/usr/sbin/capsh
$ capsh --help | sed -n '1,12p'
usage: capsh [args ...]
  --addamb=xxx   add xxx,... capabilities to ambient set
  --cap-uid=<n>  use libcap cap_setuid() to change uid
  --caps=xxx     set caps as per cap_from_text()
  --chroot=path  chroot(2) to this path
  --current      show current caps and IAB vectors
  --decode=xxx   decode a hex string to a list of caps
  --delamb=xxx   remove xxx,... capabilities from ambient
  --drop=xxx     drop xxx,... caps from bounding set
  --explain=xxx  explain what capability xxx permits
  --forkfor=<n>  fork and make child sleep for <n> sec

Your path can differ. The installed help is worth trusting over the manual page here, since the local package may expose options added after the manual was written: on this machine the manual is dated 22 October 2021, while the package is newer.

Checkpoint: command -v capsh prints a path and capsh --help exits successfully.

2. Inspect the current process state

Run --print before experimenting. It reports the effective state, bounding set, ambient set, inheritable and ambient information, securebits, user and group IDs, and libcap's guessed mode. The exact list depends on the process, kernel, container, and account that launched it:

$ capsh --print
Current: =
Bounding set =cap_chown,cap_dac_override,...
Ambient set =
Current IAB:
Securebits: 00/0x0/1'b0 (no-new-privs=0)
...
Guessed mode: HYBRID (4)

An empty Current: line is not proof that every privilege is unavailable. The bounding set is a separate ceiling, and permitted, effective, inheritable and ambient sets each answer a different question. For a shorter report, use --current:

$ capsh --current
Current: =
Current IAB:

To compare that against the kernel's raw view, read the capability fields for a process directly. Also read-only:

$ grep -E '^(Cap(Inh|Prm|Eff|Bnd|Amb)|NoNewPrivs):' /proc/self/status
CapInh:  0000000000000000
CapPrm:  0000000000000000
CapEff:  0000000000000000
CapBnd:  0000003fffffffff
CapAmb:  0000000000000000
NoNewPrivs:  0

Those values are only an example of the shape. A container, service manager, login shell or set of file capabilities can all produce different masks, so check the actual output on the process you are investigating.

3. Decode a capability mask

When a status field holds a hexadecimal vector, pass it to --decode. It turns the low bits into capability names without changing the process:

$ capsh --decode=3
0x0000000000000003=cap_chown,cap_dac_override

3 here is only a small demonstration: copy the complete hex value from the field you actually care about, and decode each field separately, since CapEff answers a different question from CapBnd or CapAmb.

Checkpoint: capsh --decode=3 reports cap_chown and cap_dac_override, and makes no persistent change.

4. Check support and explain a capability

Use --supports for a script-friendly feature check. It exits zero when the named capability is known to the running kernel, and one when it is not. It does not grant the capability:

$ capsh --supports=cap_net_bind_service
$ printf 'exit=%s\n' "$?"
exit=0
$ capsh --explain=cap_net_bind_service
cap_net_bind_service (10) [/proc/self/status:CapXXX: 0x0000000000000400]

    Allows a process to bind to privileged ports:
      - TCP/UDP sockets below 1024
      - ATM VCIs below 32

Support is not possession. A successful --supports check says the capability exists as a concept on this kernel, not that your process has it in its permitted or effective set. Use --has-p=cap_net_bind_service, --has-a=cap_net_bind_service, or the matching --print output to test a particular set. These use exit status, so a shell script needs to check $? or use them directly in an if.

5. Launch a read-only child shell

-- ends capsh's own options and hands the rest to /bin/bash. Use -c to run one command and exit, so you never leave an experimental shell open by accident:

$ capsh -- -c 'printf "uid=%s\\n" "$UID"; id -u; capsh --current'
uid=1000
1000
Current: =
Current IAB:

The child prints its identity and capability state, then exits, using your current account with no sudo needed. For a different shell executable, the manual provides --shell=/full/path, but only use that with a path you have checked yourself.

Warning: Do not casually add --user, --uid, --gid, --caps, --inh, or --addamb to a copied command. These alter the child's identity or capability state, some need privileges, and a process can stay privileged in a permitted set after its effective capabilities are cleared. If the child is meant as a security boundary, validate the whole resulting state and the program it runs, not just one line of output.

6. Treat state-changing options as security tests

--drop=capability removes capabilities from the process's bounding set. That reduction is not restored on exit, but normally affects only that process and its descendants. --noamb drops all ambient capabilities for the process. --chroot=/path changes the child's root and needs CAP_SYS_CHROOT: it is not a general container or privilege-isolation mechanism.

Warning: Options that change UID, GID, securebits, capability sets, ambient capabilities, the bounding set, or the root directory are security-sensitive. Test them in a disposable process or isolated environment first. Never paste a command containing a placeholder such as /path/to/root into a production shell, and never reach for sudo just to make an example succeed. There is no general undo for a dropped bounding-set capability inside the affected process: start a fresh process instead.

For a script, prefer a small assertion followed by a controlled exec. This checks the capability exists, then runs something harmless:

$ if capsh --supports=cap_net_bind_service; then
>   printf '%s\n' 'capability name is supported'
> else
>   printf '%s\n' 'capability name is not supported' >&2
>   exit 1
> fi
capability name is supported

That example deliberately stops at discovery. Granting or arranging a capability belongs in the service or deployment configuration that owns the process, with its own review of why it is needed.

Done means