Why can't I mark a captured variable as 'sanitized' in a dynamic function?
← Security and Sanitization · Ref: Q1332
The 'sanitized' modifier cannot be applied to a captured variable in a dynamic function or dynamic class (E07941). By the time a value is captured into a closure it has already crossed the trust boundary, so re-asserting sanitization there is both meaningless and misleading.
Sanitization is a property of the ENTRY POINT - the function or method parameter where untrusted external data first enters the system. Mark that parameter 'sanitized' and the compiler injects a defensive sanitizing copy. The dynamic function then captures the already-clean value with a plain capture (no modifier).
WRONG
build()
-> raw as String
fn <- (sanitized raw) is Formatter as pure function ... // E07941
RIGHT
build()
-> raw as sanitized String // sanitize at the boundary
fn <- (raw) is Formatter as pure function ... // capture the clean value
See Q950 for the full set of places 'sanitized' is restricted (captures E07941, declarations E07943).
Example
defines module qa.sanitizeddeep.captureboundary defines function <?- Abstract function contract the dynamic function will implement. -?> Formatter as pure abstract -> text as String <- output as String? <?- THE FIX: sanitize at the ENTRY POINT (the parameter), not at the capture. The compiler injects a sanitizing defensive copy for 'rawInput' here. The dynamic function then captures the already-clean value with a plain capture list (no 'sanitized' modifier - that would be E07941). -?> buildPrefixer() as pure -> rawInput as sanitized String <- made as Formatter? made: (prefix: rawInput) is Formatter as pure function output: `${prefix}: ${text}` defines program SanitizedCaptureDemo() stdout <- Stdout() formatter <- buildPrefixer("user-data") stdout.println(formatter("hello")) stdout.println("sanitize at the parameter, capture the clean value")
Common mistakes
E07941 — 'sanitized' cannot be applied to a captured variable - it belongs on the entry-point parameter where untrusted data enters. See ek9 -h E07941 for details.
Incorrect:
made: (prefix: sanitized rawInput) is Formatter as pure function
Correct:
made: (prefix: rawInput) is Formatter as pure function
Other ways to ask this
- What triggers E07941 SANITIZED_NOT_ON_CAPTURED?
- How do I sanitize a value before capturing it into a closure?
- Why is 'sanitized' rejected on a dynamic function or class capture list?
Coming from another language?
Java/Kotlin/Python: no language-level taint tracking - a closure can capture tainted data and developers must remember to sanitize manually somewhere, with no compiler check. EK9 forces sanitization to live at the single entry-point parameter and rejects it on captures at compile time (E07941), so the trust boundary stays in exactly one obvious place.
Keywords: trust, captured, boundary, security, closure, dynamic, sanitized, function, capture, E07941