style(no-slop): remove every em-dash + banned words across all modules + capstone
Apply the no-ai-slop standard (now binding in AGENTS.md): the em-dash character is banned outright (restructured, not blind-replaced), plus the banned word/phrase list (delve, leverage, robust, seamless, truly, unlock, etc.). 0 em-dashes remain in modules + capstone; the only "robust" left is the planted M10 ai-change.patch trap. Module H1 titles use a colon separator. All deliberate teaching devices preserved; labs compile/parse (py/sh/yaml/json); no junk. AGENTS.md updated with the hard no-slop rules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TfzV5QvtPDz8LJS3Pu5VLT
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
# Module 17 — Secrets, Config, and Environments
|
||||
# Module 17: Secrets, Config, and Environments
|
||||
|
||||
> **Ask an AI to "connect to the API" and it will paste your secret key straight into a source
|
||||
> file, the one place it must never go.** This module gives you the standard, boring, correct
|
||||
@@ -9,14 +9,14 @@
|
||||
|
||||
## Prerequisites
|
||||
|
||||
- **Module 2 — Version Control as a Safety Net.** You need `.gitignore` and the habit of reading
|
||||
- **Module 2: Version Control as a Safety Net.** You need `.gitignore` and the habit of reading
|
||||
`git diff` before you commit. Both matter here.
|
||||
- **Module 12 — Revert, Reset, and Recovery.** You learned that Git history is forever and that
|
||||
secrets *don't belong in it* — this module is the practical follow-through on that promise.
|
||||
- **Module 15 — Security Scanning for AI-Generated Code.** Secret scanning is the automated gate
|
||||
- **Module 12: Revert, Reset, and Recovery.** You learned that Git history is forever and that
|
||||
secrets *don't belong in it*; this module is the practical follow-through on that promise.
|
||||
- **Module 15: Security Scanning for AI-Generated Code.** Secret scanning is the automated gate
|
||||
that catches a hardcoded key after the fact. This module is the *prevention* that means the gate
|
||||
rarely has to fire.
|
||||
- **Module 16 — Containers and Reproducible Environments.** A container is a sealed box; config and
|
||||
- **Module 16: Containers and Reproducible Environments.** A container is a sealed box; config and
|
||||
secrets are how you pass the outside world *into* it at run time. That handoff is environment
|
||||
variables, which is exactly what this module is about.
|
||||
|
||||
@@ -34,7 +34,7 @@ By the end of this module you can:
|
||||
`.env` file), and have the app read it back at run time.
|
||||
3. Keep config you *can* commit (a committed template) separate from secrets you *can't* (the real
|
||||
`.env`), so a teammate or a fresh AI session knows exactly what to supply.
|
||||
4. Apply the 12-factor rule — *config lives in the environment, not the build* — to run one codebase
|
||||
4. Apply the 12-factor rule (*config lives in the environment, not the build*) to run one codebase
|
||||
unchanged across dev, staging, and prod.
|
||||
5. Describe what a secrets manager buys you over `.env` files, in vendor-neutral terms, and know
|
||||
when you've outgrown a file on disk.
|
||||
@@ -70,7 +70,7 @@ rest of this module:
|
||||
|
||||
| Kind | Example | Where it lives | Goes in Git? |
|
||||
|------|---------|----------------|--------------|
|
||||
| **Code** | The logic of your app | Source files | **Yes** — that's the point |
|
||||
| **Code** | The logic of your app | Source files | **Yes**, that's the point |
|
||||
| **Config** | Which backend URL, log level, feature flags, timeouts | The environment (often a `.env` *template* you commit + real values you don't) | The *template* yes, the *values* it depends |
|
||||
| **Secrets** | API keys, passwords, tokens | The environment, sourced from a secret store in real deployments | **Never** |
|
||||
|
||||
@@ -129,7 +129,7 @@ Two non-negotiable rules come with it:
|
||||
most important line in this module:
|
||||
|
||||
```gitignore
|
||||
# secrets and local config — never commit
|
||||
# secrets and local config, never commit
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -164,7 +164,7 @@ The principle behind all of this comes from the [12-factor app](https://12factor
|
||||
and factor III states it plainly: **store config in the environment.** The payoff for this audience:
|
||||
|
||||
> You build the artifact **once** and run the *same* artifact in every environment. Nothing about
|
||||
> dev, staging, or prod is baked into the code or the container image — the differences are injected
|
||||
> dev, staging, or prod is baked into the code or the container image; the differences are injected
|
||||
> at run time as environment variables.
|
||||
|
||||
This is why it pairs so tightly with containers (Module 16). A container image is your immutable,
|
||||
@@ -184,9 +184,9 @@ promote one artifact through environments instead of rebuilding per stage.
|
||||
"Environments" here means the distinct places your code runs, each with its own config and its own
|
||||
secrets. The standard three:
|
||||
|
||||
- **dev** — your machine. A dev backend, a dev key with low privileges, verbose logging.
|
||||
- **staging** — a production-like rehearsal. Separate backend, separate key, real-ish data.
|
||||
- **prod** — the real thing. Real users, the powerful key, conservative settings.
|
||||
- **dev**: your machine. A dev backend, a dev key with low privileges, verbose logging.
|
||||
- **staging**: a production-like rehearsal. Separate backend, separate key, real-ish data.
|
||||
- **prod**: the real thing. Real users, the powerful key, conservative settings.
|
||||
|
||||
The rule that catches people: **each environment gets its own secrets, and they never mix.** A dev
|
||||
key must not be able to touch prod data, and a prod key must never sit in a developer's `.env`. The
|
||||
@@ -217,8 +217,8 @@ reasons that show up fast in real operations:
|
||||
|
||||
- A plaintext file on a server is readable by anything that compromises that box.
|
||||
- You can't **rotate** a key across fifty machines by editing fifty files.
|
||||
- You get no **audit trail** — no record of who read which secret when.
|
||||
- There's no **access control** — "this service can read the DB password but not the signing key."
|
||||
- You get no **audit trail**: no record of who read which secret when.
|
||||
- There's no **access control**: "this service can read the DB password but not the signing key."
|
||||
|
||||
A **secret manager** (also called a secrets store or vault, categorically) solves these. It's a
|
||||
dedicated service that stores secrets encrypted at rest, hands them out only to authenticated
|
||||
@@ -226,12 +226,12 @@ callers, logs every access, and supports rotation and fine-grained access polici
|
||||
app (or the platform it runs on) fetches the secret from the manager into memory instead of reading
|
||||
a file. The categories you'll encounter:
|
||||
|
||||
- **Cloud-provider managers** — every major cloud has one, tightly integrated with that cloud's
|
||||
- **Cloud-provider managers**: every major cloud has one, tightly integrated with that cloud's
|
||||
identity system.
|
||||
- **Standalone / self-hostable vaults** — dedicated secret-management products you run yourself, a
|
||||
- **Standalone / self-hostable vaults**: dedicated secret-management products you run yourself, a
|
||||
good fit for the on-prem and air-gapped scenarios this audience often lives in (the same
|
||||
self-host instinct from Module 8).
|
||||
- **Platform-native secrets** — your container orchestrator and your CI/CD system both have a
|
||||
- **Platform-native secrets**: your container orchestrator and your CI/CD system both have a
|
||||
built-in concept of "secrets" you can inject as environment variables, which is how secrets reach
|
||||
a pipeline (Module 14) or a deployment (Module 18) without ever touching the repo.
|
||||
|
||||
@@ -291,7 +291,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
- The starter files in this module's `lab/starter/`: `sync.py` (the before) and `.env.example`.
|
||||
- Claude Code in your terminal (`claude --version` to confirm it's installed; sub your own agent).
|
||||
|
||||
### Part A — See the smell
|
||||
### Part A: See the smell
|
||||
|
||||
1. Copy `lab/starter/sync.py` and `lab/starter/.env.example` into your `tasks-app` folder, then run
|
||||
the before-picture:
|
||||
@@ -306,7 +306,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
this getting committed and pushed: the key is now in history forever (Module 12) and a secret
|
||||
scanner (Module 15) would light up, if you were lucky enough to have one.
|
||||
|
||||
### Part B — Gitignore the secret *first*
|
||||
### Part B: Gitignore the secret *first*
|
||||
|
||||
2. Before any real secret exists, close the door. Tell Claude Code (sub your own agent) to set up
|
||||
the ignore rules:
|
||||
@@ -319,7 +319,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
(ignore the secret before the secret exists). The rules should land like this:
|
||||
|
||||
```gitignore
|
||||
# secrets and local config — never commit
|
||||
# secrets and local config, never commit
|
||||
.env
|
||||
.env.*
|
||||
!.env.example
|
||||
@@ -334,7 +334,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
If `.env` shows up in `git status`, the ignore rule is wrong; have the agent fix it before going
|
||||
further. This verification is the step that prevents the leak.
|
||||
|
||||
### Part C — Refactor the secret into the environment
|
||||
### Part C: Refactor the secret into the environment
|
||||
|
||||
4. Now move the secret and the environment-specific URL out of the code. Ask Claude Code (sub your
|
||||
own agent):
|
||||
@@ -353,7 +353,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
from pathlib import Path
|
||||
|
||||
def load_dotenv(path: Path) -> None:
|
||||
"""Minimal .env loader — no dependency. Real projects use a library for this."""
|
||||
"""Minimal .env loader, no dependency. Real projects use a library for this."""
|
||||
if not path.exists():
|
||||
return
|
||||
for line in path.read_text().splitlines():
|
||||
@@ -393,7 +393,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
stomp on what's already in the environment. If the AI hands you plain assignment, that's the
|
||||
correction to make.
|
||||
|
||||
### Part D — Run it from the environment
|
||||
### Part D: Run it from the environment
|
||||
|
||||
5. Run it reading from your `.env`:
|
||||
|
||||
@@ -420,7 +420,7 @@ type the commands by hand. Then you'll make it select config per environment.
|
||||
set:** it's using `os.environ[key] = value` where it needs `os.environ.setdefault(...)` (see
|
||||
Part C). Fix the loader so the command line wins, and the override takes effect.
|
||||
|
||||
### Part E — Commit, and verify the secret didn't tag along
|
||||
### Part E: Commit, and verify the secret didn't tag along
|
||||
|
||||
7. Have the agent commit the refactor, then **read the diff yourself before you accept it** (the
|
||||
review reflex from the AI angle). Tell Claude Code (sub your own agent):
|
||||
@@ -498,7 +498,7 @@ publishing:
|
||||
products. If you add specific product names, re-verify each still exists, is current, and
|
||||
isn't pinned as *the* answer (vendor-neutral rule, AGENTS.md).
|
||||
- [ ] **Re-check the 12-factor reference.** Confirm the [12factor.net](https://12factor.net) link
|
||||
resolves and that "factor III — config" is still phrased as "store config in the environment."
|
||||
resolves and that "factor III, config" is still phrased as "store config in the environment."
|
||||
- [ ] **Re-verify `.gitignore` negation behavior.** Confirm `!.env.example` still un-ignores the
|
||||
template under the `.env.*` rule with a current Git, and that `git status` behaves as the lab
|
||||
claims.
|
||||
|
||||
Reference in New Issue
Block a user