Skip to content

fix(settings): bound contributor open caps - #3977

Merged
JSONbored merged 1 commit into
mainfrom
codex/fix-contributor-pr-cap-bypass-vulnerability
Jul 7, 2026
Merged

fix(settings): bound contributor open caps#3977
JSONbored merged 1 commit into
mainfrom
codex/fix-contributor-pr-cap-bypass-vulnerability

Conversation

@JSONbored

Copy link
Copy Markdown
Owner

Motivation

  • Prevent configured per-repository contributor open-PR/issue caps from exceeding the fixed live-verification sample budget (100 rows) which made enforcement bypassable for caps >100.

Description

  • Add a shared cap constant MAX_CONTRIBUTOR_OPEN_ITEM_CAP = 100 and document the bound in src/types.ts.
  • Clamp stored/normalized caps by changing normalizeOpenItemCap to Math.min(value, MAX_CONTRIBUTOR_OPEN_ITEM_CAP) in src/db/repositories.ts so DB-roundtrips cannot store values above the enforcement sample.
  • Clamp manifest-parsed caps with a normalizeOptionalContributorOpenItemCap helper in packages/gittensory-engine/src/focus-manifest.ts so .gittensory.yml overrides also cannot exceed the live-check budget while preserving null clears and invalid-value warnings.
  • Tighten the public API validation by adding .max(MAX_CONTRIBUTOR_OPEN_ITEM_CAP) for contributorOpenPrCap and contributorOpenIssueCap in src/openapi/schemas.ts and regenerate the UI OpenAPI artifact.
  • Add regression tests and test adjustments in test/unit/data-spine.test.ts, test/unit/focus-manifest.test.ts, and test/unit/ci-openapi-settings-parity.test.ts that assert clamping and schema rejection above 100.

Testing

  • Ran git diff --check which produced no issues.
  • Ran targeted unit suites with npx vitest run test/unit/data-spine.test.ts test/unit/focus-manifest.test.ts test/unit/ci-openapi-settings-parity.test.ts test/unit/duplicate-winner.test.ts and they all passed locally.
  • Ran npm run typecheck and npm run ui:openapi:check (the OpenAPI file was regenerated) and both succeeded.
  • Attempted npm run test:coverage and npm audit --audit-level=moderate; test:coverage timed out / hit long-running existing suites in this environment and was aborted, and npm audit failed with a 403 from the npm audit endpoint (network registry error), so full coverage/audit could not be completed here.

Codex Task

@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Jul 7, 2026

Copy link
Copy Markdown

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
gittensory-ui 59eeb91 Commit Preview URL

Branch Preview URL
Jul 07 2026, 08:33 AM

@superagent-security

Copy link
Copy Markdown
Contributor

Superagent didn't find any vulnerabilities or security issues in this PR.

@codecov

codecov Bot commented Jul 7, 2026

Copy link
Copy Markdown

❌ 9 Tests Failed:

Tests completed Failed Passed Skipped
10991 9 10982 7
View the top 3 failed test(s) by shortest run time
test/unit/gittensory-focus-manifest.test.ts > Gittensory repo focus manifest > keeps bundled YAML aligned with the committed .gittensory.yml file
Stack Traces | 0.0205s run time
AssertionError: expected '# Gittensory repo focus manifest — ma…' to be '# Gittensory repo focus manifest — ma…' // Object.is equality

- Expected
+ Received

@@ -35,11 +35,11 @@
    # enabled: false             # set false to disable the gate from config (turning it on is a dashboard setting)
    linkedIssue: advisory        # block | advisory | off — issues aren't always available; advise, don't block
    duplicates: block            # block | advisory | off — block obvious duplicate PRs
    readiness:
      mode: advisory             # advisory | off — readiness score is informational and never blocks the Gate
-     minScore: 40               # lowered from 60: 73% false-positive rate showed PRs scoring 40-59 merge freely
+     minScore: 60
    # aiReview:                  # opt-in AI maintainer review (off by default; needs the AI flags enabled)
    #   mode: advisory           # block | advisory | off — block only blocks on a dual-model consensus defect
    #   byok: false              # use a maintainer Anthropic/OpenAI key for the write-up; consensus stays on the free/default reviewer
    #   allAuthors: false        # true reviews every PR author with the selected self-host model(s)
    #   provider: anthropic      # anthropic | openai — which BYOK provider (the secret key is set via the dashboard, never here)

 ❯ test/unit/gittensory-focus-manifest.test.ts:37:56
test/unit/moderation-config-db.test.ts > global moderation config DB round-trip (#selfhost-mod-engine) > a violationDecayDays AT the max is preserved unclamped (boundary, not just strictly-under)
Stack Traces | 0.409s run time
AssertionError: expected 100 to be 3650 // Object.is equality

- Expected
+ Received

- 3650
+ 100

 ❯ test/unit/moderation-config-db.test.ts:101:41
test/unit/moderation-config-db.test.ts > global moderation config DB round-trip (#selfhost-mod-engine) > REGRESSION (gate-flagged): violationDecayDays above MAX_MODERATION_VIOLATION_DECAY_DAYS is CLAMPED, not passed through raw -- an unbounded value overflows Date arithmetic on the live close path
Stack Traces | 0.42s run time
AssertionError: expected 100 to be 3650 // Object.is equality

- Expected
+ Received

- 3650
+ 100

 ❯ test/unit/moderation-config-db.test.ts:91:41
test/unit/moderation-config-db.test.ts > global moderation config DB round-trip (#selfhost-mod-engine) > a raw DB row with an over-max violation_decay_days is also clamped on READ (not just on write)
Stack Traces | 0.449s run time
AssertionError: expected 100 to be 3650 // Object.is equality

- Expected
+ Received

- 3650
+ 100

 ❯ test/unit/moderation-config-db.test.ts:108:41
View the full list of 5 ❄️ flaky test(s)
test/unit/slop.test.ts > buildNonSubstantivePaddingFinding (#561 path-matcher signal) > contributes to the aggregate slop assessment without colliding with trivial-churn

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.00177s run time
AssertionError: expected 'low' to be 'elevated' // Object.is equality

Expected: "elevated"
Received: "low"

 ❯ test/unit/slop.test.ts:628:25
test/unit/slop.test.ts > buildSlopAssessment > raises trivial-churn slop for high-churn diffs with minimal source lines

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.00177s run time
AssertionError: expected 'low' to be 'elevated' // Object.is equality

Expected: "elevated"
Received: "low"

 ❯ test/unit/slop.test.ts:199:25
test/unit/slop.test.ts > buildSlopAssessment > reaches the high band when multiple strong signals stack

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.00152s run time
AssertionError: expected 'low' to be 'elevated' // Object.is equality

Expected: "elevated"
Received: "low"

 ❯ test/unit/slop.test.ts:312:27
test/unit/slop.test.ts > buildSlopAssessment > stacks the duplicate-cluster weight with another signal into the expected band (#563)

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.0259s run time
AssertionError: expected 'low' to be 'elevated' // Object.is equality

Expected: "elevated"
Received: "low"

 ❯ test/unit/slop.test.ts:124:25
test/unit/slop.test.ts > slop golden fixtures & determinism (#565) > scores the 'elevated — untested code change insid…' fixture to its documented band

Flake rate in main: 100.00% (Passed 0 times, Failed 2 times)

Stack Traces | 0.00302s run time
AssertionError: expected 'low' to be 'elevated' // Object.is equality

Expected: "elevated"
Received: "low"

 ❯ test/unit/slop.test.ts:664:25

To view more test analytics, go to the Test Analytics Dashboard
📋 Got 3 mins? Take this short survey to help us improve Test Analytics.

@loopover-orb loopover-orb Bot added the gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. label Jul 7, 2026
@loopover-orb

loopover-orb Bot commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

Caution

🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥🟥

🛑 Gittensory review result - fixes required

Review updated: 2026-07-07 08:42:17 UTC

8 files · 1 AI reviewer · 2 blockers · readiness 93/100 · CI failing · blocked

🛑 Suggested Action - Manual Review

  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue — Link the relevant issue (for example Closes #123) before opening the PR.

Review summary
This PR closes a real enforcement gap: contributor open-PR/issue caps stored in the DB, parsed from .gittensory.yml, or accepted via the public API could previously exceed the fixed 100-row live-verification sample, making the cap silently unenforceable above that threshold. The fix is applied consistently at all three layers (repositories.ts normalizeOpenItemCap, focus-manifest.ts normalizeOptionalContributorOpenItemCap, openapi/schemas.ts zod .max()) using a single shared MAX_CONTRIBUTOR_OPEN_ITEM_CAP constant, with the OpenAPI artifact regenerated to match. Test coverage exercises both the clamp path (101/150 → 100) and the schema-rejection boundary (101 throws, 100 passes), which is exactly the branch behavior this diff introduces.

Nits — 5 non-blocking
  • The PR description doesn't cite the specific open issue this closes (only references prior feat(agent-actions): configurable per-contributor open PR/issue cap with auto-close over the limit #2270 context in surrounding comments) — confirm there's an eligible linked issue per repo convention.
  • test/unit/focus-manifest.test.ts covers 101/150 clamping to 100 but doesn't add a case for the exact boundary value 100 itself in the manifest layer (only the DB-layer test in data-spine.test.ts covers the 100-exact case).
  • src/db/repositories.ts:6758 comment now says 'clamped to the fixed live-verification sample budget' without naming where that 100-row sample constant lives — a pointer/reference to the actual sample-size constant would make the coupling easier to audit later.
  • Consider asserting in a test that the sample-budget constant used by the live-verification enforcement path (wherever it's defined) is literally reused or kept in lockstep with MAX_CONTRIBUTOR_OPEN_ITEM_CAP, so a future change to one can't silently desync from the other.
  • Given three independent call sites now import MAX_CONTRIBUTOR_OPEN_ITEM_CAP from src/types.ts, a single JSDoc example there showing the clamp behavior would help contributors editing any one layer keep the others in sync.

Why this is blocked

  • No linked issue detected — If this PR is intended to solve an issue, link it explicitly in the PR body.
  • Maintainer requires a linked issue — Link the relevant issue (for example Closes #123) before opening the PR.

CI checks failing

  • validate
  • validate-code
Signal Result Evidence
Code review ❌ 2 blockers 1 reviewer
Linked issue ⚠️ Missing No linked issue or no-issue rationale found.
Related work ✅ No active overlap found No same-issue or scoped active PR overlap found.
Change scope ✅ 20/20 Low review scope from cached public metadata (no linked issue context).
Validation posture ✅ 25/25 PR body includes validation/test evidence.
Contributor workload ✅ 10/10 Author activity: 51 registered-repo PR(s), 43 merged, 343 issue(s).
Contributor context ✅ Confirmed Gittensor contributor JSONbored; Gittensor profile; 51 PR(s), 343 issue(s).
Gate result ❌ Blocking Repo-configured hard blocker found.
Review context
  • Author: JSONbored
  • Role context: owner (maintainer lane)
  • Public audience mode: oss maintainer
  • Lane context: Repository is configured for direct PR review.
  • Public profile languages: Python, TypeScript, JavaScript, Ruby, Go, Kotlin, MDX, Shell
  • Official Gittensor activity: 51 PR(s), 343 issue(s).
  • PR-specific overlap: none found.
Contributor next steps
  • Treat this as maintainer-lane context rather than normal contributor-lane activity.
  • Explain no-issue PR.
  • Link the issue being solved, or explicitly explain why this is a no-issue PR.
Signal definitions
  • Related work = same linked issue, overlapping active PRs, or title/path similarity.
  • Change scope = cached public metadata such as size labels, draft state, and review-burden hints.
  • Validation posture = whether the PR provides enough public validation/test evidence for maintainer review.
  • Contributor workload = public contributor activity and cleanup pressure, not a repo-wide quality failure.
  • Contributor context = public GitHub/Gittensor identity context; non-Gittensor status is not a blocker.

🟩 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 Gittensory, a quiet PR intelligence layer for OSS maintainers.

  • Re-run Gittensory review

@loopover-orb loopover-orb Bot added the manual-review Gittensor contributor context label Jul 7, 2026
@JSONbored
JSONbored merged commit 9195349 into main Jul 7, 2026
8 of 10 checks passed
@JSONbored
JSONbored deleted the codex/fix-contributor-pr-cap-bypass-vulnerability branch July 7, 2026 09:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

gittensor:bug Gittensor-scored bug fix — scores a 0.05x multiplier. manual-review Gittensor contributor context

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant