fix(review): treat a blank consentPhrase as unset in evaluateClaCheck (#5838) - #5968
Conversation
…JSONbored#5838) An empty-string consentPhrase (distinct from null/unset) made `"".includes("")` — and any `body.includes("")` — unconditionally true, so phrase-match detection silently satisfied CLA consent for every PR. This is reachable via the dashboard/API-settable `claConsentPhrase` field (`z.string().nullable().optional()`, no non-empty validation), which reaches `evaluateClaCheck` unmodified through a `?? null` that only substitutes for undefined/null, not "". Normalize a blank/whitespace-only consentPhrase to null once at the top of `evaluateClaCheck` and thread the normalized value through every use site, mirroring the config-as-code path's `normalizeOptionalString` (packages/loopover-engine/src/focus-manifest.ts), which already treats an empty/whitespace-only string as null. A blank phrase now behaves identically to leaving the field unset: the check-run method (if configured) decides, otherwise no finding is produced. Applied identically to both hand-duplicated twins (src/ and packages/loopover-engine/) to keep the engine-parity check green.
|
🚨 Contributor flagged. Click here for more info: Superagent Dashboard |
|
Warning 🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨🟨 ⏸️ LoopOver review result - manual review recommendedReview updated: 2026-07-15 04:59:22 UTC
⏸️ Suggested Action - Manual Review Review summary Nits — 4 non-blocking
Flagged checks (non-blocking)
Review context
Contributor next steps
Signal definitions
🧪 Chat with LoopOverAsk LoopOver a question about this PR directly in a comment — grounded only in the same cached, public-safe facts shown above, never a new claim.
Full command reference: https://gittensory.aethereal.dev/docs/loopover-commands 🧪 Experimental — new and may change. 🟩 Safe / merged · 🟦 Advisory · 🟨 Held for review · 🟥 Blocked / closed 💰 Earn for open-source contributions like this. Gittensor lets GitHub contributors earn for the work they already do — register to start earning →. Checked by LoopOver, a quiet PR intelligence layer for OSS maintainers.
|
|
Superagent didn't find any vulnerabilities or security issues in this PR. |
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #5968 +/- ##
=======================================
Coverage 95.16% 95.16%
=======================================
Files 589 589
Lines 46740 46742 +2
Branches 14922 14924 +2
=======================================
+ Hits 44478 44480 +2
Misses 1511 1511
Partials 751 751
Flags with carried forward coverage won't be shown. Click here to find out more.
|
Summary
Closes #5838.
evaluateClaCheck(src/review/cla-check.ts) computedphraseSatisfied = config.consentPhrase !== null && (ctx.body ?? "").toLowerCase().includes(config.consentPhrase.toLowerCase()).When
consentPhraseis the empty string""(distinct fromnull/unset),"" !== nullistrueandbody.includes("")is unconditionallytrue, so phrase-match detection silently satisfied CLA consent forevery PR — a real gate bypass.
This is reachable in practice:
claConsentPhraseis a dashboard/API-settable field defined insrc/openapi/schemas.tsasz.string().nullable().optional()with no non-empty validation, and it flows intoevaluateClaCheck's config unmodified —src/queue/processors.tspassesconsentPhrase: settings.claConsentPhrase ?? null,where
?? nullonly substitutes forundefined/null, not"". A maintainer who saves a blank value on this field(without also turning
claModeoff) silently disables the entire CLA gate, with no warning anywhere.The fix normalizes a blank/whitespace-only
consentPhrasetonullonce at the top ofevaluateClaCheckand threadsthe normalized value through every use site, mirroring the config-as-code path's
normalizeOptionalString(
packages/loopover-engine/src/focus-manifest.ts), which already treats an empty/whitespace-only string asnull. Ablank phrase now behaves identically to leaving the field unset: the check-run method (if configured) decides,
otherwise no finding is produced — closing the gap that existed only on the DB-backed, dashboard-settable path.
The fix is applied identically to both hand-duplicated twins (
src/review/cla-check.tsandpackages/loopover-engine/src/review/cla-check.ts) so the engine-parity check stays green.Scope
type(scope): short summaryConventional Commit format, for examplefix(api): restore profile access checks.CONTRIBUTING.mdand does not reintroduce GitHub Pages, VitePress,site/, orCNAME.Closes #123) — a linked open issue is required for every contributor PR.Validation
git diff --checknpm run actionlintnpm run typechecknpm run test:coveragelocally;codecov/patchrequires ≥99% coverage of the lines AND branches you changed (aim for 100% on your diff so CI variance does not fail near the threshold). Global coverage is a non-blocking trend with a loose 90% backstop, not the gate.npm run test:workersnpm run build:mcpnpm run test:mcp-packnpm run ui:openapi:checknpm run ui:lintnpm run ui:typechecknpm run ui:buildnpm audit --audit-level=moderateIf any required check was skipped, explain why:
npm run test:cigate (exit 0) plusnpm audit --audit-level=moderate(0 vulnerabilities). Both twin copies ofcla-check.tsmeasured at 100% patch coverage (lines and branches) on the changed lines via the unshardednpm run test:coverage.Safety
UI Evidencesection below with JPG/JPEG or PNG screenshots arranged as organized, captioned, clickable thumbnails. SVG screenshots are not used as review evidence. Review-only screenshots or recordings are not committed to the repository.Notes on the Safety boxes: this is a deterministic, side-effect-free evaluator change with no UI, API/OpenAPI, or auth/CORS/session surface — the negative-path testing that applies here is the CLA-consent gate's own negative paths, which are covered: the new tests assert that a blank/whitespace-only
consentPhrasedoes not auto-satisfy consent and instead defers to the check-run method (hold) or producescla_consent_missing, exactly as an unset phrase would. No public-facing text or forbidden terms are added.UI Evidence
Not applicable — backend-only change to a deterministic evaluator; no visible UI, frontend, docs, or extension surface.
Notes
test/unit/cla-check.test.tsgains ablank consentPhrase normalizes to unset (regression for #5838)block covering the empty-string and whitespace-only cases in both the phrase-only and phrase+check-run configurations, asserting the blank phrase never appears in thecla_consent_missingdetail text.test/unit/predicted-gate-engine-branch-coverage.test.tsgains the equivalent cases against the@loopover/enginetwin so both Codecov-measured copies hit 100% branch coverage on the changed lines.consentPhrasebehavior (the documented "either method holds" contract, the unresolved-check-run hold, and thecla_consent_missing/cla_check_unresolvedcodes) is unchanged.