Skip to content

feat(ci) work-email proactive denylist via secret-sourced pattern#68

Merged
jkeeley2073 merged 2 commits into
mainfrom
Dev-WorkEmailDenylist
May 4, 2026
Merged

feat(ci) work-email proactive denylist via secret-sourced pattern#68
jkeeley2073 merged 2 commits into
mainfrom
Dev-WorkEmailDenylist

Conversation

@jkeeley2073
Copy link
Copy Markdown
Contributor

@jkeeley2073 jkeeley2073 commented May 4, 2026

Summary

Closes docs/build-spec.md Phase 2 § Scope item 9 (Wave 1).

Promotes the work-email block from manual-trigger ("add to denylist if risk emerges" per feedback_personal_identity_only.md) to proactive enforcement in the sanitization workflow. The pattern itself is stored in the WORK_EMAIL_PATTERN repository secret so the actual work-email pattern never lands in this public file — putting it literally in the workflow YAML would publish the work-email pattern in the public repo and defeat the personal-account-isolation goal the rule is trying to protect.

The workflow change:

  • Loads WORK_EMAIL_PATTERN as a step env var sourced from ${{ secrets.WORK_EMAIL_PATTERN }}
  • Pre-validates the pattern is a well-formed extended regex before the existing run_rule is invoked (added in 9dc3de5; see "Local review (retroactive)" below)
  • Runs the existing run_rule grep against the secret-sourced pattern when set and well-formed
  • Skips with a ::warning annotation (not a failure) when the secret is missing, so the workflow doesn't break for forks or before initial setup
  • Continues to enforce the personal-email rules unconditionally

Setup required after merge (one-time)

  1. Repo Settings → Secrets and variables → Actions → New repository secret
  2. Name: WORK_EMAIL_PATTERN
  3. Value: an ERE regex matching the work email or domain (e.g., @workdomain\.com or firstname\.lastname@workdomain). Escape regex metacharacters like . as \..
  4. The workflow becomes proactive on next push

The pattern lives only in repo settings, never in source. Forks and external contributors can clone the repo, run the workflow, and never see the pattern.

Test Plan

CI run on this PR will exercise the modified workflow once. Expected behavior on this PR (with WORK_EMAIL_PATTERN not yet set): rule skipped with ::warning annotation; personal-email rules continue to run; sanitization passes.

Validation hand-off after merge (per Phase 2 § Exit criteria — verified by synthetic commits):

  1. Set the WORK_EMAIL_PATTERN secret
  2. On a throwaway branch, commit a file containing the work-email pattern → push → confirm CI fails on Sanitization failure: Work email (secret-sourced)
  3. On a throwaway branch, commit a file containing the personal-email pattern → push → confirm CI fails on the existing personal-email rules
  4. Delete both throwaway branches; do not merge

Captured against Phase 2 § Exit criteria in the phase retrospective.

Out of Scope

  • Setting the secret value itself. Out-of-band repo settings action; cannot land in this PR (and would defeat the design if it did).
  • Validation hand-off. Happens after merge as a small ops task, not in this PR.
  • Broader run_rule exit-code disambiguation. The existing run_rule function's || true masks grep exit codes 0/1/2 indistinguishably for every rule. The narrow fix in 9dc3de5 addresses the new rule's specific risk; refactoring run_rule itself to distinguish exit 2 (malformed pattern) from exit 1 (no match) is a separate cleanup and out of scope here.

Local review (retroactive)

/local-review was run retroactively against this branch (audit gap relative to guardrails.md § Per-PR gate; the original push had only the 7-item mechanical audit). Outcome: 0 🔴 / 3 ⚠️ / 7 categories ✅.

  • ⚠️ Test quality (covered in PR description). Agent flagged that the PR should describe a post-merge synthetic-token smoke test. The "Validation hand-off after merge" section above already covers it (steps 2–3 are the synthetic-token verification). No change.
  • ⚠️ Error handling (fixed in 9dc3de5). Agent identified that grep -E exits 2 on a malformed extended regex, but run_rule's || true masks exit 2 as "no match" — silently disabling the rule if the operator sets WORK_EMAIL_PATTERN to a typo. Follow-up commit 9dc3de5 pre-validates the pattern by running it against empty stdin and checking grep's exit code directly; exit 2 fails the workflow with a clear remediation message instead of passing it.
  • ⚠️ Configuration discipline (verified). Agent flagged the comment's cross-reference to docs/build-spec.md Phase 2 § Scope item 9 as worth verifying. Confirmed: anchor exists at build-spec.md:225. No change.

Security analysis (the load-bearing category for this PR) cleared with no findings: secret masked in logs by GitHub Actions automatically, safe quoting under set -u, no command injection via regex interpolation, no meaningful indirect leak through grep's matched-line echoing.

Checklist

  • CI is green — will verify; sanitization expected to pass with the warning annotation
  • PR title follows the Conventional Commits format
  • If this is a new architectural decision, an ADR has been added — N/A; execution of already-approved Phase 2 scope item
  • If user-visible behavior changes, docs are updated — N/A; build-spec already documents the work-email gate; quality-spec already updated to Phase 2 placement
  • If a memory is now stale, it has been updated or removed — feedback_personal_identity_only.md will need a small follow-up memory edit post-merge to note that work-email blocking is now proactive
  • No TODO / FIXME / commented-out code committed
  • No new entries in <NoWarn> without justification — N/A; no code

Pre-push self-audit (additive PRs)

Workflow YAML edit; no scraper / options class / extension / additive C# code surface. Reviewed against the audit criteria:

  • Conditional [ -n "${WORK_EMAIL_PATTERN:-}" ] is the toggle; rule fires when the secret is set, skips otherwise
  • Pre-validation step distinguishes malformed-pattern (exit 2) from no-match (exit 1); fails loudly on malformed
  • No bare catch; the run_rule grep already uses || true to avoid the workflow crashing on no-match (and the new pre-validation handles exit 2 separately)
  • Workflow won't fail open: when secret is set with a valid pattern that matches, fail=1 is set and the script exits 1 at the bottom
  • Workflow won't false-fail: when secret is unset, the rule is skipped with a warning, not an error
  • Identity check verified: git log -1 --format='%an <%ae>'Jim Keeley <94459922+jkeeley2073@users.noreply.github.com>

🤖 Generated with Claude Code

Promote the work-email block from manual-trigger ("add to denylist if
risk emerges" per feedback_personal_identity_only.md) to proactive
enforcement in the sanitization workflow, per Phase 2 § Scope item 9.

The pattern itself is stored in the WORK_EMAIL_PATTERN repository
secret so the actual work-email pattern never lands in this public
file (which would defeat the personal-account-isolation goal the rule
is trying to protect). The workflow:

- Loads WORK_EMAIL_PATTERN as a step env var sourced from secrets
- Runs the existing run_rule grep against the secret-sourced pattern
  when set
- Skips with a ::warning annotation (not a failure) when the secret
  is missing, so the workflow doesn't break for forks or before
  initial setup
- Continues to enforce the personal-email rules unconditionally

Setup (one-time, out-of-band): repo Settings → Secrets and variables
→ Actions → New repository secret, name WORK_EMAIL_PATTERN, value an
ERE regex matching the work email or work domain (e.g., the work
domain like "@workdomain\.com" or a name pattern). The workflow
becomes proactive immediately on next push.

Closes Phase 2 § Scope item 9 (Wave 1). Item 7 round 1 (close 4
deprecated-path Dependabot PRs) was a no-PR GitHub action and ships
alongside; item 3 (seed ingestion_sources) is the third Wave 1 item
and ships separately.
@jkeeley2073 jkeeley2073 added the claude-code Generated with Claude Code label May 4, 2026
Per /local-review on PR #68: grep -E exits 2 on a malformed extended
regex, but run_rule wraps the grep call with `|| true` which masks
exit 2 as "no match" — silently disabling the rule. A typo in the
WORK_EMAIL_PATTERN secret would pass the workflow without ever
checking commits against the work-email pattern.

The narrow fix: pre-validate the pattern by running it against an
empty stdin via printf '' | grep -E "$WORK_EMAIL_PATTERN" and
checking grep's exit code directly. Exit 2 = malformed pattern,
fail the workflow with an error annotation that names the issue
and points the operator at how to fix the secret. Exit 0 (matches
empty) or 1 (no match against empty) → pattern is well-formed,
proceed to run_rule normally.

The broader cleanup of run_rule itself (distinguishing grep exit
codes 0/1/2 for every rule) is out of scope for this PR — the
narrow fix here addresses the new rule's specific risk without
touching pre-existing behavior of the other rules.

Local review summary (retroactive on PR #68): 0 🔴, 3 ⚠️ findings.
- ⚠️ #1 (post-merge smoke test): already covered in the PR
  description's "Validation hand-off after merge" section.
- ⚠️ #2 (grep exit-2 silent swallow): fixed by this commit.
- ⚠️ #3 (doc-anchor verification): the comment cites
  "docs/build-spec.md Phase 2 § Scope item 9" which exists at
  build-spec.md:225 — verified, no change needed.
@jkeeley2073 jkeeley2073 merged commit 5f87ad2 into main May 4, 2026
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

claude-code Generated with Claude Code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant