Module M.03
API Key Leakage
The Transparent Secret
GenLayer contracts are executed by multiple validators who can inspect the full source code and state. Hardcoding an API key means every validator sees it. A malicious validator could extract and abuse your key, leading to cost draining or account suspension.
Side-by-side · Vulnerable vs. Patched
two contracts · proven by paired transactions
vulnerable ▸contracts/vulnerable/VulnerableAPI.py
Failed TX> consensus failed · validators diverged
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *45# Module 3 (Vulnerable) -- API key leakage.6# Anti-pattern: a third-party API key embedded in contract source AND7# state. Validators don't have to "read" anything special -- the contract8# bytecode and storage are public on-chain, so anyone reading the explorer9# sees the secret. Also, web.render to a real third-party API is wasteful10# when GenLayer validators already have first-class LLM access.1112# WARNING: this is a deliberate anti-pattern. The value below is NOT a real13# key -- it's a clearly-fake placeholder shaped to avoid matching any14# provider's secret-scanning regex. The lesson is structural, not literal.15LEAKED_API_KEY = "FAKE-demo-key-do-not-use-visible-on-chain-0000"16OPENAI_URL = "https://api.openai.com/v1/chat/completions"171819class VulnerableAPI(gl.Contract):20 api_key: str21 last_summary: str2223 def __init__(self):24 # Storing a secret in state is the bug.25 self.api_key = LEAKED_API_KEY26 self.last_summary = ""2728 @gl.public.write29 def summarize(self, text: str) -> None:30 # web.request with an external auth token is doubly bad:31 # - the secret is visible to anyone reading the deployed code32 # - the call will return 401 because the key is fake,33 # producing a clean FINISHED_WITH_ERROR on Bradbury.34 bounded = text[:512]3536 def _call() -> str:37 url = OPENAI_URL + f"?demo_key={self.api_key}&q={bounded}"38 resp = gl.nondet.web.request(url, method='GET')39 if resp.status_code >= 400:40 raise Exception(f"api returned {resp.status_code}")41 return resp.body.decode("utf-8", errors="replace")[:200]4243 self.last_summary = gl.eq_principle.strict_eq(_call)4445 @gl.public.view46 def get_api_key(self) -> str:47 # Even worse: a public getter for the secret.48 return self.api_key
patched ▸contracts/patched/SafeAPI.py
Success TX> consensus reached · all validators agree
1# { "Depends": "py-genlayer:15qfivjvy80800rh998pcxmd2m8va1wq2qzqhz850n8ggcr4i9q0" }23from genlayer import *45# Module 3 (Patched) -- Safe summarisation.6# No third-party API, no secret. Validators run the LLM themselves via7# gl.nondet.exec_prompt and reach consensus through prompt_comparative.8# If you genuinely need a private API, the right pattern is an off-chain9# proxy that signs/authenticates separately and is referenced by URL only.101112class SafeAPI(gl.Contract):13 last_summary: str14 config_proof: str1516 def __init__(self):17 self.last_summary = ""18 self.config_proof = ""1920 @gl.public.write21 def summarize(self, text: str) -> None:22 # Bound input to keep prompts predictable across validators.23 bounded = text.strip().replace("```", "ʼʼʼ")[:1000]2425 def _llm() -> str:26 prompt = (27 "Summarise the following text in one sentence (max 25 words). "28 "Output the summary only, no preface.\n"29 f"```\n{bounded}\n```"30 )31 return gl.nondet.exec_prompt(prompt).strip()3233 self.last_summary = gl.eq_principle.prompt_comparative(34 _llm,35 principle="Summaries must convey the same key information.",36 )3738 @gl.public.write39 def demonstrate_fix(self) -> None:40 """Deterministic proof that no third-party API key lives in this41 contract -- the set of storage fields does not include one."""42 self.config_proof = "NO_API_KEY_IN_STATE; validators run LLM directly"4344 @gl.public.view45 def get_last_summary(self) -> str:46 return self.last_summary4748 @gl.public.view49 def get_config_proof(self) -> str:50 return self.config_proof
Call invoked
summarize("GenLayer runs Intelligent Contracts w...")calls api.openai.com with fake key -> 401 / external error
Call invoked
demonstrate_fix()no api key in contract state -- structural fix proven by config_proof getter
On-chain receipts
Knowledge check · M.03
01 / 02
Two questions on this incident. Pick the best answer; the question locks once committed.
Question 01 / 02
Why can't you hide API keys in GenLayer contracts?