How do I load secrets from environment variables in EK9?

← Security and Sanitization · Ref: Q702

Use EnvVars.sensitiveGet() to load secret values as Sensitive type. This is the ONLY way to create a set Sensitive value in EK9.

BASIC PATTERN

  env <- EnvVars()
  apiKey <- env.sensitiveGet("API_KEY")
  if apiKey?
    //Key is loaded and protected

GUARD PATTERN

Combine sensitiveGet() with guard expressions for clean handling:

  if dbPassword <- env.sensitiveGet("DB_PASSWORD")
    connectDatabase(dbPassword)
  else
    stderr.println("DB_PASSWORD not configured")

GET vs SENSITIVEGET

  env.get(name)            Returns String, auto-sanitized against injection
  env.sensitiveGet(name)   Returns Sensitive, auto-redacting wrapper

Use get() for configuration values (paths, URLs, feature flags).
Use sensitiveGet() for secrets (passwords, API keys, tokens).

MULTIPLE SECRETS

  env <- EnvVars()
  dbHost <- env.get("DB_HOST")
  dbUser <- env.get("DB_USER")
  dbPass <- env.sensitiveGet("DB_PASSWORD")

Configuration values use get(), only the password uses sensitiveGet().

See Q701 for the Sensitive type overview. See Q703 for Privileged reveal(). See Q704 for compile-time secret detection. See Q271 for EnvVars get() patterns.

Example

defines module qa.security.sensitiveget

  defines function

    <?-
      Basic pattern: load a secret from an environment variable.
    -?>
    testBasicSensitiveGet()
      stdout <- Stdout()
      env <- EnvVars()

      apiKey <- env.sensitiveGet("API_KEY")

      if apiKey?
        stdout.println("API key loaded (redacted): " + apiKey)
      else
        stdout.println("API_KEY not set in environment")

    <?-
      Guard pattern: combine sensitiveGet with guard expression.
    -?>
    testGuardPattern()
      stdout <- Stdout()
      stderr <- Stderr()
      env <- EnvVars()

      if dbPassword <- env.sensitiveGet("DB_PASSWORD")
        stdout.println("Database password loaded")
      else
        stderr.println("DB_PASSWORD not configured")

    <?-
      Compare get() vs sensitiveGet() for different use cases.
    -?>
    testGetVsSensitiveGet()
      stdout <- Stdout()
      env <- EnvVars()

      //get() for configuration values
      if dbHost <- env.get("DB_HOST")
        stdout.println("DB host: " + dbHost)

      //sensitiveGet() for secrets
      dbPass <- env.sensitiveGet("DB_PASSWORD")
      if dbPass?
        //Auto-promotes to ***REDACTED***
        stdout.println("DB password: " + dbPass)

    <?-
      Loading multiple secrets from environment.
    -?>
    testMultipleSecrets()
      stdout <- Stdout()
      env <- EnvVars()

      //Configuration uses get()
      region <- env.get("AWS_REGION")

      //Secrets use sensitiveGet()
      accessKey <- env.sensitiveGet("AWS_ACCESS_KEY_ID")
      secretKey <- env.sensitiveGet("AWS_SECRET_ACCESS_KEY")

      if region?
        stdout.println("Region: " + region)

      if accessKey? and secretKey?
        stdout.println("AWS credentials loaded")

    <?-
      Safe comparison of two secrets using constant-time equality.
    -?>
    testSecretComparison()
      stdout <- Stdout()
      env <- EnvVars()

      expected <- env.sensitiveGet("EXPECTED_TOKEN")
      provided <- env.sensitiveGet("PROVIDED_TOKEN")

      if expected? and provided?
        if expected == provided
          stdout.println("Tokens match")
        else
          stdout.println("Tokens do not match")

  defines program

    SensitiveGetDemo()
      stdout <- Stdout()
      stdout.println("sensitiveGet() demonstrations")
      testBasicSensitiveGet()
      testGuardPattern()
      testGetVsSensitiveGet()
      testMultipleSecrets()
      testSecretComparison()

Common mistakes

E11080 — AWS access keys must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11080 for details.

Incorrect:

accessKey <- "AKIAIOSFODNN7EXAMPLE1"

Correct:

accessKey <- env.sensitiveGet("AWS_ACCESS_KEY_ID")

E11081 — GitHub personal access tokens must not be hardcoded. Load them from environment variables using sensitiveGet(). See ek9 -h E11081 for details.

Incorrect:

expected <- "ghp_ABCDEFabcdef1234567890abcdef12345678"

Correct:

expected <- env.sensitiveGet("EXPECTED_TOKEN")

E11083 — Database URLs with embedded passwords are detected at compile time. Store the connection string in an environment variable. See ek9 -h E11083 for details.

Incorrect:

dbPass <- "postgres://admin:s3cret@db.example.com/prod"

Correct:

dbPass <- env.sensitiveGet("DB_PASSWORD")

E11084 — JWT tokens must not be hardcoded in source code. Generate tokens at runtime or load from environment variables. See ek9 -h E11084 for details.

Incorrect:

provided <- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.dozjgNryP4J3jVmNHl0w5N_XgL0n3I9PlFUP0THsR8U"

Correct:

provided <- env.sensitiveGet("PROVIDED_TOKEN")
Other ways to ask this
  • What is sensitiveGet() in EK9?
  • How do I use EnvVars to load credentials safely?
  • What is the difference between get() and sensitiveGet()?

Coming from another language?

Java: System.getenv() returns raw String with no protection class. Secrets are indistinguishable from configuration. Python: os.environ returns raw strings. Go: os.Getenv returns raw string. Rust: std::env::var returns raw string. All these allow secrets to be logged, serialized, or leaked. EK9: sensitiveGet() returns Sensitive type that auto-redacts on any string conversion.

Keywords: runtime, connect, safe, secret, database, envvars, credential, service, url, guard, load, environment, token, sensitiveGet, password, username, configuration