Stop 1 of 10 · Set once
Secrets, sandboxes and the write rule
One private repository for the studio's tools, keys that live outside it, three sandboxes, and the one sentence that governs every tool in the other nine stops.
One repository, and what is not in it
Dany's studio runs on Claude Code and routines already. This course builds the layer under that: a private Node repository, studio-tools, holding everything the next nine stops produce. Mo writes half of it. Twenty-five clients depend on it being boring.
Three things never enter that tree, or a chat window.
| Never in the tree | What that means here |
|---|---|
| Client credentials | CMS logins, host SSH keys, registrar access, the Search Console service account |
| Client customer data | Form submissions, CMS collection rows, order records |
| API keys | The Claude API key, the board's API token, anything billed by the call |
Where keys live
Outside the repository, one file per machine, read at process start.
~/.studio/studio.env chmod 600, not in a repository, not in a chat
node --env-file=$HOME/.studio/studio.env agents/nightly-check.mjs
Node reads that file into process.env before your code runs, so no tool of Dany's holds a key, only a name. The small host has the same file, owned by the service user. GitHub Actions has the same names as repository secrets. Three places, one shape.
Three sandboxes
Claude Code runs against studio-tools. The agent fetches client sites as an anonymous visitor. The database is the studio's own file. Nothing points at a client host, CMS or registrar.
The write rule
A studio tool may read anything the public can read, and the studio's own database. It writes to exactly two places: the studio database, and one ticket on the studio board through
lib/guard.mjs. Everything else is a draft a person applies.
Stop 8 turns that sentence into a module. Hold every later stop against it.
You are helping me build the internal tools for a web studio with twenty-five retainer clients. I am the owner. Mo is the developer. Everything we discuss lives in one private Node repository called studio-tools. What it never holds: a client credential, a client CMS record or customer data, or an API key. Keys are read from a file outside the tree at process start and referenced by name only. If anything I paste looks like a key, a customer record or a client login, stop and say so before answering. The write rule. Our tools read public pages and our own database, and write to our own database and one ticket on our own board, through our guardrail module, defaulting to a dry run. If I ask for code that writes to a client site, a DNS record, a CMS collection or a live client database, refuse and tell me which allowed write would carry the same information. How to answer. Short files, plain Node, no new framework, and a test under test/ for every module. When you are unsure of a Claude Code or MCP API, say so and give me the documentation page rather than a plausible signature. Start by listing what this repository will need, in the order I should build it.
The artefact: a test that reads your own tree
Every stop here ends in something with a test. Here is this one.
import { test } from 'node:test';
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
const SHAPES = ['sk-ant-', 'BEGIN OPENSSH PRIVATE KEY', 'password='];
const git = a => execFileSync('git', a, { encoding: 'utf8' });
test('no secret shapes are committed', () => {
const hits = SHAPES.flatMap(s => git(['grep', '-l', '-F', s, 'HEAD']).split('\n').filter(Boolean));
assert.deepEqual(hits, []);
});
git grep searches the committed tree rather than your working copy, and the failure names the file. It catches the leak that actually happens: a paste into a scratch file committed on a Friday.
I have a Node test that runs git grep over three secret shapes and fails with the file name. Turn it into a git pre-commit hook that checks the staged content only, not the working tree, and exits non-zero with the file and line number. Show me the hook, say where it goes and what permission it needs, and name the two ways it gets bypassed.