How do I access the raw value of a Sensitive in EK9?

← Security and Sanitization · Ref: Q703

The reveal() method on Sensitive returns the raw secret value as a String. It is gated by the Privileged trait — only classes with 'with trait of Privileged' can call reveal().

PRIVILEGED TRAIT

The Privileged trait is a built-in marker trait with no methods. It acts as a compile-time gate:

  HttpClient with trait of Privileged
    sendRequest()
      -> apiKey as Sensitive
      header <- apiKey.reveal()

WHY GATED?

Most code should never need the raw secret. Sensitive values should flow through the system redacted. Only infrastructure code (HTTP clients, database drivers, encryption) needs the actual value. The Privileged trait makes this boundary explicit and auditable.

WHO CANNOT CALL REVEAL?

  - Classes without the Privileged trait → E11090
  - Functions (cannot have traits) → E11090
  - Dynamic classes without Privileged → E11090

WHO CAN CALL REVEAL?

  - Regular classes with 'with trait of Privileged'
  - Named dynamic classes with 'trait of Privileged'
  - Unnamed dynamic classes with 'trait of Privileged'

AUDIT TRAIL

Searching the codebase for 'Privileged' identifies every point where secrets can be exposed. This makes security audits trivial compared to languages where any code can access secret values.

See Q701 for the Sensitive type overview. See Q702 for sensitiveGet() patterns. See Q704 for compile-time secret detection.

Example

defines module qa.security.privilegedreveal

  defines trait

    SecretConsumer
      consumeSecret() as abstract
        -> secret as Sensitive

  defines class

    <?-
      A class WITH Privileged can call reveal() to access raw secret value.
      Only infrastructure code like HTTP clients should do this.
    -?>
    HttpClient with trait of Privileged

      sendAuthenticatedRequest()
        -> token as Sensitive

        rawToken <- token.reveal()
        stdout <- Stdout()
        if rawToken?
          header <- `Bearer ${rawToken}`
          stdout.println(`Auth header set: ${length header} chars`)

    <?-
      A class WITHOUT Privileged can safely handle Sensitive values
      using redaction, comparison, and copy - but NOT reveal().
    -?>
    SafeLogger

      logActivity()
        -> credential as Sensitive

        stdout <- Stdout()
        if credential?
          //$ always returns ***REDACTED*** - safe to log
          stdout.println("Activity with credential: " + $credential)
        else
          stdout.println("No credential for activity")

      default operator ?

  defines function

    <?-
      Named dynamic class with Privileged can call reveal().
    -?>
    testNamedDynamicReveal()
      env <- EnvVars()
      secret <- env.sensitiveGet("DB_PASSWORD")

      extractor <- PasswordExtractor(secret) trait of Privileged as class
        getPassword()
          <- pwd as String?
          pwd: secret.reveal()
        default operator ?

      if extractor?
        result <- extractor.getPassword()
        stdout <- Stdout()
        if result?
          stdout.println(`Password extracted: ${length result} chars`)

    <?-
      Test that non-Privileged code can still work with Sensitive values.
    -?>
    testSafeOperations()
      stdout <- Stdout()
      env <- EnvVars()

      secret <- env.sensitiveGet("API_KEY")

      //All safe operations - no Privileged needed
      if secret?
        //Auto-promotes to ***REDACTED*** in string context
        stdout.println("Redacted: " + secret)

      backup <- Sensitive()
      if secret?
        backup :=: secret
        if backup?
          stdout.println("Backup created successfully")

    testPrivilegedReveal()
      env <- EnvVars()
      token <- env.sensitiveGet("AUTH_TOKEN")
      client <- HttpClient()
      if client? and token?
        client.sendAuthenticatedRequest(token)

    testSafeLogging()
      env <- EnvVars()
      credential <- env.sensitiveGet("SERVICE_KEY")
      logger <- SafeLogger()
      if logger?
        logger.logActivity(credential)

  defines program

    PrivilegedRevealDemo()
      stdout <- Stdout()
      stdout.println("Privileged trait and reveal() demonstrations")
      testNamedDynamicReveal()
      testSafeOperations()
      testPrivilegedReveal()
      testSafeLogging()

Common mistakes

E11090 — Only classes with the Privileged trait can call reveal() on Sensitive values. Removing 'with trait of Privileged' causes the reveal() call inside sendAuthenticatedRequest() to fail. See ek9 -h E11090 for details.

Incorrect:

HttpClient

Correct:

HttpClient with trait of Privileged

E11090 — Dynamic classes also need the Privileged trait to call reveal(). Removing 'trait of Privileged' causes the reveal() call inside getPassword() to fail. See ek9 -h E11090 for details.

Incorrect:

PasswordExtractor(secret) as class

Correct:

PasswordExtractor(secret) trait of Privileged as class

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

Incorrect:

secret <- "sk_test_abcdefghijklmnopqrstuvwxyz"

Correct:

secret <- env.sensitiveGet("API_KEY")

E50060 — Private key material must not be hardcoded in source code. Load private keys from environment variables or files at runtime. See ek9 -h E50060 for details.

Incorrect:

token <- "-----BEGIN RSA PRIVATE KEY-----"

Correct:

token <- env.sensitiveGet("AUTH_TOKEN")
Other ways to ask this
  • What is the Privileged trait in EK9?
  • How does reveal() work on Sensitive values?
  • How do I unwrap a secret when I need the actual value?

Coming from another language?

Java: No access control on secret values. Any code with a String reference can print, log, or serialize it. Rust: secrecy crate's expose_secret() can be called by any code — no trait gating. Python: No secret type at all. Go: No secret type. EK9: reveal() is compile-time gated by the Privileged trait. The compiler rejects reveal() calls from non-Privileged contexts.

Keywords: privileged, gate, sensitive, reveal, access, infrastructure, secret, raw, audit, security, trait, unwrap