Driver
Every apexyard user runs a portfolio of private projects registered in their fork's apexyard.projects.yaml, plus contributes upstream to public framework repos (me2resh/apexyard, others). Authored content that crosses that boundary — a ticket or PR filed upstream that references a private project's name, ticket numbers, timelines, or internal decisions — leaks information the user never intended to publish.
The leak vector isn't malicious; it's routine. An agent diagnoses a framework bug while working in a private project, then files the upstream ticket with a helpful "discovered during <private-project> rebuild" reference. Once filed, the private project name is on the public framework repo's issue tracker and searchable indefinitely.
This affects:
- The fork's owner — the repo names in their private registry become public context.
- Their clients / employers — if the user manages projects on behalf of others, those project names leak too.
- Every other apexyard user, for the same reasons.
Self-discipline doesn't prevent this. The information is right there while the agent is writing the upstream ticket — not referencing it takes active suppression. Mechanical enforcement is the right shape, same pattern as check-secrets.sh and the commit-format hooks.
Scope
Add .claude/hooks/block-private-refs-in-public-repos.sh, wired to PreToolUse on Bash matching:
gh issue create --repo <repo> …
gh pr create --repo <repo> …
gh issue comment <n> --repo <repo> … and gh pr comment …
gh api calls hitting /issues or /pulls endpoints
The hook:
- Extracts the target repo from the command (the
--repo arg, or repo slug in a gh api path).
- Determines if the target is public / framework-class — configurable list, defaults to
me2resh/apexyard + whatever the fork's upstream remote resolves to.
- If public-class, reads the fork's
apexyard.projects.yaml to extract every registered project's name, repo, and workspace fields.
- Scans the title + body (from
--title, --body, --body-file, or -F) for any match against the extracted tokens, plus ticket-reference patterns (<owner>/<repo>#<n>) where <owner>/<repo> matches a registered project.
- On match, exits 2 with a message that:
- Names the specific leaked identifier (the matched token from the fork's own registry)
- Suggests an abstract replacement (e.g. "describe as 'a registered project', not by name")
- Explains the escape hatch
- Skip marker:
<!-- private-refs: allow --> in the body lets a deliberate reference through (rare — e.g. an apexyard ticket legitimately needs to reference a fork's registry entry by name).
What counts as "public / framework-class"
Configurable. Default heuristic:
# .claude/project-config.json or onboarding.yaml
leak_protection:
public_framework_repos:
- me2resh/apexyard
# Also treat anything reachable via `git remote get-url upstream` as framework-class.
auto_detect_upstream: true
skip_marker: "<!-- private-refs: allow -->"
What gets scrubbed
- Project names from
apexyard.projects.yaml (.projects[].name)
- Project repo slugs (
.projects[].repo)
- Workspace paths (
.projects[].workspace)
- Any
<owner>/<repo>#<n> pattern where <owner>/<repo> is a registered project
Does NOT scrub:
- The user's own
git config user.name / email (that's signed on every commit anyway)
- Generic class descriptions ("a multi-package monorepo", "a private project")
- Timeline phrases ("during bulk filing", "on 2026-04-24") — dates are fine; it's the attribution that leaks
Acceptance Criteria
Risks / Dependencies
- False positives if a project's
name collides with a generic word (e.g. a project named "auth" — would block any mention of auth in upstream tickets). Mitigation: warn on short / generic names when the registry is loaded, and match on whole-word boundaries only.
- False negatives if the user mentions the project by an alias not in the registry (shorthand, codename, internal nickname). Mitigation: hook is a backstop, not the only line of defence — pair with a rule doc and a feedback memory.
- Hook needs to parse
gh api command strings, which is brittle. Mitigation: support --method PUT/POST + -f / -F field args; skip on unrecognised shapes with a warning logged to the session file.
Glossary
| Term |
Definition |
| Public framework repo |
A repo whose issues and PRs are publicly visible and indexed. me2resh/apexyard is the canonical one; each fork may add others. |
| Registered project |
Any entry in the fork's apexyard.projects.yaml — by convention, these are the fork owner's private portfolio. |
| Skip marker |
HTML comment in the body that tells the hook to allow this particular reference. |
Driver
Every apexyard user runs a portfolio of private projects registered in their fork's
apexyard.projects.yaml, plus contributes upstream to public framework repos (me2resh/apexyard, others). Authored content that crosses that boundary — a ticket or PR filed upstream that references a private project's name, ticket numbers, timelines, or internal decisions — leaks information the user never intended to publish.The leak vector isn't malicious; it's routine. An agent diagnoses a framework bug while working in a private project, then files the upstream ticket with a helpful "discovered during
<private-project>rebuild" reference. Once filed, the private project name is on the public framework repo's issue tracker and searchable indefinitely.This affects:
Self-discipline doesn't prevent this. The information is right there while the agent is writing the upstream ticket — not referencing it takes active suppression. Mechanical enforcement is the right shape, same pattern as
check-secrets.shand the commit-format hooks.Scope
Add
.claude/hooks/block-private-refs-in-public-repos.sh, wired toPreToolUseonBashmatching:gh issue create --repo <repo> …gh pr create --repo <repo> …gh issue comment <n> --repo <repo> …andgh pr comment …gh apicalls hitting/issuesor/pullsendpointsThe hook:
--repoarg, or repo slug in agh apipath).me2resh/apexyard+ whatever the fork'supstreamremote resolves to.apexyard.projects.yamlto extract every registered project'sname,repo, andworkspacefields.--title,--body,--body-file, or-F) for any match against the extracted tokens, plus ticket-reference patterns (<owner>/<repo>#<n>) where<owner>/<repo>matches a registered project.<!-- private-refs: allow -->in the body lets a deliberate reference through (rare — e.g. an apexyard ticket legitimately needs to reference a fork's registry entry by name).What counts as "public / framework-class"
Configurable. Default heuristic:
What gets scrubbed
apexyard.projects.yaml(.projects[].name).projects[].repo).projects[].workspace)<owner>/<repo>#<n>pattern where<owner>/<repo>is a registered projectDoes NOT scrub:
git config user.name/ email (that's signed on every commit anyway)Acceptance Criteria
PreToolUseon all fourghinvocation shapes.apexyard.projects.yaml— handles missing file gracefully (no registry → no scrub list → hook is a no-op).me2resh/apexyardor the configured upstream..claude/hooks/tests/cover: name leak, repo-slug leak, ticket-ref leak, skip marker, missing registry, non-public target.docs/rule-audit.mdlists the hook under its enforced rule..claude/rules/leak-protection.mddocuments the principle and the escape hatch.Risks / Dependencies
namecollides with a generic word (e.g. a project named "auth" — would block any mention of auth in upstream tickets). Mitigation: warn on short / generic names when the registry is loaded, and match on whole-word boundaries only.gh apicommand strings, which is brittle. Mitigation: support--method PUT/POST+-f/-Ffield args; skip on unrecognised shapes with a warning logged to the session file.Glossary
me2resh/apexyardis the canonical one; each fork may add others.apexyard.projects.yaml— by convention, these are the fork owner's private portfolio.