Skip to content

fix(ci) bypass errexit on WORK_EMAIL_PATTERN validation in sanitization#102

Merged
jkeeley2073 merged 1 commit into
mainfrom
Dev-SanitizationErrexitBypass
May 8, 2026
Merged

fix(ci) bypass errexit on WORK_EMAIL_PATTERN validation in sanitization#102
jkeeley2073 merged 1 commit into
mainfrom
Dev-SanitizationErrexitBypass

Conversation

@jkeeley2073
Copy link
Copy Markdown
Contributor

Summary

The Sanitization workflow has been silently skipping every scan rule after the Personal domain rule on every PR run since the work-email secret-validation guard was added (commit 9dc3de5). Discovered while debugging blocked PRs #99 and #100 — after PR #101 fixed the recursive-trap docs issue, the Personal email and Personal domain scans started passing as expected, but the workflow still exited 1 with no further output. Inspection of multiple runs confirms only the JungleTech / Personal email / Personal domain scan groups appear in logs. The Work email, PEM, RSA, Slack, GitHub PAT, AWS, and literal-credential-assignment scans never run.

Root cause. The shell runs under bash -e -o pipefail (GitHub Actions default for shell: bash). The validation line at sanitization.yml:109–110:

printf '' | grep -E "$WORK_EMAIL_PATTERN" >/dev/null 2>&1
pattern_rc=$?

intends to capture grep's exit code so the malformed-pattern guard can fire. But grep -E "<any-valid-pattern>" against empty stdin returns rc=1 ("no match"). pipefail surfaces that as the pipeline's exit code, and errexit aborts the script before pattern_rc=$? runs. Result: the script exits 1 with no further scans.

The originally-malformed-pattern case (rc=2) hit the same trap and also exited prematurely — the fact that PRs #99/#100 originally showed "Forbidden token(s) found" hits from the recursive-trap docs was incidental: those rules ran before the validation. Anything after never executed.

Fix. Convert the validation to an ||-list, which errexit explicitly skips per the bash manual:

pattern_rc=0
printf '' | grep -E "$WORK_EMAIL_PATTERN" >/dev/null 2>&1 || pattern_rc=$?

Default-zero pattern_rc means a theoretical successful grep (rc=0 on empty input) leaves it at 0; any non-zero exit captures the real code. The downstream if [ "$pattern_rc" -eq 2 ] malformed-pattern guard works unchanged.

Test Plan

  • Local sanity check: set -e -o pipefail; pattern_rc=0; printf '' | grep -E '@distilledtech\.com' >/dev/null 2>&1 || pattern_rc=$?; echo $pattern_rc → prints 1 (no match against empty input — expected, no script abort)
  • Local sanity check with malformed pattern: set -e -o pipefail; pattern_rc=0; printf '' | grep -E '[invalid' >/dev/null 2>&1 || pattern_rc=$?; echo $pattern_rc → prints 2 (parse error, captured correctly)
  • Diff is mechanical (1-line behavior change + extensive comment update); no source code changes; build remains green by definition
  • Post-merge verification: PRs chore(eval) Phase 4 W1-3 hardening — mfg-aware re-curation skip-on-mismatch #99 and fix(opdb) treat blank strings as null in OpdbMachineMapper fallback chain #100 retrigger and show green Sanitization, and the Sanitization log shows all expected scan groups (Work email, PEM, RSA, Slack, GitHub PAT, AWS, password/api-key assignments)

Security implications

This fix has higher stakes than a typical CI tweak. Every credential-detection rule has been a no-op on every PR check since the guard was added. A leak of an AWS access key, a GitHub PAT, a Slack token, or a password literal in committed code would not have been blocked by this workflow during that window.

Mitigations already in place that limited blast radius:

  • The personal-noreply identity guard (verified per feedback_personal_identity_only.md) means commits weren't accidentally tagged with work-email author identity
  • CodeQL runs separately and catches some secret-shape patterns
  • The JungleTech / Personal email / Personal domain scans did run, so prior-client-name and personal-email exposures were still blocked

But the tens of additional rules — covering most actual secret leakage scenarios — were silent. After this merges, those rules become live for the first time. Possible (small) follow-up: run a sweep of recent merged PRs against the now-active rule set to confirm no historical leaks slipped through. Tracked as Phase 4.x follow-up; not blocking this PR.

Out of Scope

  • Architectural fix (move the personal-email and personal-domain patterns into secrets, mirroring WORK_EMAIL_PATTERN). Tracked as a Phase 4.x follow-up; doesn't block this PR.
  • Sweep of recently-merged PRs for historical credential leaks. Tracked as Phase 4.x follow-up; the workflow now-fixed and CodeQL covering forward.

Checklist

  • CI is green (build + test + coverage + CodeQL + sanitization) — Sanitization on this PR is itself the validation; expected green now
  • PR title follows the Conventional Commits format
  • If this is a new architectural decision, an ADR has been added — N/A (workflow correctness fix)
  • If user-visible behavior changes, README.md and/or docs/ are updated — N/A (CI-internal)
  • If a memory in ~/.claude/projects/c--projects-PinballWizard/memory/ is now stale, it has been updated or removed — N/A
  • No TODO / FIXME / commented-out code committed
  • No new entries in <NoWarn> without a comment — N/A

Pre-push self-audit

Step 0 — /local-review (qualitative)

  • Skipped — workflow-only, single-line behavior change with extensive WHY comment alongside. Per .claude/skills/local-review/SKILL.md § "When to invoke": doc/CI-only changes may skip.

Step 1 — Mechanical checklist

  • Every new *Options property has at least one real getter call in src/ — N/A
  • Sibling-diffed against the closest existing implementation — diffed against the original 9dc3de5 line; the original intent is preserved, the runtime behavior is corrected
  • No bare catch { } — N/A
  • New ISourceScraper? — N/A
  • Tests assert behavior, not just structure — N/A; the workflow's behavior is asserted by its own runs (which will demonstrate the post-Personal-domain scans appearing in logs)
  • Build is zero-warning — N/A (no .cs)
  • git log -1 --format='%an <%ae>' shows personal noreply, not work email — confirmed 94459922+jkeeley2073@users.noreply.github.com

The Sanitization workflow has been silently skipping every scan rule
after the Personal domain rule on every PR run since the work-email
secret-validation guard was added. Confirmed by inspecting log output
across multiple runs: only the JungleTech, Personal email, and
Personal domain scan groups appear; the Work email, PEM, RSA, Slack,
GitHub PAT, AWS, and literal-credential-assignment scans never run.

Root cause. The shell runs under `bash -e -o pipefail` (GitHub
Actions default). The validation line:

    printf '' | grep -E "$WORK_EMAIL_PATTERN" >/dev/null 2>&1
    pattern_rc=$?

intends to capture grep's exit code so the malformed-pattern guard
can fire. But `grep -E "<any-valid-pattern>"` against empty stdin
returns rc=1 ("no match"). pipefail surfaces that as the pipeline's
exit code, and errexit aborts the script before `pattern_rc=$?`
runs. Result: the script exits 1 with no further scans.

The originally-malformed-pattern case (rc=2) hit the same trap and
also exited prematurely — the fact that PRs #99/#100 originally
showed "Forbidden token(s) found" hits from the recursive-trap rules
was incidental: those rules ran BEFORE the validation. Anything
after never executed.

Fix. Convert the validation to an ||-list, which errexit explicitly
skips per the bash manual:

    pattern_rc=0
    printf '' | grep -E "$WORK_EMAIL_PATTERN" >/dev/null 2>&1 || pattern_rc=$?

Default-zero pattern_rc means a successful grep (rc=0, theoretical
on empty input) leaves it at 0; any non-zero exit captures the real
code. The downstream `if [ "$pattern_rc" -eq 2 ]` malformed-pattern
guard works unchanged.

Why this matters. Every scan after the Personal-domain rule —
including critical credential-detection rules for AWS keys, GitHub
PATs, Slack tokens, RSA/PEM keys, and literal password/api-key
assignments — has been a no-op on every PR check. A real credential
leak past those rules would have been masked. Fixing the errexit
bypass restores all rules to working order.

After this merges:
- The Work email rule will run if the WORK_EMAIL_PATTERN secret is
  set (it is, as of this session)
- The credential-detection rules will run for the first time since
  the guard was added in 9dc3de5
- PRs #99 and #100 (currently blocked) should retrigger CI green
@jkeeley2073 jkeeley2073 added the claude-code Generated with Claude Code label May 8, 2026
@jkeeley2073 jkeeley2073 merged commit fd85771 into main May 8, 2026
4 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