agentleFS
narenaryannarenaryan/agent-skills/skills/rust/rust-runtime-bug-classes/SKILL.md

rust-runtime-bug-classes

Skill0 starsChanged 39 days agoMITView on GitHub

Use when writing or reviewing Rust code that touches the filesystem, processes untrusted input, reimplements a Unix CLI, crosses a trust boundary like chroot, or handles bytes that may not be UTF-8 — covers TOCTOU on paths, permission races, OsStr vs String, panic-on-input, silently-discarded errors, NSS/dlopen after chroot, and bug-for-bug compatibility with GNU tools

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 0 are distinctive, and 2 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈0

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 2

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Use ?, .get(i), checked_add, try_from for expected failures.

    line 41
  2. Very specific:

    When rewriting coreutils, find, grep, etc., scripts depend on bug-for-bug behavior: exit codes, stderr wording, signal-name parsing (kill …

    line 66

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Bugs Rust's Type System Won't Catch

Rust prevents memory unsafety and data races, not logic bugs that live in syscalls, encoding, error propagation, or trust boundaries. These are the recurring classes.

Filesystem race & identity bugs

BugWrongRight
TOCTOU between remove_file + createfs::remove_file(p)?; File::create(p)?OpenOptions::new().create_new(true).open(p) — atomic, refuses dangling symlinks
Default perms then chmodcreate_dir(p)?; set_permissions(p, 0o700)?DirBuilderExt::mode(0o700) / OpenOptions::mode() at creation; set umask explicitly
Path equality via stringfile == Path::new("/")fs::canonicalize(file)?; for identity, compare (dev, inode) from MetadataExt
Path operations on tainted inputresolving paths repeatedlyAnchor on a file descriptor (e.g. openat, cap-std crate) instead of re-resolving paths

OsStr / bytes vs String

Filesystem paths and process args are not guaranteed UTF-8 on Unix.

  • String::from_utf8_lossy corrupts binary streams by replacing bytes with U+FFFD.
  • print!("{}", ...) panics on broken pipe and forces UTF-8.
  • Stay in bytes: OsStr, OsString, Path, PathBuf, Vec<u8>, &[u8]. Use io::stdout().write_all(&bytes) for raw output.

Panic-on-input is a DoS

unwrap(), expect(), and indexing can panic when input violates their preconditions. Integer +/- overflow can panic with checks enabled or wrap with checks disabled; never rely on build-profile behavior to reject invalid input. For input-facing crates, configure optional Clippy restrictions in Cargo.toml:

# Cargo.toml (not clippy.toml)
[lints.clippy]
unwrap_used = "warn"
expect_used = "warn"
panic = "warn"
indexing_slicing = "warn"
arithmetic_side_effects = "warn"

Use ?, .get(i), checked_add, try_from for expected failures. A justified expect may document an internal invariant whose violation is a bug; explain why input cannot invalidate it. Gallant's panic-boundary discussion distinguishes those bugs from recoverable input errors. Rust's overflow rules remain configuration-sensitive.

Silently discarded errors

file.set_len(size).ok();          // hides ENOSPC
let _ = writeln!(out, "...");     // hides broken pipe
fs::remove_file(p).unwrap_or_default();

Propagate. If ignoring is intentional, leave a comment naming the exact errno that's safe. In tools that iterate (e.g. rm -r), track the worst exit code and return it at the end rather than aborting on first failure or losing failures silently.

Trust boundaries: chroot, setuid, sandboxing

NSS (getpwnam, getgrnam, DNS) and other glibc paths dlopen shared libs at call time. After chroot, those loads come from the new root.

let uid = users::get_user_by_name(name);  // resolve BEFORE
chroot(new_root)?;                         // ...then cross the boundary

Static linking the Rust binary does not prevent glibc from dlopen'ing NSS modules. Resolve users, groups, hostnames, and load any plugins before chroot/setuid/seccomp.

Reimplementing GNU/BSD tools

When rewriting coreutils, find, grep, etc., scripts depend on bug-for-bug behavior: exit codes, stderr wording, signal-name parsing (kill -1 vs kill -SIGHUP), locale-sensitive sort, glob edge cases. Run the upstream test suite in CI; treat behavioral divergence as a bug, not a feature.

Pitfalls

  • create_new(true) returns AlreadyExists on dangling symlinks — handle it; don't fall back to create() which re-introduces the race.
  • canonicalize requires the path to exist; for not-yet-created files, canonicalize the parent and join the basename.
  • OsStr has no len_chars or contains(&str) — converting to &str to search reintroduces the UTF-8 assumption you were avoiding.
  • Clippy arithmetic_side_effects flags every + including loop counters; scope it to crates handling untrusted input, not workspace-wide.
  • chroot alone is not a sandbox; combine with setuid to a non-root user and ideally seccomp/landlock.

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.