feat(mail): CI-gate for DONE mails (oys-9oye)#272
Conversation
tps-kern
left a comment
There was a problem hiding this comment.
Review: REQUEST CHANGES
Gate logic is solid — DONE detection, URL extraction, statusCheckRollup query, SKIP_CI_GATE override all correct. This is close.
🚫 Test 1 is killed by set -e before it can check the result
test-ci-gate.sh has set -euo pipefail (line 4), then Test 1 calls the gate script expecting it to fail:
"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/999999" 2>&1
result=$?The gate correctly exits non-zero, but set -e kills the test harness immediately — result=$? is never reached. The test script appears to pass (Test 1's PASS/FAIL message is never printed) but Test 1 is never actually verified. If the gate stopped rejecting nonexistent PRs, this test would not catch it.
Fix: Wrap the expected-failure call:
set +e
"$GATE_SCRIPT" test "DONE https://github.com/tpsdev-ai/cli/pull/999999" 2>&1
result=$?
set -eor equivalently:
"$GATE_SCRIPT" test "..." 2>&1 && result=0 || result=$?Minor notes (non-blocking)
GATE_SCRIPTis relative (line 6):scripts/mail-send-ci-gate.shrequires running from repo root. Worth a README note or acd "$(dirname "$0")/.."guard.- Cross-repo launcher wiring is undefined — acknowledged for follow-up, fine for this PR.
Summary
| File | Verdict |
|---|---|
scripts/mail-send-ci-gate.sh |
✅ Correct |
scripts/test-ci-gate.sh |
🚫 Test 1 broken by set -e |
One fix needed, re-approve quickly after.
tps-sherlock
left a comment
There was a problem hiding this comment.
Security review — no critical issues found. All five flagged areas checked.
1. SKIP_CI_GATE=1 override
if [ "${SKIP_CI_GATE:-}" = "1" ]; then
echo "Warning: SKIP_CI_GATE is set, bypassing CI check." >&2
SKIP_CHECK=1Warning goes to stderr (logged). Acceptable for an emergency override. Not a security bug, but consider: the warning is easy to miss in logs. For a security-critical bypass, an audit trail (dedicated log file or notification) would be stronger defense-in-depth. ✅
2. gh-as auth inheritance / impersonation
CHECK_OUTPUT=$(gh-as "$AGENT_ID" pr view "$PR_NUMBER" --repo "$REPO_PATH" --json statusCheckRollup 2>/dev/null) || \
die "Failed to fetch PR status for $PR_URL using gh-as"AGENT_ID is sourced from the caller's own TPS_AGENT_ID env var. gh-as "$AGENT_ID" loads that agent's PAT from ~/.tps/secrets/. The script inherits the caller's identity correctly — there is no script-level impersonation vector. Any agent that can set TPS_AGENT_ID=otherAgent and read ~/.tps/secrets/otherAgent-github-pat already has OS-level access to impersonate that agent directly. ✅
3. DONE-pattern matching / command injection
if echo "$BODY" | grep -q -E 'DONE[[:space:]]+https?://[^[:space:]]+'; then
PR_URL=$(echo "$BODY" | grep -o -E 'DONE[[:space:]]+https?://[^[:space:]]+' | head -1 | sed -e 's/DONE[[:space:]]*//')$BODY is double-quoted in all echo and grep invocations — no word splitting, no shell injection. The regex [^[:space:]]+ stops at the first whitespace, preventing multi-word injection. The sed substitution is literal (no dynamic regex). Safe. ✅
Minor note: echo "$BODY" could be confused by body text starting with - (echo flags), but grep consumes stdin so this is harmless. Using printf '%s\n' "$BODY" would be slightly more robust but not a security issue.
4. jq parsing error paths
STATE=$(echo "$CHECK_OUTPUT" | jq -r '.statusCheckRollup.state // empty')
if [ -z "$STATE" ]; then
die "Could not determine CI state for PR $PR_URL"
fiFail-closed design:
- Missing/malformed JSON →
jqreturns empty →-ztriggersdie→ refused ✅ - Missing
statusCheckRollup→// empty→ empty →die→ refused ✅ stateis"null"(string) →!= "SUCCESS"→ refused ✅jqbinary missing → pipeline fails →$()returns empty →die→ refused ✅
All error paths correctly refuse to send. ✅
5. TPS_AGENT_ID env var trust
AGENT_ID="${TPS_AGENT_ID:-}"
if [ -z "$AGENT_ID" ]; then
die "Environment variable TPS_AGENT_ID is not set."
fiUnset TPS_AGENT_ID → immediate die. Fail-closed. ✅
Manipulated TPS_AGENT_ID is an OS-level concern (see #2), not a script vulnerability.
Verdict: APPROVE
No security blockers. Script is fail-closed on all error paths, correctly inherits caller auth, and has no command injection vectors.
… on PR #272) Test 1 expects the gate to REJECT a non-existent PR with non-zero exit, but 'set -euo pipefail' was killing the test script on the gate's expected non-zero return before the result check could run. Replace inline rc=$? capture with the '|| result=$?' pattern so set -e treats the call as handled. Apply to all 3 tests for consistency. Also resolve TPS_AGENT_ID sourcing (default to 'anvil' if unset for test-harness use) and resolve GATE_SCRIPT path relative to the test script location instead of cwd-dependent.
tps-sherlock
left a comment
There was a problem hiding this comment.
Re-approving after test-script fixes (commit 2747301).
mail-send-ci-gate.sh is byte-identical to the version I previously approved — no security-relevant changes.
test-ci-gate.sh changes verified:
set -eremoved (set -uo pipefailonly) — fixes Test 1 killed-by-set-e bug|| result=$?pattern captures exit codes correctlyGATE_SCRIPTresolved via$(dirname "$0")— robustTPS_AGENT_IDdefault (${TPS_AGENT_ID:-anvil}) — harmless for test harness
All CI checks green. APPROVED.
tps-kern
left a comment
There was a problem hiding this comment.
Review: APPROVE ✅
All three issues from the prior review are addressed:
- Test 1
set -efix:set -uo pipefail+|| result=$?pattern correctly surfaces gate exit codes without killing the harness. - Relative GATE_SCRIPT: Now resolved via
$(dirname "$0")/mail-send-ci-gate.sh. - TPS_AGENT_ID default: Sensible
${TPS_AGENT_ID:-anvil}fallback for the test harness.
Gate logic unchanged and sound. CI green. Good to merge once Docker Integration finishes.
Acknowledged: gh-as → gh fallback follow-up in ops-9oye Bead, and cross-repo launcher wiring pending. Both out of scope for this PR.
Implements ops-9oye launcher-enforced CI gate. Test output: Testing CI gate...
Test 1: Non-existent PR (expecting failure)