E11090: reveal() on Sensitive is only permitted in a method of a class or trait that is Privileged: it has the Privileged trait, a trait that has it, or a super class that has it
What went wrong
A call to reveal() on a Sensitive value was detected outside a Privileged class or trait. The reveal() method returns the raw secret string and is restricted to methods of a class or trait that is Privileged.
Privilege is inherited. A class or trait is Privileged when it has the Privileged trait itself, has a trait that has it anywhere in its trait chain, or extends a class that is Privileged. So 'Derived extends Base', where 'Base with trait of Privileged', may call reveal() too.
Worked examples
Compilable examples demonstrating this error and the correct fix:
- ek9 -q 703 (How do I access the raw value of a Sensitive in EK9?)
- ek9 -q 706 (How do I use credentials, URLs, usernames and passwords o...)
Rationale
The Sensitive type wraps secret values (API keys, passwords, tokens) and automatically redacts them on $ and $$ operators. The reveal() method is the only way to access the raw secret value, and it must be gated to prevent accidental exposure.
Without this check, any code could call reveal() and the secret would flow through logging, error messages, or serialization - exactly the pattern that caused the Uber (2016), Capital One (2019), and CircleCI (2023) credential leaks.
The Privileged trait acts as a compile-time capability token. Searching for 'Privileged' in any codebase finds every place the capability is granted; the classes and traits that inherit it from those are the rest of the audit trail.
Functions cannot call reveal() because functions cannot have traits in EK9 - there is no way to declare a function as Privileged.
How to fix it
Make the enclosing class Privileged: give it the Privileged trait (or a trait that has it), or have it extend a Privileged class. 'HttpClient with trait of Privileged' may call 'apiKey.reveal()' in its methods (good.ek9 in the [EXAMPLE] section below; its SafeLogger shows that interpolating the Sensitive value itself is fine without the trait - it redacts).
If the code is in a function, move the reveal() into a class with Privileged and have the function hand the Sensitive value to it (good_delegate.ek9 below).
See also
Examples
Every example is compiled by the EK9 compiler: the first fails exactly as captioned, the others fix it.
Fails with E11090: reveal() on Sensitive is only permitted in a method of a class or trait that is Privileged: it has the Privileged trait, a trait that has it, or a super class that has it
#!ek9 defines module error.e11090.bad defines trait Connector connectWithSecret() as abstract -> secret as Sensitive Extractor extract() as abstract <- result as String? defines class <?- Regular class without Privileged trait cannot call reveal(). -?> UnprivilegedService with trait of Connector override connectWithSecret() -> secret as Sensitive rawSecret <- secret.reveal() stdout <- Stdout() if rawSecret? stdout.println(rawSecret) <?- Another class without Privileged calling reveal() on a local variable. -?> AnotherUnprivileged processSecret() -> credential as Sensitive exposed <- credential.reveal() stdout <- Stdout() if exposed? stdout.println(exposed) defines function <?- Named dynamic class without Privileged trait cannot call reveal(). -?> namedDynamicClassBadReveal() -> secret as Sensitive <- result as String? handler <- BadExtractor(secret) as class extract() <- rtn as String? rtn: secret.reveal() default operator ? if handler? result: handler.extract() else result: "no handler" <?- Unnamed dynamic class with non-Privileged trait cannot call reveal(). -?> unnamedDynamicClassBadReveal() -> secret as Sensitive <- result as String? handler <- (secret) trait of Extractor as class override extract() <- rtn as String? rtn: secret.reveal() default operator ? if handler? result: handler.extract() else result: "no handler" <?- Standalone function cannot call reveal() because functions cannot have traits. -?> attemptRevealInFunction() -> secret as Sensitive <- result as String? result: secret.reveal() <?- A valid function that does NOT call reveal() - should compile fine. -?> safeFunction() -> secret as Sensitive <- status as String? if secret? status: `Secret present (redacted: ${$secret})` else status: "No secret available"
Fixed: Reveal only in a Privileged class (the trait, or inherited from a trait or super class); elsewhere handle the Sensitive as it is
#!ek9 defines module error.e11090.good defines class 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`) default operator ? SafeLogger logActivity() -> credential as Sensitive stdout <- Stdout() stdout.println(`Activity with credential: ${credential}`) default operator ?
Fixed: A function cannot be Privileged: it hands the Sensitive value to a Privileged class
#!ek9 defines module error.e11090.good.delegate defines class HttpClient with trait of Privileged sendRequest() -> apiKey as Sensitive rawKey <- apiKey.reveal() stdout <- Stdout() if rawKey? stdout.println(`Key of ${length rawKey} chars sent`) default operator ? defines function doRequest() -> apiKey as Sensitive client <- HttpClient() client.sendRequest(apiKey)
Questions and answers
These Q&A entries show E11090 and its fix: