How does purity create security boundaries?

← Security and Sanitization · Ref: Q273

EK9 pragmatic purity is focused on preventing mutation-based attacks and data corruption. It is not Haskell-style 'no side effects' but a practical security boundary.

WHAT PURE PREVENTS

Mutation operators are forbidden in pure functions (E08120): +=, -=, *=, /=, :=:, :~: and :^:. These modify the left-hand side in place and could affect the right-hand side as a side effect. Calling user-defined impure functions is also blocked.

WHAT PURE ALLOWS

Reassignment with : (creates new value):

  result: result * i

Loop variables naturally change:

  for i in 1 ... n

Return variables are reassigned, not mutated. ALL I/O is allowed because I/O types carry the IO marker trait. Stdout, Stderr, Stdin, EnvVars, TextFile, TCP, and UDP are all usable in pure functions.

DATA INTEGRITY

The key insight is that + creates a new value while += mutates in place. In pure functions, only + is allowed. This means parameters passed to a pure function cannot be modified as a side effect:

  pureProcess() as pure
    -> data as String
    result <- "Prefix: " + data
    // 'data' is guaranteed untouched

SANITIZED PARAMETERS IN PURE FUNCTIONS

A sanitized parameter is cleaned by the receiving function on entry. It cannot be aliased into a local (E07930), in a pure function or any other, and a pure function cannot mutate it either. Copy it with the copy constructor, or use it directly:

  secureProcess() as pure
    -> input as sanitized String
    <- result as String?
    localCopy <- String(input)
    result: localCopy

Purity only adds the ban on mutation operators; the no-alias rule is not specific to pure functions.

CONTROLLED CALL CHAINS

Pure functions can only call other pure functions. This creates a trust chain: if the entry point is pure, every function in the chain is pure. No impure operation can hide in the middle.

TOCTOU PREVENTION

Time-of-check-time-of-use attacks require mutating shared state between the check and the use. Since mutation operators are blocked in pure functions, the checked value cannot change before it is used.

CONSUMER vs ACCEPTOR
Consumer of T is pure (read-only access). Acceptor of T is impure (can mutate). This distinction lets APIs express whether a callback can modify data:

  process() as pure
    -> handler as Consumer of String
    handler("data")
    // handler CANNOT modify anything

See Q54 for pure functions and Consumer/Acceptor. See Q217 for sanitized parameter mechanics in pure context. See Q218 for security best practices. See Q241 for mutation operators. See Q272 for defense in depth. See Q560 for purity contracts and enforcement rules. See Q635 for forbidden mutation operators in pure methods.

Example

defines module qa.security.puritysecurity

  defines function

    // Pure function with I/O — allowed
    pureWithIo() as pure
      -> message as String
      stdout <- Stdout()
      stdout.println("Pure I/O: " + message)

    // Pure function — reassignment, not mutation
    pureCalculate() as pure
      -> n as Integer
      <- result as Integer: 1

      for i in 1 ... n
        // reassignment with : makes a new value
        // result *= i — would be BLOCKED (mutation operator)
        result: result * i

    // Pure + sanitized = forced copy pattern
    pureSecureProcess() as pure
      -> input as sanitized String
      <- result as String?

      // An independent copy; 'localCopy <- input' would be E07930 in any function
      localCopy <- String(input)
      result: "Secure: " + localCopy

    // Consumer is pure — read only
    pureConsumerExample() as pure
      -> handler as Consumer of String
      handler("data")

  defines program

    PuritySecurityDemo()
      stdout <- Stdout()

      // === PURE WITH I/O ===

      pureWithIo("I/O is allowed in pure functions")

      // === PURE CALCULATION ===

      factorial <- pureCalculate(5)
      stdout.println("5! = " + $factorial)

      // === PURE + SANITIZED ===

      secureResult <- pureSecureProcess("external input")
      if secureResult?
        stdout.println(secureResult)

      // === CONSUMER vs ACCEPTOR ===

      stdout.println("Consumer of T: pure, read-only")
      stdout.println("Acceptor of T: impure, can mutate")

      // === SECURITY BENEFITS ===

      stdout.println("Purity security benefits:")
      stdout.println("  No mutation operators in pure")
      stdout.println("  Parameters cannot be modified as side effect")
      stdout.println("  A sanitized parameter is never aliased (E07930)")
      stdout.println("  Pure call chains create trust boundaries")
      stdout.println("  TOCTOU prevented by no shared mutation")

Common mistakes

E08120 — In a pure function, the mutation operator *= is forbidden. Use reassignment with : to create a new value (result: result * i). See ek9 -h E08120 for details.

Incorrect:

result *= i

Correct:

result: result * i

E50001 — A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. See ek9 -h E50001 for details.

Incorrect:

factorial <- pureCalculat(5)

Correct:

factorial <- pureCalculate(5)

E50001 — A misspelt function name cannot be resolved, so E50001 is reported on that line. Spell the declared function name exactly. See ek9 -h E50001 for details.

Incorrect:

secureResult <- pureSecureProces("external input")

Correct:

secureResult <- pureSecureProcess("external input")
Other ways to ask this
  • Why does purity matter for security in EK9?
  • How does EK9 pragmatic purity prevent security bugs?
  • How do pure functions protect against data corruption?

Coming from another language?

Java: no purity concept, any method can mutate any reachable object. Python: no purity enforcement, convention only. Haskell: strict purity but no I/O in pure (uses IO monad). Rust: immutable borrows prevent mutation but no purity concept. Go: no purity. Kotlin: no purity enforcement. EK9: pragmatic purity blocks mutation operators while allowing I/O, creating security boundaries that prevent data corruption and TOCTOU attacks.

Keywords: security, purity, immutable, operator, side-effect, mutation, data, migrate, integrity, safe, protect, toctou, pragmatic, pure, audit, define