Skip to content

Record bounty details when a PR with a bounty is merged#5468

Closed
SuyashJain17 wants to merge 14 commits intoOWASP-BLT:mainfrom
SuyashJain17:feat/bounty-recording
Closed

Record bounty details when a PR with a bounty is merged#5468
SuyashJain17 wants to merge 14 commits intoOWASP-BLT:mainfrom
SuyashJain17:feat/bounty-recording

Conversation

@SuyashJain17
Copy link

@SuyashJain17 SuyashJain17 commented Jan 14, 2026

Resolves #3941

What this PR does

This adds a small flow to record bounty details when a merged pull request closes an issue that has a $ bounty label.

When such a PR is merged, the bounty information is sent to the backend and stored so it can be paid out manually via GitHub Sponsors.

Why this change

There have been a few attempts to fully automate bounty payouts, but those changes are hard to review safely.
This keeps things simple by only recording the bounty, without automating any payments.

Scope

  • Records bounty information for merged PRs
  • Prevents duplicate recordings
  • Uses existing fields only
  • No payment automation or GitHub side effects

Notes

Actual payout is handled manually for now.
This keeps the flow easy to reason about and avoids touching financial automation at this stage.

Summary by CodeRabbit

  • Refactor

    • Bounty payout now records bounties for manual processing; automated sponsor/payment calls removed and responses simplified.
  • Bug Fixes

    • Added input validation for amounts and owner/contributor names, deterministic repo lookup with legacy fallback, and atomic checks to prevent duplicate/race payouts.
  • Chores

  • Tests

    • Tests adjusted and expanded for legacy-org fallback and boundary/validation scenarios (negative/zero/excessive amounts, invalid owner/username).

@github-actions
Copy link
Contributor

👋 Hi @SuyashJain17!

This pull request needs a peer review before it can be merged. Please request a review from a team member who is not:

  • The PR author
  • DonnieBLT
  • coderabbitai
  • copilot

Once a valid peer review is submitted, this check will pass automatically. Thank you!

@github-actions github-actions bot added the needs-peer-review PR needs peer review label Jan 14, 2026
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 14, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

Walkthrough

Replaces live GitHub Sponsors payouts with a recording workflow: the GitHub Action posts bounty details to a BLT API; backend validates input, locks the issue row, and records a sponsors_tx_id on the issue instead of performing sponsor payments or calling GitHub APIs. The Action also switched HTTP implementation and the default API base URL.

Changes

Cohort / File(s) Summary
Workflow Configuration
.github/workflows/bounty-payout.yml
Default API URL changed to https://owaspblt.org; replaced fetch-based call with Node https.request() building URL/options, added Content-Length and X-BLT-API-TOKEN headers, used req.write(data)/req.end(), buffered response handling, explicit status checks and error handling, and updated action pin to actions/github-script@v6.1.1.
Backend Payment Logic
website/views/bounty.py
Removed live GitHub Sponsors payment flow and several helper functions; added API token and JSON payload validation, numeric/bounds checks for amount, owner/repo/username format validation, deterministic repo lookup preferring organization.github_org with legacy fallback, atomic transaction with select_for_update to record a synthetic sponsors_tx_id (BOUNTY:<contributor>:<amount>:<pr_number>), and updated success response to indicate the bounty was recorded.
Test Suite
website/tests/test_bounty.py
Tests updated to match recording-based flow: removed payment mocks, switched to environment-based token patching, added github_org to Organization setup and a legacy-org fallback test, simplified success/duplicate assertions to check sponsors_tx_id presence, and added validation tests for negative/zero/excessive amounts and invalid owner/username formats.

Sequence Diagram(s)

sequenceDiagram
    participant GH_Action as GH Action
    participant BLT_API as BLT API
    participant Backend as Website Backend
    participant DB as Database

    GH_Action->>BLT_API: POST /bounty_payout (issue_number, repo, owner, amount, contributor, pr_number) + X-BLT-API-TOKEN
    BLT_API->>Backend: Forward request (validate token & payload)
    Backend->>Backend: Validate input (amount bounds, owner/repo/username formats)
    Backend->>DB: SELECT ... FOR UPDATE (lock issue row)
    Backend->>DB: UPDATE issue set sponsors_tx_id = "BOUNTY:<contributor>:<amount>:<pr_number>"
    Backend-->>BLT_API: 200 OK (recorded status, issue_number, amount, recipient)
    BLT_API-->>GH_Action: Workflow success
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR addresses the core requirement from #3941 (bounty recording when PR merges) but implements recording instead of live GitHub Sponsors payments, diverging from the original automated-payment objective. Clarify with stakeholders whether the record-only approach aligns with the original intent to auto-pay bounties, as the current implementation removes automated payment processing entirely.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately captures the main change: recording bounty details when merged PRs close bounty-labeled issues, reflecting the shift from automated payment to recording-based storage.
Out of Scope Changes check ✅ Passed All changes directly support bounty recording: workflow sends bounty data, backend validates and records it atomically, tests verify recording logic, and no unrelated functionality is modified.
Docstring Coverage ✅ Passed Docstring coverage is 87.50% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

📊 Monthly Leaderboard

Hi @SuyashJain17! Here's how you rank for January 2026:

Rank User PRs Reviews Comments Total
#22 @sharanyaa30 1 0 0 10
#23 @SuyashJain17 1 0 0 10
#24 @nehamanoj1105 0 0 5 10

Leaderboard based on contributions in January 2026. Keep up the great work! 🚀

@github-actions github-actions bot added the pre-commit: failed Pre-commit checks failed label Jan 14, 2026
Comment on lines +134 to +144
throw new Error(`Request failed: ${res.statusCode}`);
}
});
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Bounty payout request failed: ${response.status} - ${errorText}`);
}
req.on('error', (e) => {
throw new Error(`Request error: ${e.message}`);
});

const result = await response.json();
console.log('Bounty payout request successful:', result);
req.write(data);
req.end();

This comment was marked as outdated.

@github-actions github-actions bot added the tests: passed Django tests passed label Jan 14, 2026
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
website/views/bounty.py (1)

74-89: Potential race condition in duplicate check.

There's a TOCTOU (time-of-check-time-of-use) gap between checking sponsors_tx_id and saving. Two concurrent requests could both pass the check. In practice, this is low risk since workflow triggers are per-PR-merge, but for robustness consider using select_for_update():

♻️ Optional fix using select_for_update
-        github_issue = GitHubIssue.objects.filter(issue_id=issue_number, repo=repo).first()
+        github_issue = GitHubIssue.objects.select_for_update().filter(issue_id=issue_number, repo=repo).first()

Note: This requires the view to be wrapped in a transaction (e.g., @transaction.atomic).

🤖 Fix all issues with AI agents
In @.github/workflows/bounty-payout.yml:
- Around line 127-144: The https.request callback is asynchronous and its throw
statements inside res.on('end') and req.on('error') won’t surface to the Actions
step; wrap the whole request sequence in a Promise, return that Promise, call
reject(new Error(...)) instead of throw when res.statusCode >= 400 or on
req.on('error'), and call resolve() when the response is successful (e.g., 2xx)
after res.on('end'); ensure you perform req.write(data) and req.end() inside the
Promise executor. Use the existing https.request(options, (res) => { ... })
callback, res.on('data'), res.on('end'), req.on('error'), req.write(data) and
req.end() but convert their error/success handling to reject/resolve and return
the Promise so the GitHub Actions step can await failures.
🧹 Nitpick comments (3)
.github/workflows/bounty-payout.yml (1)

122-122: Use Buffer.byteLength() for accurate Content-Length.

data.length returns character count, not byte count. While GitHub usernames are ASCII-only (so this works in practice), using Buffer.byteLength(data) is more correct for HTTP headers.

♻️ Suggested fix
-                'Content-Length': data.length,
+                'Content-Length': Buffer.byteLength(data),
website/tests/test_bounty.py (2)

66-68: Consider asserting the sponsors_tx_id format.

The test only checks sponsors_tx_id is not None. Given the view stores a specific format (BOUNTY:username:amount:pr), consider asserting the expected value for stronger test coverage.

♻️ Suggested enhancement
         # Verify database was updated
         self.issue.refresh_from_db()
-        self.assertIsNotNone(self.issue.sponsors_tx_id)
+        self.assertEqual(self.issue.sponsors_tx_id, "BOUNTY:testuser:5000:456")

10-11: Consider adding tests for edge cases.

The test suite covers main paths well but could benefit from:

  1. Invalid numeric fields (e.g., "bounty_amount": "not_a_number") to test the ValueError handling at lines 51-57 in bounty.py.
  2. Invalid JSON body to test the JSONDecodeError handling.
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0d1eee1 and f818915.

📒 Files selected for processing (3)
  • .github/workflows/bounty-payout.yml
  • website/tests/test_bounty.py
  • website/views/bounty.py
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (7)
website/tests/test_bounty.py (2)

70-94: LGTM!

The duplicate detection test correctly pre-populates sponsors_tx_id and verifies the warning response. The test aligns well with the view's duplicate handling logic.


96-171: LGTM!

The error handling tests provide good coverage for authentication failures, missing fields, and entity lookup failures. The test cases align with the view's error handling paths.

website/views/bounty.py (4)

26-35: LGTM!

Good security practices: constant-time comparison via secrets.compare_digest prevents timing attacks, and proper handling of missing configuration returns a 500 without leaking details.


86-94: LGTM!

The bounty recording logic is clean. The sponsors_tx_id format includes all necessary information for manual processing, and the logging provides good audit trail without exposing sensitive data.


44-61: LGTM!

Input validation is thorough: required field checking, numeric type validation, and proper error responses. The extracted fields are used safely without injection risks.


74-84: LGTM!

Good idempotency handling - returning a 200 with a warning status for duplicates ensures the workflow doesn't fail on retries while clearly indicating the bounty was already recorded.

.github/workflows/bounty-payout.yml (1)

69-78: Bounty amount regex correctly requires two decimal places for consistency.

The regex /\$(\d+(?:\.\d{2})?)/ intentionally requires exactly two decimal places (or no decimals), matching patterns like $50 and $25.50 but not $25.5. This aligns with the code comment explicitly documenting both formats and standard currency representation. The cents conversion (* 100) confirms this is designed for standard two-decimal currency amounts. No evidence of single-decimal label formats (e.g., $25.5) being used in the codebase.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

@github-project-automation github-project-automation bot moved this from Backlog to Ready in 📌 OWASP BLT Project Board Jan 14, 2026
@SuyashJain17 SuyashJain17 force-pushed the feat/bounty-recording branch from f818915 to a738e65 Compare January 14, 2026 21:07
@github-actions github-actions bot added changes-requested PR has requested changes from a reviewer files-changed: 49 PR changes 49 files and removed files-changed: 3 PR changes 3 files labels Jan 14, 2026
@github-actions
Copy link
Contributor

github-actions bot commented Jan 14, 2026

❌ Console Statements Detected

Found 2 file(s) with console statements that should be removed.

Why remove console statements?

  • The project has sufficient error tracking in place
  • Console statements can expose sensitive information in production
  • They can clutter browser console logs
  • They may impact performance in production builds

Files with violations:

website/static/js/issue.js:

388:        console.error('Error fetching issues:', error);

website/static/organization/js/hunt_controller.js:

23:                    console.error('[add_prize] Could not find list-prize-container element');
42:                    console.error(`[add_prize] Could not find element: ${key}`);
107:            console.error('[add_prize] Error:', error);
239:                console.error('[PublishBughunt] Form not found');
260:                console.error('[PublishBughunt] CSRF token not found in form');
289:            console.error('[PublishBughunt] Error:', error);
307:            console.error('[displayLogoPreview] Required elements not found');
401:        console.error('Error:', error);
512:            console.error('One or more required elements not found');
559:                console.error('Error reading file');
582:            console.error('One or more required elements not found');

How to fix:

  1. Remove or comment out all console.* statements from your code (console.log, console.error, console.warn, etc.)
  2. Use the project's existing error tracking system for debugging
  3. For temporary debugging during development, comment out console statements: // console.log()

Note:

  • All console methods (log, error, warn, debug, info, etc.) are forbidden.
  • If console statements are inside a multi-line comment block (/* ... */), they may still be flagged. Please manually verify.

Please remove all console statements and push your changes.

@github-actions github-actions bot added has-console-statements PR contains console statements that need to be removed pre-commit: passed Pre-commit checks passed and removed pre-commit: failed Pre-commit checks failed labels Jan 14, 2026
Comment on lines +83 to +92
status=200,
)

# Process payment via GitHub Sponsors API
transaction_id = process_github_sponsors_payment(
username=contributor_username,
amount=bounty_amount,
note=f"Bounty for PR #{pr_number} resolving issue #{issue_number} in {owner_name}/{repo_name}",
)

if not transaction_id:
logger.error(f"Failed to process GitHub Sponsors payment for issue #{issue_number}")
return JsonResponse({"status": "error", "message": "Payment processing failed"}, status=500)
# Record the bounty for manual payment via GitHub Sponsors
# Format: BOUNTY:<contributor>:<amount_cents>:<pr>
github_issue.sponsors_tx_id = f"BOUNTY:{contributor_username}:{bounty_amount}:{pr_number}"
github_issue.save()

logger.info(
f"Successfully processed bounty payment: ${bounty_amount / 100:.2f} to {contributor_username} "
f"Recorded bounty: ${bounty_amount / 100:.2f} to {contributor_username} "

This comment was marked as outdated.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
website/static/js/issue.js (1)

387-389: Remove console statement flagged by CI pipeline.

The pipeline failure indicates console.error usage at line 388. Consider using a structured logging approach or removing the console statement to pass the CI check.

Suggested fix
     } catch (error) {
-        console.error('Error fetching issues:', error);
         if (!append) {
             hideSuggestionBox();
         } else {

Alternatively, if error visibility is needed, consider using a centralized error reporting mechanism similar to safeLog in gsoc_pr_report.js.

🤖 Fix all issues with AI agents
In @.github/workflows/bounty-payout.yml:
- Around line 120-124: The Content-Length header is computed using data.length
which counts UTF-16 code units and will be wrong for non-ASCII
contributor_username; update the headers object to compute the byte length with
Buffer.byteLength(data, 'utf8') (replace data.length with
Buffer.byteLength(data, 'utf8')) so the Content-Length value reflects actual
bytes sent.
♻️ Duplicate comments (1)
.github/workflows/bounty-payout.yml (1)

127-144: Critical: Async https.request is not awaited - workflow will complete before request finishes.

The callback-based https.request is asynchronous. The script exits immediately after req.end() without waiting for the response. Errors thrown in callbacks (res.on('end'), req.on('error')) won't propagate to the workflow step - the step will always succeed regardless of the actual HTTP outcome.

🔧 Wrap in Promise and await
-            const req = https.request(options, (res) => {
-              let body = '';
-              res.on('data', chunk => body += chunk);
-              res.on('end', () => {
-                console.log(`Status: ${res.statusCode}`);
-                console.log(`Response: ${body}`);
-                if (res.statusCode >= 400) {
-                  throw new Error(`Request failed: ${res.statusCode}`);
-                }
-              });
-            });
-
-            req.on('error', (e) => {
-              throw new Error(`Request error: ${e.message}`);
-            });
-
-            req.write(data);
-            req.end();
+            await new Promise((resolve, reject) => {
+              const req = https.request(options, (res) => {
+                let body = '';
+                res.on('data', chunk => body += chunk);
+                res.on('end', () => {
+                  console.log(`Status: ${res.statusCode}`);
+                  console.log(`Response: ${body}`);
+                  if (res.statusCode >= 400) {
+                    reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
+                  } else {
+                    resolve(body);
+                  }
+                });
+              });
+
+              req.on('error', (e) => {
+                reject(new Error(`Request error: ${e.message}`));
+              });
+
+              req.write(data);
+              req.end();
+            });
🧹 Nitpick comments (6)
website/documents/BltTerms.md (1)

94-94: Trailing newline fix looks good.

The static analysis tool flags the email as a bare URL (MD034). Optionally, you could wrap it in angle brackets to create a proper markdown autolink: <blt-support@owasp.org>. This is a minor stylistic preference and not blocking.

📝 Optional markdown style fix
-Questions about the Terms of Service should be sent to blt-support@owasp.org.
+Questions about the Terms of Service should be sent to <blt-support@owasp.org>.
website/documents/BltWeeklyActivityOfContributor.md (1)

44-44: LGTM – Documentation formatting change.

This appears to be a formatting-only adjustment (likely an end-of-file newline addition) with no functional impact.

Optional style nitpick: The static analysis tool suggests replacing "a large number of" with "many" or "numerous" to reduce wordiness, though the current phrasing is perfectly acceptable for technical documentation.

website/views/bounty.py (1)

50-57: Consider validating that bounty_amount is positive.

The numeric validation ensures the value is an integer but doesn't reject zero or negative amounts, which would be invalid for a bounty.

🛠️ Suggested fix
         try:
             issue_number = int(data["issue_number"])
             pr_number = int(data["pr_number"])
             bounty_amount = int(data["bounty_amount"])
+            if bounty_amount <= 0:
+                raise ValueError("bounty_amount must be positive")
         except (ValueError, TypeError):
             logger.warning("Invalid numeric fields in request")
             return JsonResponse({"status": "error", "message": "Invalid numeric fields"}, status=400)
website/tests/test_bounty.py (3)

66-68: Consider verifying the sponsors_tx_id format.

The test only checks that sponsors_tx_id is not None. Per the new flow, this field should follow the format BOUNTY:<contributor>:<amount>:<pr>. Verifying the format would catch regressions in the recording logic.

💡 Suggested enhancement
         # Verify database was updated
         self.issue.refresh_from_db()
         self.assertIsNotNone(self.issue.sponsors_tx_id)
+        self.assertTrue(
+            self.issue.sponsors_tx_id.startswith("BOUNTY:testuser:5000:456"),
+            f"Unexpected sponsors_tx_id format: {self.issue.sponsors_tx_id}"
+        )

70-94: Add assertion to verify sponsors_tx_id remains unchanged.

The duplicate test should verify that the existing sponsors_tx_id is not overwritten. This ensures the idempotency guarantee is properly tested.

💡 Suggested enhancement
         self.assertEqual(response.status_code, 200)
         response_data = response.json()
         self.assertEqual(response_data["status"], "warning")
+
+        # Verify the existing transaction ID was not overwritten
+        self.issue.refresh_from_db()
+        self.assertEqual(self.issue.sponsors_tx_id, "EXISTING_TX")

10-171: Consider adding test coverage for edge cases.

The test suite covers the main paths well, but consider adding tests for:

  1. Issue without bounty tag: What happens when has_dollar_tag=False? Per PR objectives, only issues with a bounty label should be processed.
  2. Invalid bounty amounts: Zero or negative bounty_amount values.
  3. Missing API token header: Request without HTTP_X_BLT_API_TOKEN header at all (vs. wrong token).
💡 Example test for issue without bounty tag
`@patch.dict`(os.environ, {"BLT_API_TOKEN": "test_token_12345"})
def test_bounty_payout_issue_without_bounty_tag(self):
    """Test that issues without bounty tag are rejected."""
    # Create issue without bounty tag
    non_bounty_issue = GitHubIssue.objects.create(
        issue_id=999,
        title="Non-bounty Issue",
        body="No bounty here",
        state="open",
        created_at="2025-01-01T00:00:00Z",
        updated_at="2025-01-01T00:00:00Z",
        url="https://github.com/TestOrg/TestRepo/issues/999",
        has_dollar_tag=False,
        repo=self.repo,
    )

    payload = {
        "issue_number": 999,
        "repo": "TestRepo",
        "owner": "TestOrg",
        "contributor_username": "testuser",
        "pr_number": 456,
        "bounty_amount": 5000,
    }

    response = self.client.post(
        "/bounty_payout/",
        data=json.dumps(payload),
        content_type="application/json",
        HTTP_X_BLT_API_TOKEN=self.api_token,
    )

    # Should reject or handle appropriately
    self.assertIn(response.status_code, [400, 404])
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Knowledge base: Disabled due to Reviews -> Disable Knowledge Base setting

📥 Commits

Reviewing files that changed from the base of the PR and between f818915 and a738e65.

⛔ Files ignored due to path filters (8)
  • website/static/images/tech/ml.svg is excluded by !**/*.svg
  • website/static/images/tech/nlp.svg is excluded by !**/*.svg
  • website/static/images/tech/oauth.svg is excluded by !**/*.svg
  • website/static/images/tech/python.svg is excluded by !**/*.svg
  • website/static/images/tech/sql.svg is excluded by !**/*.svg
  • website/static/images/tech/tailwind.svg is excluded by !**/*.svg
  • website/static/img/tomato-svgrepo-com.svg is excluded by !**/*.svg
  • website/static/js/jquery.sparkline.min.js is excluded by !**/*.min.js
📒 Files selected for processing (41)
  • .github/codeql-config.yml
  • .github/workflows/add-files-changed-label.yml
  • .github/workflows/auto-fix-main-precommit.yml
  • .github/workflows/auto-fix-pr-precommit.yml
  • .github/workflows/bounty-payout.yml
  • .lgtm.yml
  • BACON/bacon-etch.yaml
  • BACON/ord-server/example-split.yaml
  • BACON/regtest/bitcoin.conf
  • Procfile
  • docs/features.md
  • website/documents/BltAboutUs.md
  • website/documents/BltBLTV.md
  • website/documents/BltChangePassword.md
  • website/documents/BltCommunityMembers.md
  • website/documents/BltCompanyDashboard.md
  • website/documents/BltCompanyListingPage.md
  • website/documents/BltCompanyScoreboard.md
  • website/documents/BltDetails.md
  • website/documents/BltDetailsFromOwasp.md
  • website/documents/BltGlobalLeaderboard.md
  • website/documents/BltInvite.md
  • website/documents/BltLoginPage.md
  • website/documents/BltSignUpPage.md
  • website/documents/BltStartaBughunt.md
  • website/documents/BltStats.md
  • website/documents/BltTerms.md
  • website/documents/BltTrademarksSearch.md
  • website/documents/BltTrademarksSearchResults.md
  • website/documents/BltUserProfile.md
  • website/documents/BltWeeklyActivityOfContributor.md
  • website/static/css/app.min.css
  • website/static/css/checkInModal.css
  • website/static/js/debug-panel.js
  • website/static/js/gsoc_pr_report.js
  • website/static/js/issue.js
  • website/static/js/messages.js
  • website/static/js/reminder_settings.js
  • website/static/organization/js/hunt_controller.js
  • website/tests/test_bounty.py
  • website/views/bounty.py
✅ Files skipped from review due to trivial changes (24)
  • website/static/js/debug-panel.js
  • website/documents/BltInvite.md
  • .lgtm.yml
  • website/documents/BltChangePassword.md
  • website/documents/BltCompanyDashboard.md
  • .github/workflows/auto-fix-main-precommit.yml
  • .github/codeql-config.yml
  • website/documents/BltSignUpPage.md
  • website/documents/BltGlobalLeaderboard.md
  • website/static/css/app.min.css
  • website/documents/BltStartaBughunt.md
  • website/documents/BltLoginPage.md
  • website/static/organization/js/hunt_controller.js
  • website/documents/BltCompanyListingPage.md
  • .github/workflows/auto-fix-pr-precommit.yml
  • website/documents/BltCompanyScoreboard.md
  • website/static/js/reminder_settings.js
  • docs/features.md
  • BACON/ord-server/example-split.yaml
  • website/documents/BltDetailsFromOwasp.md
  • website/documents/BltAboutUs.md
  • website/documents/BltDetails.md
  • website/documents/BltStats.md
  • website/static/css/checkInModal.css
🧰 Additional context used
🪛 GitHub Actions: Check Console Statements
website/static/js/issue.js

[error] 388-388: Found console statements in 2 file(s)

🪛 LanguageTool
website/documents/BltWeeklyActivityOfContributor.md

[style] ~44-~44: To reduce wordiness, try specifying a number or using “many” or “numerous” instead.
Context: ...lity**: The table is designed to handle a large number of users and activity metrics, maintaining...

(LARGE_NUMBER_OF)

🪛 markdownlint-cli2 (0.18.1)
website/documents/BltTerms.md

94-94: Bare URL used

(MD034, no-bare-urls)

⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Run pre-commit
  • GitHub Check: Run Tests
  • GitHub Check: docker-test
🔇 Additional comments (21)
BACON/regtest/bitcoin.conf (1)

12-13: Verify the exposure scope of this regtest RPC configuration.

The combination of rpcbind=0.0.0.0:18443 and rpcallowip=0.0.0.0/0 allows RPC connections from any IP address to this Bitcoin regtest node. While permissive settings are typical for local regtest environments, ensure this container is not exposed to untrusted networks—especially given the default credentials (bitcoin:bitcoin).

If this is strictly for isolated CI/local development, this is acceptable. Otherwise, consider restricting rpcallowip to specific trusted subnets (e.g., Docker network ranges).

.github/workflows/add-files-changed-label.yml (1)

130-130: LGTM!

Trailing newline normalization at EOF - no functional change.

website/documents/BltCommunityMembers.md (1)

42-42: LGTM!

Markdown list formatting fix - corrects list item syntax.

Procfile (1)

3-3: LGTM!

Trailing newline normalization at EOF - no functional change. The active uvicorn configuration remains intact.

website/static/js/issue.js (1)

735-735: LGTM!

Trailing newline formatting change aligns with project-wide consistency standards.

website/static/js/gsoc_pr_report.js (1)

928-939: LGTM!

The viewRawData function is properly structured with correct closing brace. The implementation safely handles popup blocking and properly escapes JSON content via JSON.stringify.

website/documents/BltBLTV.md (1)

43-43: LGTM!

Formatting adjustment in documentation. No content changes.

website/documents/BltTrademarksSearchResults.md (1)

42-42: LGTM!

Bullet point formatting standardized for consistency with other list items.

website/static/js/messages.js (1)

111-111: LGTM!

Trailing newline formatting change for file consistency. The createMessage function implementation is solid with proper type-based styling and auto-dismiss handling.

BACON/bacon-etch.yaml (1)

11-12: LGTM!

The YAML syntax fix is correct. The - file: is proper list item syntax under the inscriptions: key.

website/documents/BltUserProfile.md (1)

35-35: LGTM!

EOF newline normalization.

website/documents/BltTrademarksSearch.md (1)

39-39: LGTM!

Formatting adjustment with no semantic change.

website/views/bounty.py (3)

25-35: LGTM!

Authentication uses constant-time comparison via secrets.compare_digest to prevent timing attacks. The error handling properly distinguishes between missing configuration (500) and invalid tokens (403).


74-84: LGTM!

Duplicate detection is appropriate. Returning 200 with a "warning" status allows idempotent behavior while signaling that no new action was taken.


86-104: LGTM!

The bounty recording format is clear and parseable. The response provides useful information for confirmation. The log message correctly converts cents to dollars for readability.

.github/workflows/bounty-payout.yml (1)

15-88: LGTM!

The issue detection logic correctly parses closing keywords, validates bounty labels, and handles both integer and decimal dollar amounts with proper conversion to cents.

website/tests/test_bounty.py (5)

10-39: LGTM!

Test setup properly creates the necessary fixtures (Organization, Repo, GitHubIssue with bounty tag) and establishes the API token for authentication tests.


96-115: LGTM!

The unauthorized test correctly verifies that an invalid API token results in a 403 response.


117-129: LGTM!

The missing fields test correctly verifies that incomplete payloads are rejected with a 400 status.


131-150: LGTM!

The repo not found test correctly verifies that requests for non-existent repositories return 404.


152-171: LGTM!

The issue not found test correctly verifies that requests for non-existent issues return 404.

✏️ Tip: You can disable this entire section by setting review_details to false in your review settings.

Copy link
Collaborator

@Jayant2908 Jayant2908 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey Do fix the bot issues and remove all the console log statements present. Thank You!

- Wrap https.request in Promise so errors fail workflow step
- Use Buffer.byteLength for correct Content-Length with non-ASCII
- Use organization__github_org for repo lookup to match GitHub data
- Update tests to include github_org field
@github-actions github-actions bot added pre-commit: failed Pre-commit checks failed and removed pre-commit: passed Pre-commit checks passed labels Jan 14, 2026
@github-actions github-actions bot added last-active: 4d PR last updated 4 days ago last-active: 5d Issue last updated 5 days ago last-active: 6d Issue last updated 6 days ago last-active: 7d Issue last updated 7 days ago last-active: 8d Issue last updated 8 days ago last-active: 9d Issue last updated 9 days ago last-active: 10d PR last updated 10 days ago and removed last-active: 3d PR last updated 3 days ago last-active: 4d PR last updated 4 days ago last-active: 5d Issue last updated 5 days ago last-active: 6d Issue last updated 6 days ago last-active: 7d Issue last updated 7 days ago last-active: 8d Issue last updated 8 days ago last-active: 9d Issue last updated 9 days ago last-active: 10d PR last updated 10 days ago labels Jan 21, 2026
Copy link
Collaborator

@DonnieBLT DonnieBLT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

lets make this a simple github action please

@Iceshen87
Copy link

Hi OWASP-BLT team! 👋

I can help with the bounty details recording feature!

My Plan:

  1. Review the bounty recording flow
  2. Test the implementation
  3. Fix any issues
  4. Add tests if needed
  5. Document the feature

Timeline: 2-3 days
Bounty: Open to discussion

Why I am a good fit:

  • Experience with GitHub integrations
  • Familiar with bounty tracking systems
  • Strong backend development skills

Ready to help!

  • Ethan (Iceshen87)

Copy link
Collaborator

@DonnieBLT DonnieBLT left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we are getting closer to this working. I'm also interested if we can make this more standalone and not require the BLT API. Let's work on this one step at a time. I think there is now a comment that is left when there is a bounty. Let's make sure that's working.

Comment on lines 155 to 156
const result = await response.json();
console.log('Bounty payout request successful:', result);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The code attempts to call .json() on an undefined variable response. The Promise resolves with a string, which should be captured and parsed instead.
Severity: CRITICAL

Suggested Fix

Assign the result of the awaited Promise to a variable like body. Then, parse the JSON from this string using JSON.parse(body). Alternatively, call resolve(JSON.parse(body)) from within the Promise's end event handler.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: .github/workflows/bounty-payout.yml#L155-L156

Potential issue: In the bounty payout workflow, an `https` request is wrapped in a
`Promise`. This `Promise` resolves with the response body as a string. However, the code
immediately following the `await` call attempts to use `response.json()`. The variable
`response` is never defined in this scope, which will lead to a `ReferenceError`. This
error will cause the GitHub Action step to crash every time it runs, preventing bounty
payout information from being recorded in the database.

Did we get this right? 👍 / 👎 to inform future reviews.

@github-actions
Copy link
Contributor

github-actions bot commented Mar 1, 2026

💬 Unresolved Conversations Detected

Hi @SuyashJain17!

This pull request currently has 5 unresolved conversations that need to be addressed.

Action Required

  • Review the pending discussions in the "Files changed" tab
  • Address or respond to each conversation
  • Mark conversations as resolved (if you have permission), or ask a maintainer/reviewer to resolve them once they are addressed

The label on this PR will automatically update as conversations are resolved. Thank you! 🙏

@github-actions
Copy link
Contributor

github-actions bot commented Mar 5, 2026

💬 Reminder: Unresolved Conversations

Hi @SuyashJain17!

This pull request has 1 unresolved conversation that need to be addressed.

Please review and resolve the pending discussions so we can move forward with merging this PR.

Thank you! 🙏

BLT_API_TOKEN: ${{ secrets.BLT_API_TOKEN }}
API_BASE_URL: ${{ secrets.API_BASE_URL || 'https://owasp.org' }}
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 https://github.com/actions/github-script/commit/f28e40c7f34bde8b3046d885e986cb6290c5673b
API_BASE_URL: ${{ secrets.API_BASE_URL || 'https://owaspblt.org' }}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: The default API_BASE_URL in the bounty payout workflow is set to https://owaspblt.org instead of the correct API domain, https://blt.owasp.org.
Severity: HIGH

Suggested Fix

In the .github/workflows/bounty-payout.yml file, change the default value for API_BASE_URL from 'https://owaspblt.org' to the correct API endpoint, 'https://blt.owasp.org'. This will ensure that bounty payouts are correctly processed if the API_BASE_URL secret is not set.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent.
Verify if this is a real issue. If it is, propose a fix; if not, explain why it's not
valid.

Location: .github/workflows/bounty-payout.yml#L104

Potential issue: The bounty payout workflow sets a default `API_BASE_URL` to
`'https://owaspblt.org'`. However, the application's internal API is hosted at
`blt.owasp.org`. If the `API_BASE_URL` secret is not configured in the environment, the
workflow will send requests to the wrong domain. Due to `continue-on-error: true` being
set for this step, these requests will fail silently, and bounty payouts will not be
recorded in the database. This contradicts the PR description, which states the intent
to use `blt.owasp.org`.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/bounty-payout.yml (1)

95-97: ⚠️ Potential issue | 🟠 Major

Gate notification comments on confirmed successful bounty recording.

The workflow posts bounty notification comments whenever has-bounty is true (line 159), regardless of whether the "Send bounty data" POST succeeded or what status the backend returned. With continue-on-error: true (line 97), step failures are silently ignored. Additionally, the backend returns HTTP 200 with "status": "warning" for duplicate bounties (website/views/bounty.py:117-124), which are treated as successful responses and trigger public comments.

There's also a critical code bug: line 155 references an undefined response variable (const result = await response.json();), which will throw a ReferenceError. The Promise resolves with a string body, not a response object. This error is masked by continue-on-error: true.

Add step output validation and gate the notification step on a confirmed successful recording:

Suggested fix
      - name: Send bounty data to BLT
+       id: send-bounty
        if: steps.check-issues.outputs['has-bounty'] == 'true'
        continue-on-error: true
        env:
          ISSUE_NUMBER: ${{ steps.check-issues.outputs.issue-number }}
          BOUNTY_AMOUNT: ${{ steps.check-issues.outputs.bounty-amount }}
          CONTRIBUTOR_USERNAME: ${{ steps.check-issues.outputs.contributor-username }}
          PR_NUMBER: ${{ github.event.pull_request.number }}
          BLT_API_TOKEN: ${{ secrets.BLT_API_TOKEN }}
          API_BASE_URL: ${{ secrets.API_BASE_URL || 'https://owaspblt.org' }}
        uses: actions/github-script@v6.1.1
        with:
          github-token: ${{ secrets.GITHUB_TOKEN }}
          script: |
            const core = require('@actions/core');
            const https = require('https');
            ...
-           const result = await response.json();
-           console.log('Bounty payout request successful:', result);
+           const result = JSON.parse(body);
+           core.setOutput('recorded', result.status === 'success' ? 'true' : 'false');
+           if (result.status !== 'success') {
+             core.warning(`Bounty was not newly recorded: ${result.message}`);
+           }
+           console.log('Bounty payout request completed:', result);

      - name: Post bounty notification comments
-       if: steps.check-issues.outputs['has-bounty'] == 'true'
+       if: steps.check-issues.outputs['has-bounty'] == 'true' && steps.send-bounty.outputs.recorded == 'true'
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bounty-payout.yml around lines 95 - 97, The "Send bounty
data to BLT" step currently swallows errors (continue-on-error: true),
references an undefined response variable (const result = await
response.json()), and doesn't surface backend "status" (success vs warning); fix
by removing or disabling continue-on-error for that step, correctly capture the
POST result (use the actual variable returned by the fetch/HTTP call — e.g.,
assign to `responseBody` or `fetchResponse` then parse JSON via
JSON.parse(responseBody) or await fetchResponse.json()), validate the parsed
JSON has status === "success" before setting a step output like
`bounty_posted=true`, and gate the downstream "post bounty notification" step on
that output (if: steps.<send-step-id>.outputs.bounty_posted == 'true') so only
confirmed successful recordings trigger public comments.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.github/workflows/bounty-payout.yml:
- Around line 133-156: The code still calls await response.json() even though
the fetch-style response variable no longer exists: after the https.request
block that resolves with the response body (the local variable body inside that
promise), remove the reference to response.json() and instead use the resolved
body from the promise returned by the https.request wrapper (parse it with
JSON.parse if you expect JSON) before logging; update the logging to use the
parsed result (e.g., parsedResult) and ensure the promise resolves/rejects from
the https.request wrapper so the subsequent code uses that resolved value rather
than a non-existent response variable.
- Around line 133-153: The outbound https.request has no timeout; update the
Promise wrapper around https.request to call req.setTimeout(TIMEOUT_MS, ...)
(e.g., 30_000) and abort the request by calling req.destroy(new Error('Request
timeout')) when the timer fires so the existing req.on('error', ...) will reject
the Promise; ensure the timeout is applied to the same request object created by
https.request (referencing req) and that you do not swallow the error so callers
get a clear timeout error.

---

Outside diff comments:
In @.github/workflows/bounty-payout.yml:
- Around line 95-97: The "Send bounty data to BLT" step currently swallows
errors (continue-on-error: true), references an undefined response variable
(const result = await response.json()), and doesn't surface backend "status"
(success vs warning); fix by removing or disabling continue-on-error for that
step, correctly capture the POST result (use the actual variable returned by the
fetch/HTTP call — e.g., assign to `responseBody` or `fetchResponse` then parse
JSON via JSON.parse(responseBody) or await fetchResponse.json()), validate the
parsed JSON has status === "success" before setting a step output like
`bounty_posted=true`, and gate the downstream "post bounty notification" step on
that output (if: steps.<send-step-id>.outputs.bounty_posted == 'true') so only
confirmed successful recordings trigger public comments.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: OWASP-BLT/coderabbit/.coderabbit.yml

Review profile: CHILL

Plan: Pro

Run ID: 46d6180f-5de2-4615-bec2-ed285b45633c

📥 Commits

Reviewing files that changed from the base of the PR and between 56aa21e and f325548.

📒 Files selected for processing (1)
  • .github/workflows/bounty-payout.yml

Comment on lines +133 to 156
await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
console.log(`Status: ${res.statusCode}`);
console.log(`Response: ${body}`);
if (res.statusCode >= 400) {
reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
} else {
resolve(body);
}
});
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Bounty payout request failed: ${response.status} - ${errorText}`);
}
req.on('error', (e) => {
reject(new Error(`Request error: ${e.message}`));
});

req.write(data);
req.end();
});
const result = await response.json();
console.log('Bounty payout request successful:', result);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, check if the file exists and get its size
wc -l .github/workflows/bounty-payout.yml

Repository: OWASP-BLT/BLT

Length of output: 96


🏁 Script executed:

# Read the relevant section around lines 133-156
sed -n '130,160p' .github/workflows/bounty-payout.yml

Repository: OWASP-BLT/BLT

Length of output: 1296


🏁 Script executed:

# Search for any assignment to 'response' variable in the workflow file
rg 'response\s*=' .github/workflows/bounty-payout.yml

Repository: OWASP-BLT/BLT

Length of output: 39


Remove the leftover fetch response handling.

After switching to https.request, the variable response no longer exists. Line 155 attempts to call await response.json(), which throws a ReferenceError on every successful POST, causing the step to fail.

🧩 Suggested fix
-            await new Promise((resolve, reject) => {
+            const responseBody = await new Promise((resolve, reject) => {
               const req = https.request(options, (res) => {
                 let body = '';
                 res.on('data', chunk => body += chunk);
                 res.on('end', () => {
                   console.log(`Status: ${res.statusCode}`);
                   console.log(`Response: ${body}`);
                   if (res.statusCode >= 400) {
                     reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
                   } else {
                     resolve(body);
                   }
                 });
               });

               req.on('error', (e) => {
                 reject(new Error(`Request error: ${e.message}`));
               });

               req.write(data);
               req.end();
             });
-            const result = await response.json();
-            console.log('Bounty payout request successful:', result);
+            console.log('Bounty payout request successful:', responseBody);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
console.log(`Status: ${res.statusCode}`);
console.log(`Response: ${body}`);
if (res.statusCode >= 400) {
reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
} else {
resolve(body);
}
});
});
if (!response.ok) {
const errorText = await response.text();
throw new Error(`Bounty payout request failed: ${response.status} - ${errorText}`);
}
req.on('error', (e) => {
reject(new Error(`Request error: ${e.message}`));
});
req.write(data);
req.end();
});
const result = await response.json();
console.log('Bounty payout request successful:', result);
const responseBody = await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
console.log(`Status: ${res.statusCode}`);
console.log(`Response: ${body}`);
if (res.statusCode >= 400) {
reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
} else {
resolve(body);
}
});
});
req.on('error', (e) => {
reject(new Error(`Request error: ${e.message}`));
});
req.write(data);
req.end();
});
console.log('Bounty payout request successful:', responseBody);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bounty-payout.yml around lines 133 - 156, The code still
calls await response.json() even though the fetch-style response variable no
longer exists: after the https.request block that resolves with the response
body (the local variable body inside that promise), remove the reference to
response.json() and instead use the resolved body from the promise returned by
the https.request wrapper (parse it with JSON.parse if you expect JSON) before
logging; update the logging to use the parsed result (e.g., parsedResult) and
ensure the promise resolves/rejects from the https.request wrapper so the
subsequent code uses that resolved value rather than a non-existent response
variable.

Comment on lines +133 to +153
await new Promise((resolve, reject) => {
const req = https.request(options, (res) => {
let body = '';
res.on('data', chunk => body += chunk);
res.on('end', () => {
console.log(`Status: ${res.statusCode}`);
console.log(`Response: ${body}`);
if (res.statusCode >= 400) {
reject(new Error(`Request failed: ${res.statusCode} - ${body}`));
} else {
resolve(body);
}
});
});

if (!response.ok) {
const errorText = await response.text();
throw new Error(`Bounty payout request failed: ${response.status} - ${errorText}`);
}
req.on('error', (e) => {
reject(new Error(`Request error: ${e.message}`));
});

req.write(data);
req.end();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, let's check if the file exists and read the relevant lines
if [ -f ".github/workflows/bounty-payout.yml" ]; then
  echo "=== File found ===" 
  wc -l ".github/workflows/bounty-payout.yml"
  echo "=== Lines 130-160 ===" 
  sed -n '130,160p' ".github/workflows/bounty-payout.yml" | cat -n
else
  echo "File not found at .github/workflows/bounty-payout.yml"
  echo "Searching for similar files..."
  find . -name "*bounty*" -type f 2>/dev/null
fi

Repository: OWASP-BLT/BLT

Length of output: 1594


🏁 Script executed:

# Let's check Node.js documentation and examples for https.request timeout handling
# First, let's see the Node.js version used in this workflow
grep -n "node" ".github/workflows/bounty-payout.yml" | head -20

Repository: OWASP-BLT/BLT

Length of output: 39


🌐 Web query:

Node.js https.request setTimeout timeout handling best practice

💡 Result:

What to know (core behavior)

  • https.request()’s timeout option and req.setTimeout(ms) do not stop the request—they only cause a 'timeout' event to be emitted. You must abort/destroy the request yourself. (nodejs.org)
  • The ClientRequest 'timeout' event means socket inactivity, not “total request took too long”. (nodejs.org)
  • req.setTimeout(ms) ultimately calls socket.setTimeout() after the socket is assigned and connected. (nodejs.org)
  • If you pass an AbortSignal to http(s).request, calling abort() behaves like req.destroy() (it will emit an 'error' with ABORT_ERR). (nodejs.org)

Best-practice pattern (manual timeout => destroy)

import https from "node:https";

export function requestWithTimeout(options, ms) {
  return new Promise((resolve, reject) => {
    const req = https.request(options, (res) => resolve(res));

    req.on("error", reject);

    req.setTimeout(ms, () => {
      // IMPORTANT: timeout only notifies; you must stop the request.
      req.destroy(new Error(`Request timed out after ${ms}ms`));
    });

    req.end();
  });
}

Preferred modern pattern (AbortSignal deadline)

If you want a “real” deadline (and not just an idle-socket timeout), use an abort signal and set your own timer, or (in newer Node.js) AbortSignal.timeout(ms):

import https from "node:https";

const req = https.request(
  { hostname: "example.com", path: "/", method: "GET", signal: AbortSignal.timeout(5000) },
  (res) => { /* ... */ }
);

req.on("error", (err) => { /* handle ABORT_ERR vs others */ });
req.end();

AbortSignal.timeout() is a convenience that creates a signal that aborts after ms. (betterstack.com)

Citations:


Add a timeout to the outbound HTTP request.

This https.request has no timeout, so a stalled BLT API can leave the Promise pending until the runner times out. The suggested fix correctly uses req.setTimeout() with req.destroy() to abort the request, which is the documented best-practice pattern for Node.js HTTP clients.

⏱️ Suggested fix
               req.on('error', (e) => {
                 reject(new Error(`Request error: ${e.message}`));
               });
+              req.setTimeout(10000, () => {
+                req.destroy(new Error('Request timed out after 10s'));
+              });

               req.write(data);
               req.end();
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.github/workflows/bounty-payout.yml around lines 133 - 153, The outbound
https.request has no timeout; update the Promise wrapper around https.request to
call req.setTimeout(TIMEOUT_MS, ...) (e.g., 30_000) and abort the request by
calling req.destroy(new Error('Request timeout')) when the timer fires so the
existing req.on('error', ...) will reject the Promise; ensure the timeout is
applied to the same request object created by https.request (referencing req)
and that you do not swallow the error so callers get a clear timeout error.

API_BASE_URL: ${{ secrets.API_BASE_URL || 'https://owasp.org' }}
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 https://github.com/actions/github-script/commit/f28e40c7f34bde8b3046d885e986cb6290c5673b
API_BASE_URL: ${{ secrets.API_BASE_URL || 'https://owaspblt.org' }}
uses: actions/github-script@v6.1.1
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
uses: actions/github-script@v6.1.1
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0 https://github.com/actions/github-script/commit/f28e40c7f34bde8b3046d885e986cb6290c5673b

@DonnieBLT
Copy link
Collaborator

lets go more simple than this - you can continue this on the rewards repo we'll handle rewards there

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

changes-requested PR has requested changes from a reviewer files-changed: 3 PR changes 3 files has-console-statements PR contains console statements that need to be removed has-peer-review PR has received peer review linked-issue PR has a linked GitHub issue pre-commit: passed Pre-commit checks passed reviewed tests: P:479 F:0 S:0 Django tests passed unresolved-conversations: 5 PR has 5 unresolved conversations

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

When a PR is merged that has an attached issue with a bounty, auto pay it

5 participants