feat(ci) work-email proactive denylist via secret-sourced pattern#68
Merged
Conversation
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.
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.
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes
docs/build-spec.mdPhase 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 theWORK_EMAIL_PATTERNrepository 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:
WORK_EMAIL_PATTERNas a step env var sourced from${{ secrets.WORK_EMAIL_PATTERN }}run_ruleis invoked (added in 9dc3de5; see "Local review (retroactive)" below)run_rulegrep against the secret-sourced pattern when set and well-formed::warningannotation (not a failure) when the secret is missing, so the workflow doesn't break for forks or before initial setupSetup required after merge (one-time)
WORK_EMAIL_PATTERN@workdomain\.comorfirstname\.lastname@workdomain). Escape regex metacharacters like.as\..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_PATTERNnot yet set): rule skipped with::warningannotation; personal-email rules continue to run; sanitization passes.Validation hand-off after merge (per Phase 2 § Exit criteria — verified by synthetic commits):
WORK_EMAIL_PATTERNsecretSanitization failure: Work email (secret-sourced)Captured against Phase 2 § Exit criteria in the phase retrospective.
Out of Scope
run_ruleexit-code disambiguation. The existingrun_rulefunction's|| truemasks grep exit codes 0/1/2 indistinguishably for every rule. The narrow fix in 9dc3de5 addresses the new rule's specific risk; refactoringrun_ruleitself to distinguish exit 2 (malformed pattern) from exit 1 (no match) is a separate cleanup and out of scope here.Local review (retroactive)
/local-reviewwas run retroactively against this branch (audit gap relative toguardrails.md§ Per-PR gate; the original push had only the 7-item mechanical audit). Outcome: 0 🔴 / 3grep -Eexits 2 on a malformed extended regex, butrun_rule's|| truemasks exit 2 as "no match" — silently disabling the rule if the operator setsWORK_EMAIL_PATTERNto 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.docs/build-spec.mdPhase 2 § Scope item 9 as worth verifying. Confirmed: anchor exists atbuild-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
feedback_personal_identity_only.mdwill need a small follow-up memory edit post-merge to note that work-email blocking is now proactiveTODO/FIXME/ commented-out code committed<NoWarn>without justification — N/A; no codePre-push self-audit (additive PRs)
Workflow YAML edit; no scraper / options class / extension / additive C# code surface. Reviewed against the audit criteria:
[ -n "${WORK_EMAIL_PATTERN:-}" ]is the toggle; rule fires when the secret is set, skips otherwisecatch; therun_rulegrep already uses|| trueto avoid the workflow crashing on no-match (and the new pre-validation handles exit 2 separately)fail=1is set and the script exits 1 at the bottomgit log -1 --format='%an <%ae>'→Jim Keeley <94459922+jkeeley2073@users.noreply.github.com>✅🤖 Generated with Claude Code