What is the Sensitive type in EK9?

← Security and Sanitization · Ref: Q701

The Sensitive type is a built-in type that wraps secret values with automatic protection against accidental exposure.

AUTO-REDACTION

The $ and $$ operators on a set Sensitive always return '***REDACTED***'. This means secrets cannot leak through:

  stdout.println($apiKey)       Shows ***REDACTED***
  msg <- `Key: ${apiKey}`       Interpolates as ***REDACTED***
  jsonField: $apiKey             Serializes as ***REDACTED***

CONTROLLED CONSTRUCTION

The only way to create a set Sensitive is EnvVars.sensitiveGet():

  env <- EnvVars()
  secret <- env.sensitiveGet("API_KEY")

There is no String constructor visible to EK9 code. Sensitive() creates an unset value.

CONSTANT-TIME EQUALITY

The == and <> operators use MessageDigest.isEqual() internally to prevent timing attacks on secret comparison.

COPY SUPPORT

The :=: copy operator transfers secret values between Sensitive variables.

See Q702 for sensitiveGet() patterns. See Q703 for the Privileged trait and reveal(). See Q704 for compile-time secret detection. See Q218 for security best practices. See Q272 for defense in depth.

Example

defines module qa.security.sensitivetype

  defines class

    <?-
      A class without Privileged cannot call reveal().
      It can receive, store, compare, and redact Sensitive values.
    -?>
    SecretLogger

      logRedacted()
        -> credential as Sensitive

        stdout <- Stdout()
        if credential?
          //$ always returns "***REDACTED***" - safe to log
          stdout.println("Credential present: " + $credential)
        else
          stdout.println("No credential provided")

      default operator ?

  defines function

    testDefaultConstructor()
      stdout <- Stdout()

      //Default constructor creates an unset Sensitive
      secret <- Sensitive()

      if not secret?
        stdout.println("Default Sensitive is unset")

    testSensitiveGet()
      stdout <- Stdout()
      env <- EnvVars()

      //sensitiveGet() is the ONLY way to create a set Sensitive
      apiKey <- env.sensitiveGet("API_KEY")

      if apiKey?
        //Promotes to ***REDACTED*** automatically
        stdout.println("Got key (redacted): " + apiKey)
      else
        stdout.println("API_KEY not configured")

    testRedactionInInterpolation()
      stdout <- Stdout()
      env <- EnvVars()

      dbPassword <- env.sensitiveGet("DB_PASSWORD")

      if dbPassword?
        //Interpolation auto-promotes to ***REDACTED***
        msg <- `Database password: ${dbPassword}`
        stdout.println(msg)

    testConstantTimeEquality()
      stdout <- Stdout()
      env <- EnvVars()

      key1 <- env.sensitiveGet("TEST_KEY")
      key2 <- env.sensitiveGet("TEST_KEY")

      if key1? and key2?
        if key1 == key2
          stdout.println("Keys match (constant-time comparison)")

    testCopyOperator()
      stdout <- Stdout()
      env <- EnvVars()

      original <- env.sensitiveGet("SECRET_TOKEN")
      backup <- Sensitive()

      if original?
        backup :=: original
        if backup?
          stdout.println("Secret copied to backup")

    testPassToLogger()
      env <- EnvVars()
      secret <- env.sensitiveGet("API_KEY")
      logger <- SecretLogger()
      if logger?
        logger.logRedacted(secret)

  defines program

    SensitiveTypeDemo()
      stdout <- Stdout()
      stdout.println("Sensitive type demonstrations")
      testDefaultConstructor()
      testSensitiveGet()
      testRedactionInInterpolation()
      testConstantTimeEquality()
      testCopyOperator()
      testPassToLogger()

Common mistakes

E50060 — Sensitive has no String constructor visible to EK9 code. The only way to create a set Sensitive is via EnvVars.sensitiveGet(). See ek9 -h E50060 for details.

Incorrect:

secret <- Sensitive("my-api-key")

Correct:

secret <- Sensitive()

E08090 — Hardcoded cloud provider credentials are detected at compile time. Use EnvVars.sensitiveGet() to load secrets from environment variables instead. See ek9 -h E08090 for details.

Incorrect:

apiKey <- "AKIAIOSFODNN7EXAMPLE1"

Correct:

apiKey <- env.sensitiveGet("API_KEY")

E50060 — Hardcoded API keys are detected at compile time. Load API keys from environment variables using EnvVars.sensitiveGet(). See ek9 -h E50060 for details.

Incorrect:

original <- "sk_test_abcdefghijklmnopqrstuvwxyz"

Correct:

original <- env.sensitiveGet("SECRET_TOKEN")

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

Incorrect:

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

Correct:

dbPassword <- env.sensitiveGet("DB_PASSWORD")
Other ways to ask this
  • How does EK9 protect secret values at runtime?
  • How do I prevent secrets from being logged in EK9?
  • What is EK9's equivalent of a secret wrapper?

Coming from another language?

Java: No language-level secret type. Secrets stored as plain Strings can be logged, serialized, or leaked through any code path. Libraries like Vault provide runtime wrappers. Python: No secret type; plain strings with no redaction. Rust: secrecy crate provides Secret<T> but any code can call expose_secret() with no restriction. Go: No secret type; plain strings. EK9: Built-in Sensitive type with automatic redaction, no String constructor, constant-time equality, and trait-gated reveal().

Keywords: type, password, log, leak, protect, credential, wrap, safe, token, secret, sensitive, runtime, redact