September 12, 2026
INPUTS, SECRETS, AND THE BOUNDARY BETWEEN HELPFUL AND UNSAFE

Lesson 48.1 gave you the threat model: assets, actors, entry points, and abuse stories. Now we zoom in on the two entry points attackers — and careless agents — abuse first: what users send in and what your system must never give out. Get these two boundaries right and a whole family of failures disappears.
Validation comes in layers
Validation means checking that input has the expected shape, size, and permitted values before using it. It is not one check; it is a stack of layers, each catching what the previous one missed:
1. Format and type — is it the right kind of thing? A ticker is short uppercase letters, not a paragraph. A date parses as a date. 2. Required fields — is everything the action needs actually present? A watchlist save needs a ticker; a refresh needs a record ID. 3. Length and size — is it within bounds? Reject the 50,000-character "ticker" before it reaches a provider query or a database. 4. Allowed values — is it one of the permitted choices? An interval of 1d, 1w, or 1mo — not whatever string arrived. 5. Business rules — does it make sense for this product? "Cannot save the same ticker twice," "refresh at most once per hour per brief." 6. Safe handling — once accepted, is it passed onward without reinterpretation? Input goes to the provider or database as *data*, never as instructions.
Two words people misuse here: sanitization and encoding. They are not magic cleanup spells you chant over input. They are context-specific techniques — escaping output for HTML here, parameterizing a database query there — and the wrong one in the wrong place protects nothing. Specify the layer and the context, not the buzzword.
And remember the Class 45 distinction: client validation helps the user; server validation protects the system. Checking the ticker format in the browser gives instant feedback — good UX. Re-checking it on the server is what stops a hand-crafted request from bypassing the form entirely. The OWASP Top 10 keeps injection and input-handling failures near the top for exactly this reason: someone always tries the form's back door.
The ticker: a bounded input, end to end
Take Research Desk's search form. A reasonable ticker rule:
Ticker: 1–5 uppercase ASCII letters (A–Z), optionally a single "." plus 1–2 letters (BRK.B).
Reject: empty, longer than 12 chars, digits-only strings, HTML/SQL fragments, 50KB pastes.
On reject: "Enter a valid ticker like AAPL or BRK.B." — no stack trace, no provider error.
Follow the input's journey: browser trims and checks format instantly (kindness); the API route re-validates length, character set, and shape (defense); only the validated value reaches the provider adapter; anything else gets the friendly message above. A raw hand-built request with ticker=<script>... or a megabyte of text dies at the route with a clean rejection and a logged event. The customer sees guidance; the log keeps the request ID and the rejection reason; the provider never sees the garbage.
Write it this way in the task card — exact accepted pattern, exact rejection message, and the test cases (valid, empty, oversized, malicious) — and the AI has no room to invent a looser rule.
Secrets: platform config, never anything else
Secrets — API keys, database passwords, session-signing values, private tokens — belong in the hosting platform's protected environment configuration (its env vars / secrets manager), read by server code at runtime. Full stop. They do not belong in any of these places:
- The repository — committed code, config files, or comments. Deleting a committed key later does not erase history; it lives in every clone and in Git history forever. Keep secret-shaped files covered by
.gitignoreand rotate anything that was ever committed. - The browser bundle — anything shipped to the browser is readable by its user (Class 45's "View Source is not a vault"). A key the frontend needs to function is a key you have published.
- A screenshot, recording, or Markdown example — redacted in the doc means nothing if the real key is visible in the pixels behind it. Use
sk-...REDACTEDorPROVIDER_KEY=[set in platform env]in every example. - A prompt, chat transcript, or log — pasting a real key into an AI conversation or printing one to a log turns two more systems into secret stores you do not control.
Rotation is part of the design: know which secrets exist, who can rotate each one, and what breaks during rotation. A secret you cannot rotate is a secret you do not own.
The AI-specific failure: the helpful agent that echoes keys
Here is a failure mode unique to AI builders. You ask an agent to debug a failing provider call, and it helpfully prints the environment to "see what's configured" — echoing the live API key into the chat, the terminal scrollback, and possibly the commit it then proposes. Or it writes a "temporary" client-side key to unblock a demo, fully intending to fix it later, and later never comes.
Defend against both in the task instructions themselves:
Do not print, log, or display secret values at any step.
Do not place keys in client-side code, examples, or commit messages.
Read secret names from the documented env list; use placeholders in all output.
Then review every generated diff for secrets before merging: search for key-shaped strings (sk-, api_, secret, token=), new env files, hardcoded URLs with credentials, and console statements that dump configuration. Add that search to your review checklist in the next lesson's spirit — it takes a minute and catches the leak while it is still a diff, not an incident.
Uploads and URLs are untrusted input
Treat everything arriving from outside the trust boundary as data, not as instructions — especially the items that *look* harmless:
- Filenames —
../../etc/passwd, thousand-character names, double extensions. Store with generated names; never let a filename choose a server path. - File contents — an uploaded PDF or CSV can carry macros, formulas, or payloads for downstream parsers. Enforce type, size caps, and parse with libraries that treat content as data.
- URLs and webhook payloads — a submitted link or an incoming webhook body is a stranger's message in a polished envelope. Validate scheme and host (no internal addresses), fetch with timeouts and size limits, and never let a URL trigger privileged actions without authentication and ownership checks.
The polished form means nothing. The route re-checks everything.
Exercise: validation rules plus SECURITY.md
Write validation rules and safe error messages for three Research Desk surfaces:
1. Public search — the ticker rules above, plus a per-IP rate note (full limits arrive in Lesson 48.3). 2. Signed-in watchlist form — ticker rules plus auth (must be signed in), ownership (item belongs to caller), and duplicates ("already on your watchlist" instead of an error dump). 3. Internal refresh endpoint — the strictest: caller must be the scheduler or an entitled user, ticker must validate, refresh throttled per brief, every call logged with request ID.
Record the secret side in SECURITY.md: where each secret lives (platform env name, never the value), who rotates it, .gitignore coverage, the no-echo task instruction, and the diff-review search. Add validation tests for each rule: one valid case, one empty case, one oversized case, one malicious case.
Finish line: validation rules with safe messages for all three surfaces, tests that prove valid input passes and hostile input is cleanly rejected, and a secret-handling checklist in SECURITY.md.
Verify: submit a raw oversized ticker and a signed-out watchlist save; confirm a friendly message, no trace or key in the response, and a logged event. Search the latest diff for key-shaped strings.
Common failure: client-only checks, or "sanitize everything" with no named layer and no server test. If the server never re-checks, the boundary is paint, not a wall.
Check your understanding
1. Name the six validation layers and say which one the ticker length limit belongs to. 2. Why does deleting a committed secret not fix the leak? 3. What task instruction prevents agent secret-echo, and what diff search backs it up?
Next, Lesson 48.3 completes the trio — validation, access control, and rate limits — and teaches your system to fail safely when all three are tested.
ARTICLE DISCUSSION
JOIN THE
CONVERSATION.
Got a question, a take, or a better way to do this? Log in and leave a comment.
