How do I handle secrets and environment configuration?

← Security and Sanitization · Ref: Q271

EK9 handles secrets through the EnvVars type, which provides auto-sanitized access to environment variables. This aligns with the 12-factor app methodology and container practices.

ENVVARS API

The EnvVars type provides:

  env <- EnvVars()
  env.get(name)            Auto-sanitized: blocks SQL, XSS, command injection but allows system paths
  env.unsanitizedGet(name) Raw value with no sanitization
  env contains name        Check if variable exists
  env.keys()               Iterate all variable names

AUTO-SANITIZATION

The get() method runs InputSanitizer automatically. This blocks injection patterns in environment variable values while allowing legitimate system paths like /usr/bin. Use unsanitizedGet() only when you specifically need raw values.

GUARD PATTERN

Combine EnvVars with declaration guards for safe access:

  env <- EnvVars()
  if apiKey <- env.get("API_KEY")
    processWithKey(apiKey)
  else
    stderr.println("API_KEY not configured")

The guard handles both missing variables and sanitization failures in one line.

SANITIZED FORWARDING

A function that takes a sanitized parameter cleans its own copy on entry, so a value carrying an injection threat arrives unset:

  connectToService()
    -> apiKey as sanitized String
    <- result as String: apiKey

This is run-time threat cleaning by the receiving function. It does not stop a value being logged and it tracks nothing through the call chain.

KEEPING SECRETS OUT OF LOGS

For a real credential use env.sensitiveGet(name), which returns a Sensitive. Its '$' always gives ***REDACTED*** and reveal() is gated by the Privileged trait. See Q701.

WHAT NOT TO DO

Never embed secrets in source code. Never log secret values (keep them in a Sensitive, whose '$' is redacted). Never store secrets in the .ek9 build directory. Never use unsanitizedGet without a specific reason.

INDUSTRY ALIGNMENT

Environment variables are the industry standard for secrets. Docker and Kubernetes inject secrets as env vars. Vault and AWS Secrets Manager ultimately deliver values through env vars. The 12-factor app methodology specifies env vars for configuration. EK9's EnvVars type with auto-sanitization goes beyond what most languages provide.

See Q215 for sanitized parameters. See Q29 for unset variables and guard patterns. See Q74 for declaration guards. See Q268 for OWASP vulnerability prevention. See Q272 for defense in depth.

Example

defines module qa.security.secrets

  defines function

    // Accept sanitized secret for processing
    connectToService() as pure
      -> apiKey as sanitized String
      <- result as String: "No key provided"

      // Copy constructor for defensive copy
      safeKey <- String(apiKey)
      if safeKey?
        result: "Connected with key length: " + $length safeKey

  defines program

    SecretsAndEnvDemo()
      stdout <- Stdout()
      stderr <- Stderr()

      // === READ ENVIRONMENT VARIABLES ===

      env <- EnvVars()

      // Auto-sanitized access with guard
      if path <- env.get("PATH")
        stdout.println("PATH available, length: " + $length path)
      else
        stdout.println("PATH not available")

      // === CHECK EXISTENCE ===

      if env contains "HOME"
        stdout.println("HOME is configured")

      // === GUARD PATTERN FOR SECRETS ===

      if apiKey <- env.get("API_KEY")
        // connectToService cleans its own sanitized parameter on entry
        result <- connectToService(apiKey)
        if result?
          stdout.println(result)
      else
        stderr.println("API_KEY not configured")

      // === ITERATE AVAILABLE KEYS ===

      stdout.println("Environment variable access patterns:")
      stdout.println("  get() — auto-sanitized")
      stdout.println("  unsanitizedGet() — raw value")
      stdout.println("  contains — check existence")
      stdout.println("  keys() — iterate names")

Common mistakes

E50001 — The type is EnvVars. A misspelt type name cannot be resolved, so E50001 is reported on that line. See ek9 -h E50001 for details.

Incorrect:

env <- EnvVar()

Correct:

env <- EnvVars()

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:

result <- connectToServic(apiKey)

Correct:

result <- connectToService(apiKey)
Other ways to ask this
  • How do I read environment variables in EK9?
  • How does EK9 handle API keys and credentials?
  • What is the best way to manage secrets in EK9?

Coming from another language?

Java: System.getenv() returns raw String, no sanitization. Python: os.environ with no safety. Go: os.Getenv returns raw string. Rust: std::env::var returns raw string. Kotlin: System.getenv() with no sanitization. EK9: EnvVars.get() auto-sanitizes blocking injection patterns, unsanitizedGet() for raw access, guard pattern for missing variables.

Keywords: api, password, config, protect, secret, sensitive, variable, env, key, safe, environment, security, token, credential