Title: How Hardcoded API Keys in WASM Modules Happen in KAP and How to Fix Them
In the wasm/kap/standard-lib/fhelp-impl.kap file of a KAP-based project, we discovered a critical hardcoded secrets vulnerability at line 3257 that exposed a live Gemini API key to every user who loaded the application. The gemini query function embedded credentials directly in source code that compiles to WebAssembly and ships to browsers—making extraction trivial through developer tools or binary inspection.
This vulnerability illustrates a dangerous anti-pattern: treating client-side code as a secure storage location for server-side secrets. When your build pipeline produces WASM modules for browser distribution, any string in your source becomes public knowledge.
The Vulnerability Explained
The Problematic Code
The vulnerable implementation in fhelp-impl.kap:3255-3262 contained a hardcoded API key for Google's Generative Language API:
∇ gemini query {
⍝ curl with your apikey to see the list of models:
⍝ https://generativelanguage.googleapis.com/v1beta/models?key=AI...
model ← \"gemini-2.5-flash-preview-05-20\"
url ← \"https://generativelanguage.googleapis.com/v1beta/models/\", model, \":generateContent?key=\"
apikey ← \"AI..................................VyY\" ⍝ your API key goes here
target_url ← url, apikey
input ← \"{\\\"contents\\\":[{\\\"parts\\\":[{\\\"text\\\":\\\"\", query, \"\\\"}]}]}\"
headrs ← \"Accept\" \"application/json\"
Two critical flaws exist here:
- The
apikeyvariable (line 3259) contains a literal 39-character Google API key starting withAIand ending withVyY - The commented URL (line 3257) includes a partial key leak in the example, reinforcing the pattern
Why WASM Makes This Especially Dangerous
KAP compiles to WebAssembly for browser execution. Unlike server-side code, WASM modules are:
- Downloaded entirely to the client before execution
- Inspectable through browser DevTools (Sources → WASM → disassembly)
- Analyzable with standard tools like
wasm2wator binary scanners
An attacker needs only to:
1. Open browser developer tools on any page using this module
2. Search for the string AI or gemini in the WASM binary
3. Extract the complete key for unauthorized API access
Real-World Impact
For this specific application:
- Financial exposure: Attackers could exhaust quota limits on the Gemini API, incurring costs for the key owner
- Data poisoning: Malicious queries through the compromised key could train models on adversarial inputs
- Rate limit exhaustion: Legitimate users would be denied service when quotas are consumed by attackers
- Credential cascading: If the same key pattern was used elsewhere, additional services become compromised
The Fix
Before: Hardcoded Credential
∇ gemini query {
⍝ curl with your apikey to see the list of models:
⍝ https://generativelanguage.googleapis.com/v1beta/models?key=AI...
model ← \"gemini-2.5-flash-preview-05-20\"
url ← \"https://generativelanguage.googleapis.com/v1beta/models/\", model, \":generateContent?key=\"
apikey ← \"AI..................................VyY\" ⍝ your API key goes here
target_url ← url, apikey
After: Environment Variable Retrieval
∇ gemini query {
⍝ curl with your apikey to see the list of models:
⍝ https://generativelanguage.googleapis.com/v1beta/models?key=YOUR_API_KEY
model ← \"gemini-2.5-flash-preview-05-20\"
url ← \"https://generativelanguage.googleapis.com/v1beta/models/\", model, \":generateContent?key=\"
apikey ← env:get \"GEMINI_API_KEY\" ⍝ set this environment variable to your own API key
target_url ← url, apikey
Security Improvements
| Aspect | Before | After |
|---|---|---|
| Key storage | Embedded in source/WASM | External environment variable |
| Distribution | Shipped to all clients | Never leaves server/build environment |
| Rotation | Requires code change + redeploy | Environment update only |
| User isolation | Shared key across all users | Each deployment uses own key |
| Audit trail | Key visible in git history | Key excluded from version control |
The comment was also sanitized to use YOUR_API_KEY as a placeholder, preventing accidental copy-paste leaks.
Prevention & Best Practices
For KAP and WASM Projects
-
Never embed credentials in compilable source: If it becomes WASM, it becomes public. Use build-time injection or server-side proxies instead.
-
Use
env:getfor all secrets: KAP'senv:getfunction retrieves values at runtime from the execution environment, keeping credentials out of distributed artifacts. -
Implement
.envpatterns with.gitignore:
bash # .gitignore .env *.local secrets.kap -
Server-side proxy for browser WASM: Instead of calling APIs directly from WASM, route through your backend where secrets remain protected.
Detection Tools
| Tool Type | Implementation | Detection Method |
|---|---|---|
| Pre-commit hooks | gitleaks, truffleHog |
Entropy analysis + pattern matching for AIza[0-9A-Za-z\-_]{35} |
| CI/CD scanning | GitHub Secret Scanning, GitLab Secret Detection | Automated PR scanning for credential patterns |
| SAST | Semgrep rules for hardcoded credentials | AST-based detection of string literals assigned to apikey, token, secret |
| WASM analysis | Custom scripts with wasm2wat |
Extract and scan all strings from compiled modules |
Security Standards
- CWE-798: Use of Hard-coded Credentials
- CWE-259: Use of Hard-coded Password
- OWASP Top 10 2021: A07:2021 – Identification and Authentication Failures
- NIST SP 800-63B: Section 5.1.1.2 – Memorized Secret Verifiers (prohibits hardcoded credentials)
Key Takeaways
-
The
geminifunction infhelp-impl.kapnow retrieves API keys viaenv:get "GEMINI_API_KEY"instead of literal strings, eliminating credential exposure in WASM distributions -
WASM binaries are not a secure storage medium—any string in your KAP source becomes extractable client-side through standard browser developer tools
-
Environment variables must be configured at build time for WASM, not runtime, requiring build pipeline changes to inject secrets securely
-
Comments containing partial credentials (
⍝ https://...?key=AI...) leak key patterns and should use placeholder values likeYOUR_API_KEY -
Each deployment should use isolated credentials rather than shared keys embedded in distributed code, enabling per-environment rotation and breach containment
How Orbis AppSec Detected This
Source: The hardcoded string literal "AI..................................VyY" assigned to variable apikey in wasm/kap/standard-lib/fhelp-impl.kap:3259
Sink: The target_url ← url, apikey concatenation at line 3260, which embeds the credential into HTTP request URLs sent to Google's API
Missing control: No environment variable abstraction or build-time secret injection; direct use of string literal in production code distributed to client browsers
CWE: CWE-798 – Use of Hard-coded Credentials
Fix: Replaced the hardcoded apikey string with env:get "GEMINI_API_KEY" to retrieve credentials from environment configuration outside the distributed WASM module.
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
The hardcoded API key in fhelp-impl.kap demonstrates a fundamental misunderstanding of WASM's security model: client-side code cannot protect server-side secrets. When your build produces .wasm files for browser execution, every string, comment, and constant becomes part of the public attack surface.
The fix—migrating from apikey ← "AI...VyY" to apikey ← env:get "GEMINI_API_KEY"—restores proper credential hygiene by ensuring secrets never enter the distributed artifact. For teams shipping KAP or other languages to WASM, this pattern must be enforced through automated scanning, pre-commit hooks, and architectural reviews that treat all client-bound code as publicly readable.