How does EK9 detect hardcoded secrets at compile time?
← Security and Sanitization · Ref: Q704
The EK9 compiler scans ALL string literals (including interpolated strings) for known secret patterns during the PRE_IR_CHECKS phase. Detection is automatic — no configuration needed.
DETECTED PATTERNS
E11080 Cloud provider keys (AWS AKIA..., GCP AIza..., Azure keys) E11081 Platform tokens (GitHub ghp_, GitLab glpat-, Slack xoxb-) E11082 Private key material (-----BEGIN RSA PRIVATE KEY-----) E11083 Database URLs with passwords (postgres://user:pass@host) E11084 JWT tokens (eyJ... three-part base64 structure) E11086 API keys (Stripe sk_test_, Anthropic sk-ant-, OpenAI sk-)
INTERPOLATED STRINGS
Secrets inside backtick interpolation are also detected:
msg <- `Key: ${"sk_test_abc123def456ghi789jkl"}` CAUGHT
HOW TO FIX
Replace hardcoded secrets with environment variable lookups:
env <- EnvVars() stripeKey <- env.sensitiveGet("STRIPE_SECRET_KEY")
For non-secret configuration, use env.get() instead.
INTEGRATED SECURITY
Unlike external SAST tools (GitGuardian, TruffleHog, detect-secrets), EK9's detection is built into the compiler. It cannot be bypassed, skipped, or misconfigured. If it compiles, there are no hardcoded secrets.
See Q701 for the Sensitive type. See Q702 for sensitiveGet() patterns. See Q703 for Privileged reveal(). See Q272 for defense in depth.
Example
defines module qa.security.secretdetection defines function <?- Correct pattern: load secrets from environment variables. -?> testCorrectSecretLoading() stdout <- Stdout() env <- EnvVars() //Cloud provider keys loaded safely awsKey <- env.sensitiveGet("AWS_ACCESS_KEY_ID") if awsKey? stdout.println("AWS key loaded: " + awsKey) //Platform tokens loaded safely ghToken <- env.sensitiveGet("GITHUB_TOKEN") if ghToken? stdout.println("GitHub token loaded: " + ghToken) //API keys loaded safely stripeKey <- env.sensitiveGet("STRIPE_SECRET_KEY") if stripeKey? stdout.println("Stripe key loaded: " + stripeKey) <?- Database connection without embedded password. -?> testSafeDatabaseConnection() stdout <- Stdout() env <- EnvVars() //Host and database name are configuration, not secrets dbHost <- env.get("DB_HOST") dbName <- env.get("DB_NAME") //Password is a secret dbPassword <- env.sensitiveGet("DB_PASSWORD") if dbHost? and dbName? and dbPassword? stdout.println("Database connection configured") <?- Safe string literals that should NOT trigger detection. -?> testSafeStrings() stdout <- Stdout() //Normal content - no secret patterns greeting <- "Hello, World!" configPath <- "/etc/app/config.yaml" contentType <- "application/json" apiEndpoint <- "https://api.example.com/v1/users" stdout.println(greeting) stdout.println(configPath) stdout.println(contentType) stdout.println(apiEndpoint) <?- Safe database URLs without passwords. -?> testSafeDatabaseUrls() stdout <- Stdout() //URLs without credentials are safe localDb <- "postgres://localhost/mydb" mysqlDb <- "mysql://localhost:3306/app" stdout.println(localDb) stdout.println(mysqlDb) defines program SecretDetectionDemo() stdout <- Stdout() stdout.println("Compile-time secret detection demonstrations") testCorrectSecretLoading() testSafeDatabaseConnection() testSafeStrings() testSafeDatabaseUrls()
Common mistakes
E11080 — AWS access keys starting with 'AKIA' are detected at compile time. Load cloud provider keys from environment variables. See ek9 -h E11080 for details.
Incorrect:
awsKey <- "AKIAIOSFODNN7EXAMPLE1"
Correct:
awsKey <- env.sensitiveGet("AWS_ACCESS_KEY_ID")
E11081 — GitHub personal access tokens with the 'ghp_' prefix are detected at compile time. Store platform tokens in environment variables. See ek9 -h E11081 for details.
Incorrect:
ghToken <- "ghp_ABCDEFabcdef1234567890abcdef12345678"
Correct:
ghToken <- env.sensitiveGet("GITHUB_TOKEN")
E11086 — Stripe API keys with the 'sk_test_' prefix are detected at compile time. Load API keys from environment variables. See ek9 -h E11086 for details.
Incorrect:
stripeKey <- "sk_test_abcdefghijklmnopqrstuvwxyz"
Correct:
stripeKey <- env.sensitiveGet("STRIPE_SECRET_KEY")
E11083 — Database URLs with embedded passwords are detected at compile time. Store database URLs in environment variables. See ek9 -h E11083 for details.
Incorrect:
dbPassword <- "postgres://admin:s3cret@db.example.com/prod"
Correct:
dbPassword <- env.sensitiveGet("DB_PASSWORD")
E11082 — Private key material including OpenSSH keys is detected at compile time. Load private keys from files or environment variables at runtime. See ek9 -h E11082 for details.
Incorrect:
greeting <- "-----BEGIN OPENSSH PRIVATE KEY-----"
Correct:
greeting <- "Hello, World!"
E11084 — JWT tokens with the characteristic three-part base64 structure are detected at compile time. Generate tokens dynamically or load from environment variables. See ek9 -h E11084 for details.
Incorrect:
configPath <- "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyIjoiYWRtaW4ifQ.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ"
Correct:
configPath <- "/etc/app/config.yaml"
Other ways to ask this
- What secret patterns does the EK9 compiler catch?
- Does EK9 have SAST for credentials?
- How do I fix a hardcoded secret error in EK9?
Coming from another language?
Java: No built-in secret detection. Relies on external tools like GitGuardian, TruffleHog, or pre-commit hooks — all optional and bypassable. Python: Same — external tools only. Go: gosec provides some detection but is optional. Rust: No built-in detection. JavaScript: No built-in detection. EK9: Secret detection is part of the compiler. Six pattern families detected automatically. Cannot be bypassed.
Keywords: sast, stripe, hardcoded, automatic, jwt, detection, private, github, key, aws, database, credential, cloud, compile, secret, pattern