Lab 05 — Rust Capability Token Parser

Language: Rust 2021 | Tools: Cargo | Difficulty: 4/5

Pairs with the Phase 01 WARMUP Chapter 11 (what Rust and Go remove — and what remains). Read it alongside Lab 04 (the C parser) to feel the contrast directly.

Why This Lab Exists (Purpose & Goal)

"Rewrite it in Rust" is a popular phrase and a dangerous half-truth. Rust's borrow checker eliminates whole classes of bug — use-after-free, data races, out-of-bounds in safe code — but it does not eliminate authorization flaws, parser differentials, resource exhaustion, or the hazards of unsafe and FFI. The goal of this lab is to make that precise: you build a memory-safe parser for a capability token and discover that all the security work that remains — bounding fields, rejecting injection, enforcing tenant/expiry/replay invariants — is logic the language will never write for you.

The Concept, In the Weeds

You parse a compact, delimiter-separated capability token without any unsafe:

CAP1|tenant|subject|action|resource|expires_at|nonce

A capability is an unforgeable token of authority — "the bearer may perform action on resource within tenant until expires_at." The parser must reject:

  • delimiter injection — a field containing the | separator that smuggles an extra field;
  • oversized fields — resource exhaustion / abuse;
  • unknown actions and cross-tenant resources — authorization confusion;
  • expired tokens and duplicate nonces — replay (a valid token reused outside its window);
  • trailing fields — the exact-consumption rule again (Lab 01's parser differential, now in a text format).

The crucial design point: authorization is separate from parsing. The parser's job is to produce a well-formed, validated structure; deciding whether this subject may touch this resource is a later, explicit step. Conflating the two is how "the token parsed, so it's authorized" bugs are born. And the Rust angle: every unwrap, allocation, integer conversion, and error boundary is a place where a panic (a denial-of-service) or a logic error can still live — memory safety did not make them disappear.

Why This Matters for Protecting the Company

Capability tokens, signed cookies, JWTs, and API keys are everywhere, and they are routinely mis-validated (Phase 02). When you can write a parser that is memory-safe and enforces issuer/ tenant/expiry/replay invariants, you can protect the authorization layer of any service. The honest framing you take from this — safe languages remove memory-corruption classes, not the need for input validation and authorization — is exactly what keeps a team from a false sense of security after a Rust rewrite, and what keeps you reviewing the unsafe/FFI boundaries where the guarantees stop.

How This Is Used on the Job

This is the secure-by-construction counterpart to writing token/credential parsers in production. The unsafe-wrapper stretch task mirrors the real review you'll do at FFI boundaries (Rust↔C, the Android JNI seam in Phase 04), where you must state and uphold the invariants the safe abstraction relies on.

Run

cargo test --features solution
cargo test                     # after implementing src/lab.rs

Security Work

  • Compare Rust ownership and slice parsing with the C frame parser (Lab 04) — same invariants, very different failure modes.
  • Add property tests or a cargo-fuzz target (logic bugs survive the borrow checker).
  • Review every unwrap, allocation, conversion, and error boundary — each is a panic/DoS or logic risk.
  • Add an unsafe FFI wrapper only as a stretch task, and document every invariant a safe caller must not violate (pointer provenance, length/capacity, initialization, alignment, ownership, thread safety).

Validation — What You Should Be Able to Do Now

  • State precisely what Rust's safe subset removes and what it does not — and give an example of a fully exploitable bug (wrong tenant, replay, panic-DoS) in safe Rust.
  • Keep parsing separate from authorization, and explain why merging them creates "it parsed so it's allowed" bugs.
  • Name the invariants an unsafe/FFI wrapper must uphold for a safe caller — the exact review you do at any language boundary.

The Broader Perspective

You now hold the calibrated view of memory safety that distinguishes a senior engineer from a language partisan: safe languages are a large, real win that shifts the threat model from "memory corruption anywhere" to "logic, resources, and the unsafe/FFI seams." That precision matters when you advise a team on a rewrite, when you scope a review (focus the deepest scrutiny on unsafe/FFI), and when you reason about Go's surviving hazards (slice aliasing, goroutine races, unbounded concurrency — Phase 01 WARMUP, Chapter 11). The tool changes the shape of the risk; it never removes your job.

Interview Angle

  • "Why can a memory-safe service still be security-critical?" — Memory safety removes corruption classes, not authorization flaws, parser differentials, resource-exhaustion DoS, secret handling, or unsafe/FFI hazards. A Rust/Go service that authorizes the wrong tenant or panics on crafted input is fully exploitable.
  • "What invariants must an unsafe wrapper uphold?" — Pointer provenance, length/capacity, initialization, alignment, ownership, and thread safety, such that no safe caller can break them.

References

  • Phase 01 WARMUP Chapter 11; The Rustonomicon; Rust Unsafe Code Guidelines.
  • cargo-fuzz / proptest documentation; Phase 02 (JWT/token validation as a protocol).