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 untrusted input at the system boundary:
processRequest()
-> input as sanitized String
The compiler tracks this input through all subsequent operations.
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 guard expression 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 -> data as String <- result as String?
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 catches missing sanitization at compile time. 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 guard expressions. 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: "Empty" if content? result: "Processed: " + content // Complete defense-in-depth pipeline processRequest() // Layer 1: sanitized entry point -> input as sanitized String <- result as String: "Rejected" // 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 === invalidResult <- processRequest("not-email") if invalidResult? stdout.println("Should not reach here") else stdout.println("Bad format caught by constrained type") // === LAYERS SUMMARY === stdout.println("Defense in depth layers:") stdout.println(" 1. sanitized — marks untrusted input") 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
E50001 — Renaming the variable means later references to 'validResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.
Incorrect:
validResultXYZ <- processRequest("user@example.com")
Correct:
validResult <- processRequest("user@example.com")
E50001 — Renaming the variable means later references to 'invalidResult' become unresolved, triggering E50001. Variable names must be consistent. See ek9 -h E50001 for details.
Incorrect:
invalidResultXYZ <- processRequest("not-email")
Correct:
invalidResult <- processRequest("not-email")
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 with compile-time enforcement.
Keywords: depth, migrate, security, combine, defense, compile, protect, pipeline, multi, safe, layer, stack, chain