Is a sanitized parameter still cleaned when my function is used in a stream, passed as a callback, or reached through an operator?
← Security and Sanitization · Ref: Q1399
Yes. The function or method that DECLARES a 'sanitized' parameter cleans it itself, the moment it is entered - before any of its code runs. Every route into it therefore gets the cleaned value: a direct call, a stream stage ('map with'), a callback from a built-in type ('Optional.whenPresent'), an operator, a switch case, a dispatcher, even a program's command-line argument.
WHAT THE FUNCTION RECEIVES
safe input -> its own copy of the String (the caller's String is never shared) a threat -> an unset String, and one log line per detector on stderr
The caller cannot bypass it and does not write anything: 'describe(sanitized text)' is an error (E07942) - the declaration decides.
WHY THE RECEIVER
A callback from built-in code has no EK9 call site at all, so cleaning at call sites could never cover every route. Cleaning in the receiver covers them by construction.
OVERRIDES AND EXTERNS
An override may add 'sanitized' but never drop it (Q663). An extern declaration cannot promise it, because the compiler never generates its body (Q1400).
See Q661 for sanitized basics. See Q662 for safe copy patterns. See Q663 for overrides.
Example
defines module qa.sanitizeddeep.everyroute defines function <?- Declares its parameter sanitized, so it cleans 'text' itself on entry. Every route that reaches it - call, stream stage, callback - gets the cleaned value. -?> describe() -> text as sanitized String <- rtn as String: "rejected" if text? rtn: `accepted: ${text}` <?- A callback: built-in Optional code calls it, there is no EK9 call site. -?> showIt() -> text as sanitized String stdout <- Stdout() stdout.println(`callback set: ${text?}`) <?- Builds an injection attempt at run time from two harmless pieces. -?> composedAttack() <- rtn as String: String() opening <- "'; DROP" closing <- " TABLE users--" rtn: opening + closing defines class <?- An operator is a receiver too: its sanitized parameter is cleaned on entry. -?> Guard default Guard() operator + as pure -> text as sanitized String <- rtn as String: "rejected" if text? rtn: `accepted: ${text}` default operator ? defines program EveryRouteDemo() stdout <- Stdout() visitorNote <- "Hello from a visitor" attack <- composedAttack() stdout.println(`direct: ${describe(visitorNote)}`) stdout.println(`direct: ${describe(attack)}`) results <- cat [attack] | map with describe | collect as List of String for result in results stdout.println(`stream: ${result}`) Optional(attack).whenPresent(showIt) guard <- Guard() stdout.println(`operator: ${guard + attack}`)
Common mistakes
E07942 — The caller never writes 'sanitized' - the function declares it on its parameter and cleans the value itself on entry. See ek9 -h E07942 for details.
Incorrect:
describe(sanitized visitorNote)
Correct:
describe(visitorNote)
Other ways to ask this
- Does sanitized work when a function is passed to map or whenPresent?
- Who cleans a sanitized parameter - the caller or the function?
- Can a sanitized parameter be bypassed by calling the function indirectly?
- Does a sanitized operator parameter get cleaned?
Coming from another language?
Java/Python/Go: validation is a call the developer must remember at every entry point, and a function passed as a lambda or callback is easily called without it. EK9: the function that declares 'sanitized' cleans the parameter on entry, so no route - lambda, stream, callback or operator - can skip it.
Keywords: bypass, entry, injection, sanitized, security, whenPresent, E07942, callback, route, stream, map, operator, receiver