Skip to content

[Chore] block-private-refs-in-public-repos.sh — hook to scrub registry names from upstream tickets #110

@atlas-apex

Description

@atlas-apex

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:

  1. Extracts the target repo from the command (the --repo arg, or repo slug in a gh api path).
  2. Determines if the target is public / framework-class — configurable list, defaults to me2resh/apexyard + whatever the fork's upstream remote resolves to.
  3. If public-class, reads the fork's apexyard.projects.yaml to extract every registered project's name, repo, and workspace fields.
  4. 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.
  5. 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
  6. 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

  • Hook exists, wired to PreToolUse on all four gh invocation shapes.
  • Reads apexyard.projects.yaml — handles missing file gracefully (no registry → no scrub list → hook is a no-op).
  • Blocks a ticket body containing a registered project name when the target is me2resh/apexyard or the configured upstream.
  • Does NOT block when the target is a private registered repo (the user's own project — leaking isn't a concern inside the user's own org).
  • Does NOT block when the skip marker is present.
  • Block message names the exact leaked token and suggests an abstract replacement.
  • Fixtures in .claude/hooks/tests/ cover: name leak, repo-slug leak, ticket-ref leak, skip marker, missing registry, non-public target.
  • docs/rule-audit.md lists the hook under its enforced rule.
  • A new rule file .claude/rules/leak-protection.md documents the principle and the escape hatch.

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High — material gap or user-impactingenhancementNew feature or request

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions