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
| Code | Meaning |
|---|---|
| 0 | allowed, no blocker |
| 1 | blocked, cause proven |
| 2 | blocked but indeterminate; needs privilege to confirm |
| 3 | target error (ENOENT, not a file, broken symlink) |
| 64 | usage error |
| 70 | internal 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 everyLayerin fixed order, short-circuits reporting at the first proven block, folds per-layer certainty into a final verdict, and assembles theReport. - fix
- Least-privilege fix synthesis, display, and application. Builds a
Fixfrom 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
--userto 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
Opa layer evaluates, theOpArgCLI keyword, the parent-directory redirection for delete/create, and op inference from a bare path or a wrapped command. - render
- Renderer selection. Dispatches one
Reportto the human, JSON, TOML, or plain writer; every format draws from the same struct so they cannot drift. - report
- Serializable report model. One
Reportdrives 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.