How do I build defense in depth in EK9?
← Security and Sanitization · Ref: Q272
Defense in depth means applying multiple security layers so that if one layer fails, the next catches the problem. EK9's security features compose naturally into a layered pipeline.
LAYER 1: SANITIZED ENTRY POINT
Mark the parameter that receives untrusted input at the system boundary:
processRequest()
-> input as sanitized String
<- result as String: input
The RECEIVING function cleans the parameter the moment it is entered, at run time, on every route into it: a detected threat (an injection, a script) arrives as an UNSET String and is logged. The compiler guarantees the cleaning step exists - the author cannot forget it - but it does not track the value afterwards.
LAYER 2: STRUCTURAL VALIDATION
Use a constrained type to validate the format:
ValidEmail as String constrain as matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/
Validate untrusted input with the fallible factory ValidEmail().of(value): it produces an unset object on a constraint failure (a bare ValidEmail(value) instead ASSERTS validity and Panics on a set invalid value).
LAYER 3: GUARD CHECK
Use a declaration guard to verify validation succeeded:
safeCopy <- String(input) if email <- ValidEmail().of(safeCopy) // Only proceeds with structurally valid data
LAYER 4: BUSINESS VALIDATION
Use require for domain-specific rules:
require length $email > 5 require $email contains "@company.com"
Failed require throws, preventing invalid business data.
LAYER 5: PURE PROCESSING
Process validated data in a pure function:
pureTransform() as pure -> content as String <- result as String: "Processed: " + content
Purity prevents side effects and mutation during processing.
LAYER 6: OUTPUT GUARD
Guard the output before use:
if result <- pureTransform(cleanData) stdout.println(result)
Ensures processing produced a valid result.
Each layer catches a different failure mode: Layer 1 neutralises injection and script payloads on entry. Layer 2 catches malformed input. Layer 3 prevents processing unset values. Layer 4 enforces business rules. Layer 5 prevents side effects. Layer 6 catches processing failures.
See Q54 for pure functions. See Q74 for declaration guards. See Q215 for sanitized parameters. See Q218 for security best practices. See Q257 for constrained types. See Q269 for input validation. See Q273 for purity as security boundary.
Example
defines module qa.security.defenseindepth defines type // Layer 2: Structural validation ValidEmail as String constrain as matches /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/ defines function // Layer 5: Pure processing pureTransform() as pure -> content as String <- result as String: String() if content? result: "Processed: " + content // Complete defense-in-depth pipeline processRequest() // Layer 1: sanitized entry point - cleaned on entry, at run time -> input as sanitized String <- result as String: String() // Layer 2+3: Copy, validate via the fallible factory, and guard safeCopy <- String(input) if email <- ValidEmail().of(safeCopy) // Layer 4: Business validation require length $email > 5 // Layer 5: Pure processing if processed <- pureTransform($email) // Layer 6: Output guard result: processed defines program DefenseInDepthDemo() stdout <- Stdout() // === FULL PIPELINE === validResult <- processRequest("user@example.com") if validResult? stdout.println(validResult) // === LAYER 2 CATCHES BAD FORMAT === //A rejected request leaves the result unset invalidResult <- processRequest("not-email") if not invalidResult? stdout.println("Bad format caught by constrained type") // === LAYERS SUMMARY === stdout.println("Defense in depth layers:") stdout.println(" 1. sanitized — cleans input on entry") stdout.println(" 2. Constrained type — validates format") stdout.println(" 3. Guard — checks construction") stdout.println(" 4. require — business rules") stdout.println(" 5. Pure — no side effects") stdout.println(" 6. Output guard — validates result")
Common mistakes
E08120 — The processing layer is pure, and a pure scope may not use mutation operators such as '+=' - not even on its return variable. Assign the newly computed value with ':' instead. See ek9 -h E08120 for details.
Incorrect:
result += "Processed: " + content
Correct:
result: "Processed: " + content
Other ways to ask this
- How do EK9 security features work together?
- How do I create a layered security pipeline in EK9?
- How do I combine sanitized, constrained, require, and pure in EK9?
Coming from another language?
Java: defense in depth requires manual layering of OWASP ESAPI, Bean Validation, Spring Security. Python: no compile-time enforcement, runtime-only layers. Rust: ownership model provides some layers but no taint tracking. Go: manual error checking at each layer. Kotlin: null safety as one layer, but no sanitization or purity. EK9: sanitized, constrained types, guards, require, and pure compose into a natural defense-in-depth pipeline; the compiler guarantees each layer is present, and the sanitizing and constraint checks run on every call.
Keywords: pipeline, protect, compile, defense, combine, security, migrate, depth, chain, stack, layer, safe, multi