Skip to main content

Crate whycant

Crate whycant 

Source
Expand description

whycant walks each access-control layer a Unix kernel consults to explain, with evidence, why a filesystem operation is denied for a chosen identity. It reports the first real blocker and prints the exact least-privilege fix, collapsing a manual runbook (id, ls -l, namei -l, getfacl, lsattr, mount, label and audit inspection) into one command. Each layer is evaluated symbolically against a target identity (uid, primary gid, full supplementary group set), not through access(2), which answers only for the current euid.

§Layered model

Access is a fixed chain of eleven layers run in order: existence, traverse, DAC, ACL, attributes, mount flags, capabilities, MAC/LSM, macOS SIP, network filesystem, and container/userns. Each engine::Layer reads real filesystem state and evaluates it against a target identity::Identity for one op::Op. Portability lives inside each layer’s check; an inapplicable platform returns a Skip, so the chain shape, output, and exit-code mapping are identical on every OS.

§Verdict and certainty

Each layer returns a engine::LayerStatus: Pass, Block, Suspect, Skip, or Error. The runner folds these into a report::Verdict (Allowed, Blocked, Indeterminate, TargetError) carrying a report::Certainty (Proven or Suspected). Anything computable unprivileged is Proven; a denial provable only from the audit log or a server that cannot be inspected stays Suspected. The earliest proven block wins; absent that, the earliest suspected block; absent that, a lone suspect yields Indeterminate; otherwise Allowed.

§Exit codes

CodeMeaning
0allowed, no blocker
1blocked, cause proven
2blocked but indeterminate; needs privilege to confirm
3target error (ENOENT, not a file, broken symlink)
64usage error
70internal error, panics only

Codes 0 through 3 follow the verdict and certainty via report::exit_code.

§CLI usage

$ whycant read /home/alice/secret.txt
✗ BLOCKED  read /home/alice/secret.txt   /home/alice not traversable by bob

$ whycant --user www-data exec /usr/local/bin/app
$ whycant -- cat /etc/shadow
$ whycant --json traverse /srv/data

§Stability

whycant is CLI-first; this library backs the binary and its tests, and the 0.x library API is not yet semver-stable.

Modules§

access_check
Kernel cross-check via faccessat(AT_EACCESS). Runs only when the target identity equals the current euid, corroborating the symbolic R/W/X verdict and flagging any disagreement between model and kernel.
cli
Command-line surface. clap derive structs for the op, path, wrap-mode command, and flags, plus shell completion and man-page generation off the same command definition so they never drift.
config
Configuration model and precedence. Merges built-in defaults, the config file, WHYCANT_* environment variables, and CLI flags, lowest to highest. A missing file means all defaults; only a malformed file is a hard error.
engine
Ordered layer chain runner. Threads one (identity, path, op) through every Layer in fixed order, short-circuits reporting at the first proven block, folds per-layer certainty into a final verdict, and assembles the Report.
fix
Least-privilege fix synthesis, display, and application. Builds a Fix from validated slots only (never probed data), presents the interactive picker, and runs an approved fix as an argv array without a shell.
identity
Target identity resolution. Defaults to the invoking real user (SUDO_UID/GID/USER before getuid) so sudo does not silently pass DAC checks, and resolves --user to a uid, primary gid, and full supplementary group set via getgrouplist. Every layer evaluates against this, not the process.
layers
The eleven access-control layers. Each is a struct implementing crate::engine::Layer; platform-specific mechanisms live inside each layer’s check and fall through to a Skip where inapplicable.
op
Operation model. The Op a layer evaluates, the OpArg CLI keyword, the parent-directory redirection for delete/create, and op inference from a bare path or a wrapped command.
render
Renderer selection. Dispatches one Report to the human, JSON, TOML, or plain writer; every format draws from the same struct so they cannot drift.
report
Serializable report model. One Report drives every output format, so JSON, TOML, plain, and human renderings cannot drift. Also holds the verdict, certainty, layer, evidence, and fix types, and the exit-code map.
term
Terminal capability and policy. Owns color and hyperlink resolution, glyph set, width, and signal-safe teardown; no other module reads terminal env vars, calls isatty, or emits an escape directly.