Skip to content

Scan for secrets before a commit exists, and again in CI #79

Description

@SociableSteve

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

  1. 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.
  2. Whether history is scanned once, as a baseline, separately from scanning each change.
  3. 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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions