How does EK9 detect and report security threats?

← Security and Sanitization · Ref: Q216

EK9 detects common injection threats at RUN time, at the point of use. A function that takes a 'sanitized' parameter scans the incoming value as it is entered. There is no compile-time taint tracking or data-flow analysis.

WHAT HAPPENS ON ENTRY

The receiving function runs the input sanitizer on each 'sanitized' parameter before its body starts. It looks for SQL injection, XSS, command injection and path traversal, among other threats. A safe value becomes the function's own clean copy. A value carrying a threat is reported as a warning on stderr (naming the threat) and the parameter arrives UNSET.

A THREAT ARRIVES UNSET

An unset String is nothing, so it prints nothing and a set-check on anything built from it is false:

  outcome <- processUserQuery(request)
  if outcome?
    stdout.println(outcome)
  else
    stdout.println("Threat blocked: " + request)

EACH RECEIVER CLEANS ITS OWN PARAMETER

When one function passes a sanitized parameter on to another, the second function cleans its own parameter again. There is no tainted flag travelling through the call chain.

WHAT THE COMPILER CHECKS

Only the placement rules and one aliasing rule: 'sanitized' is for incoming String parameters (E07910, E07920) and a sanitized parameter may not be aliased into a local (E07930).

See Q215 for sanitized parameters. See Q218 for security best practices. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.

Example

defines module qa.security.threats

  defines function

    // A sanitized parameter is scanned by the receiving function as it is entered
    processUserQuery() as pure
      -> userInput as sanitized String
      <- result as String?

      // An independent copy, never an alias of the parameter (E07930)
      safeCopy <- String(userInput)
      result: "Safe query: " + safeCopy

    // The helper receives its own cleaned copy again: each receiver cleans its own parameter
    handleRequest() as pure
      -> requestData as sanitized String
      <- result as String: processUserQuery(requestData)

  defines program

    ThreatDetectionDemo()
      stdout <- Stdout()

      // === RUN-TIME DETECTION AT THE POINT OF USE ===

      requests <- ["user-provided data", "x' OR '1'='1", "<script>alert(1)</script>", "../../etc/passwd"]

      for request in requests
        outcome <- handleRequest(request)
        if outcome?
          stdout.println(outcome)
        else
          stdout.println("Threat blocked: " + request)

Common mistakes

E07930 — Assigning a sanitized parameter straight into a new local makes the local an alias of the parameter, in any function, pure or not, so the compiler reports E07930. Use the copy constructor, String(userInput), for an independent copy. See ek9 -h E07930 for details.

Incorrect:

safeCopy <- userInput

Correct:

safeCopy <- String(userInput)
Other ways to ask this
  • What security threats does EK9 detect when input is received?
  • How does EK9 prevent injection attacks?
  • What does the EK9 compiler check about sanitized parameters?

Coming from another language?

Java: OWASP ESAPI or FindBugs for taint analysis (external tools). Python: Bandit for security linting. Rust: no taint tracking, but ownership prevents some classes. Go: no built-in, gosec for analysis. EK9: the receiving function sanitizes a 'sanitized' parameter at run time and a detected SQL, XSS, command or path-traversal threat arrives unset; the compiler checks only placement and aliasing.

Keywords: command, security, detection, safe, xss, threat, protect, injection, migrate, sql, report