C.W.K.
Stream
Lesson 03 of 05 · published

The Strict Parser

~13 min · identity-handoff, parsing, validation, security

Level 0Reel Novice
0 XP0/39 lessons0/12 achievements
0/100 XP to next level100 XP to go0% complete
"The parser is strict: scheme, host, path, field names, multiplicity, integer range, and decoded length are all validated. Unknown or repeated fields are rejected."

The Parser Is the Gate

Every deep link that reaches Ashen Reel passes through one function first. That function is the border checkpoint, and its strictness is the security. A lenient parser that shrugs at surprises is an unlocked door with a friendly sign; a strict one that rejects anything it wasn't explicitly told to accept is the wall the whole track has been building toward.

Validate Everything, Trust Nothing

Strict means every part of the URL is checked, not just the fields you care about:

  • Scheme must be exactly cwkashenreel.
  • Host and path must be exactly recall and /open — one route, not a family.
  • Field names must be drawn only from the allowed set; an unknown field is a rejection, not a shrug.
  • Multiplicity — each field appears at most once; a repeated field is rejected, so nobody can smuggle a second value past a first check.
  • Integer rangetimestamp_ms must parse as an unsigned integer, not a float, not a negative, not a word.
  • Decoded length — the id is bounded, so a megabyte of 'id' can't sail in.
Reject by default; accept only by explicit exception. A trust boundary should treat 'I didn't plan for this' as 'no,' automatically. Every input the parser doesn't recognize is an input an attacker got to design — so the safe default for the unrecognized is refusal.

Here's the checkpoint in full — notice how many lines are rejections:

Postel's Law is wrong at a trust boundary. 'Be liberal in what you accept' builds friendly protocols between cooperating peers — and soft, exploitable surfaces between an app and the open internet. At a security boundary, be conservative in what you accept: the stricter the parser, the smaller the space an attacker has to play in.

Why Repeated Fields Matter

The 'reject repeated fields' rule looks pedantic until you meet parameter pollution. Different layers can disagree about which value of a duplicated key wins — your validator checks the first timestamp_ms, some downstream reader uses the last. Attackers live in that disagreement. Ashen Reel refuses the ambiguity entirely: one field, one value, or the whole link is rejected. Strictness isn't fussiness — it's closing the gaps where two lenient readers would have disagreed.

Code

The border checkpoint — reject-by-default, one route, no repeats·swift
enum ParseResult { case ok(RecallOpenRequest); case rejected(String) }

func parseRecallOpen(_ url: URL) -> ParseResult {
    guard url.scheme == "cwkashenreel" else { return .rejected("scheme") }
    guard url.host == "recall", url.path == "/open" else { return .rejected("route") }
    guard let comps = URLComponents(url: url, resolvingAgainstBaseURL: false),
          let items = comps.queryItems else { return .rejected("no-query") }

    let allowed: Set = ["video_id", "timestamp_ms"]   // the entire vocabulary
    var seen = Set<String>()
    var videoID: String?
    var ts: UInt64 = 0
    for item in items {
        guard allowed.contains(item.name) else { return .rejected("unknown \(item.name)") }
        guard seen.insert(item.name).inserted else { return .rejected("repeated \(item.name)") }
        switch item.name {
        case "video_id":
            guard let v = item.value, (1...128).contains(v.count) else { return .rejected("id len") }
            videoID = v
        case "timestamp_ms":
            guard let v = item.value, let n = UInt64(v) else { return .rejected("ts not uint") }
            ts = n
        default: break
        }
    }
    guard let id = videoID else { return .rejected("missing video_id") }
    return .ok(RecallOpenRequest(videoID: id, timestampMs: ts))
}

External links

Exercise

Take a parser or input handler you've written that accepts structured input (query params, JSON, a command). Count how it treats an unrecognized field: does it ignore it, or reject the whole input? Then check whether it rejects a duplicated key. Rewrite it so the default for anything unplanned is refusal, and a repeated key is an error. How much smaller did the space of accepted inputs get?
Hint
Two questions expose most lenient parsers: 'what happens to a field I never defined?' and 'what happens if a field appears twice?' If the answers are 'ignored' and 'last one wins,' you have an attack surface. Strict answers — 'rejected' and 'rejected' — close it.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.