Problem
This is a public repository, developed against live installs holding Google OAuth refresh tokens, a
GitHub token and LLM provider API keys. The credentials live in caroline.config.json and
data/google-tokens.json, both correctly ignored today, and nothing but that .gitignore stands
between a real key and a public commit.
One control is in place and works: GitHub secret scanning and push protection are enabled on this
repository, so a recognised provider credential is blocked at push time, server-side, where
--no-verify cannot reach it.
It protects exactly one repository, and it is the one that already has it. That is the shape of the
problem:
- A fork inherits none of it. Push protection is a per-repository setting owned by whoever owns
the repository. A fork is a different repository with different settings, and its owner can turn
the setting off or never have it on. Anyone mirroring the code into a private repository has no
secret scanning at all without Advanced Security, and anyone pushing to a host that is not GitHub
has nothing whatever. A public project's protections have to travel with the code, and a
repository setting does not.
- Non-provider patterns are disabled even here. Push protection currently catches credentials
it can attribute to a known provider. It does not catch a private key, a generic API key, a
password in a connection string, or an unrecognised token format. Caroline's own session and MCP
tokens are exactly that shape.
- Nothing runs before the commit exists. Feedback arrives at push time, when the secret is
already in a local commit and in the reflog. Removing it then is a history rewrite rather than an
amend, and on a shared branch it is a rewrite everyone else pays for.
- Nothing checks a pull request. A secret in a form push protection does not recognise is
reviewed by a human reading a diff, which is not a control.
- A fixture is the likely route in. The test strategy commits recorded provider payloads,
scrubbed of real addresses, names and repository identifiers. Scrubbing is a manual step and the
material being scrubbed came from a real account. This risk lands on contributors, in their own
forks, where none of this repository's settings apply.
Expected outcome
The protection is part of the repository rather than part of this repository's configuration, so a
fork gets it too.
- The check is committed to the repository and travels with a clone. A control that has to be
configured per fork protects only the forks whose owners thought about it.
- Secrets are caught before the commit exists, for the common case of a local mistake, so nothing
has to be rewritten out of history.
- The same check runs in CI and blocks a pull request, whatever the contributor's machine is set up
to do and whatever --no-verify was passed. The local check is fast feedback; CI is the gate.
- The CI check is required on the protected branch, so a change that removes or alters the workflow
produces a missing required check rather than a silently absent one. An absent check is invisible
in the way a failing one is not.
- Coverage extends past recognised provider formats to private keys, generic credentials and
Caroline's own token shapes.
- False positives are manageable and suppressible deliberately, with the suppression committed and
reviewable. A noisy check gets turned off, and a check that has been turned off is worse than none
because it still reads as protection.
- Installing the local hook is one documented step a contributor meets when they start. A hook that
lives only in somebody's .git/hooks is invisible to everyone else and lost on the next clone,
which is the same reason repository conventions do not belong in git-ignored directories.
Open questions
- Which scanner. The same one locally and in CI, so a commit accepted by the hook is not
rejected a minute later by a different tool with different rules.
- Whether history is scanned once, as a baseline, separately from scanning each change.
- Whether the non-provider patterns toggle is turned on here as well. It is a repository
setting and so is not a substitute for anything above, but it costs nothing and closes the
provider-shaped gap for this repository while the committed checks close it for every clone.
Problem
This is a public repository, developed against live installs holding Google OAuth refresh tokens, a
GitHub token and LLM provider API keys. The credentials live in
caroline.config.jsonanddata/google-tokens.json, both correctly ignored today, and nothing but that.gitignorestandsbetween a real key and a public commit.
One control is in place and works: GitHub secret scanning and push protection are enabled on this
repository, so a recognised provider credential is blocked at push time, server-side, where
--no-verifycannot reach it.It protects exactly one repository, and it is the one that already has it. That is the shape of the
problem:
the repository. A fork is a different repository with different settings, and its owner can turn
the setting off or never have it on. Anyone mirroring the code into a private repository has no
secret scanning at all without Advanced Security, and anyone pushing to a host that is not GitHub
has nothing whatever. A public project's protections have to travel with the code, and a
repository setting does not.
it can attribute to a known provider. It does not catch a private key, a generic API key, a
password in a connection string, or an unrecognised token format. Caroline's own session and MCP
tokens are exactly that shape.
already in a local commit and in the reflog. Removing it then is a history rewrite rather than an
amend, and on a shared branch it is a rewrite everyone else pays for.
reviewed by a human reading a diff, which is not a control.
scrubbed of real addresses, names and repository identifiers. Scrubbing is a manual step and the
material being scrubbed came from a real account. This risk lands on contributors, in their own
forks, where none of this repository's settings apply.
Expected outcome
The protection is part of the repository rather than part of this repository's configuration, so a
fork gets it too.
configured per fork protects only the forks whose owners thought about it.
has to be rewritten out of history.
to do and whatever
--no-verifywas passed. The local check is fast feedback; CI is the gate.produces a missing required check rather than a silently absent one. An absent check is invisible
in the way a failing one is not.
Caroline's own token shapes.
reviewable. A noisy check gets turned off, and a check that has been turned off is worse than none
because it still reads as protection.
lives only in somebody's
.git/hooksis invisible to everyone else and lost on the next clone,which is the same reason repository conventions do not belong in git-ignored directories.
Open questions
rejected a minute later by a different tool with different rules.
setting and so is not a substitute for anything above, but it costs nothing and closes the
provider-shaped gap for this repository while the committed checks close it for every clone.