Security

BugDrop is designed with security and privacy at its core. This page covers the permissions the GitHub App requires, how data is stored, privacy guarantees, and the built-in rate limiting that protects against abuse.

GitHub App Permissions

The BugDrop GitHub App uses the following repository permissions:

PermissionAccess LevelPurpose
IssuesRead & WriteCreate bug reports, feature requests, and questions as GitHub Issues
ContentsRead & WriteStore screenshots and attachments on the bugdrop-screenshots branch
MetadataReadRead basic repository information, including the default branch

Permission granted. GitHub’s Contents: Read & Write permission allows access to repository files, including source code. It is repository-scoped, not limited to a folder or branch. Branch rules can constrain writes, but storing files on a separate branch does not narrow the app’s permission. See GitHub’s Contents API documentation.

How BugDrop uses it. The file-upload implementation stores screenshots in .bugdrop/screenshots/ and attachments in .bugdrop/uploads/ on bugdrop-screenshots. To create that branch, it reads repository information and the default branch reference. This describes the inspected implementation, not a technical restriction preventing other use of the granted permission.

Limit access. Choose Only select repositories when installing and select only the feedback destination. Check GitHub’s installation screen for the permissions you are granting. You can change the selected repositories or revoke access at any time under GitHub Settings > Applications > Installed GitHub Apps.

Data Storage

BugDrop follows a "GitHub-native" feedback-storage model. Feedback content and uploaded files are stored in your GitHub repository, not in a separate BugDrop feedback database. The hosted service temporarily stores rate-limit counters and limited installation information as described in the Privacy Policy.

Issues

Feedback submissions are created as GitHub Issues in your repository. Each issue includes:

  • Title and description from the user
  • Feedback category (Bug, Feature, or Question) as a GitHub label
  • Automatic system information (browser, OS, viewport, language, URL)
  • Submitter name and email (if configured and provided)
  • Link to the screenshot (if attached)

Screenshots

Screenshots are stored as image files in a .bugdrop/ directory on a dedicated bugdrop-screenshots branch in your repository. This design has several benefits:

  • Screenshots do not clutter your main branch -- They live on a separate branch that never merges into your codebase
  • Full version history -- Every screenshot is a Git commit, giving you a full audit trail
  • GitHub-hosted -- Images are served directly from GitHub, with no external image hosting
  • Easy cleanup -- Delete the bugdrop-screenshots branch to remove all screenshots at once

The screenshot branch is created automatically when the first screenshot is uploaded. No manual setup is required.

Treat screenshots as unauthenticated user-generated content. The hosted service enforces rate limits, size limits, and PNG payload validation, but it is not a spam or malware filtering product.

Screenshot Format

Screenshots are captured client-side using html-to-image, which renders the current page to a canvas element in the user's browser. The canvas is then converted to a PNG image and uploaded. This means:

  • The initial screenshot capture is rendered from what the user actually sees
  • No server-side rendering or page access is required
  • The screenshot is generated entirely in the user's browser before being sent to the API
  • Users can redact additional screenshot regions before submitting when using the manual screenshot flow

Manual redaction is user-driven and does not automatically detect sensitive content. It complements, but does not replace, developer-configured masking for fields that should be visually covered in supported screenshot modes, especially when using automatic screenshots.

Because clients are untrusted, the API validates screenshot uploads server-side before storing them. BugDrop currently accepts PNG data URLs only and rejects SVG, malformed base64, oversized payloads, and data that does not have a PNG file signature.

Privacy

BugDrop is built with a privacy-first approach:

  • No user tracking -- BugDrop does not set cookies, use local storage for tracking, or fingerprint users
  • No analytics -- The widget does not send any telemetry, usage data, or analytics to any server
  • GitHub-native feedback storage -- Issue content, screenshots, and attachments are stored in your GitHub repository rather than a separate BugDrop feedback database
  • No user accounts -- Users submitting feedback do not need to create accounts or log in
  • No PII collection by default -- Name and email fields are off by default. When enabled, this data goes only to the GitHub Issue in your repository
  • Client-side screenshots -- Screenshots are rendered in the user's browser, not captured server-side
  • Open source -- The entire codebase is open source (MIT licensed) and auditable

Screenshot masking

BugDrop supports developer-configured visual masking for screenshots. Add data-bugdrop-redact or data-bugdrop-mask to any DOM element that should be covered:

<input type="email" data-bugdrop-redact />

<div data-bugdrop-mask>
  <span>User name</span>
  <span>[email protected]</span>
</div>

Supported explicit attributes are data-bugdrop-redact, data-bd-redact, data-bugdrop-redacted, and data-bugdrop-mask.

For supported DOM-rendered captures, when masking succeeds, BugDrop records the geometry of matching DOM elements, renders the screenshot, then paints opaque rectangles over those measured boxes in the PNG. The submitted image contains the black rectangles. If masking fails, BugDrop discards the screenshot instead of uploading it. The original page DOM is not mutated.

Masking is visual coverage, not data-loss prevention. BugDrop does not inspect text or pixels to discover secrets. Developers must mark the sensitive regions they control, and users should review manual screenshots before submitting.

Inheritance. When an ancestor has data-bugdrop-mask, the entire ancestor box is masked as a single rectangle. Descendants do not get individual rectangles — this prevents gaps from CSS gap or non-masked siblings inside a masked container.

Built-in defaults. In supported DOM-rendered screenshot paths, BugDrop automatically masks these with or without an explicit attribute:

  • input[type="password"]
  • Any input with autocomplete="cc-number", cc-csc, or cc-exp

These defaults do not apply to native viewport capture or skipped/failed screenshot paths.

SurfaceBehaviorRecommended control
Regular DOM elementsMarked element box is coveredMark the smallest stable container that fully encloses the sensitive content
Password and credit-card inputsCovered automaticallyNo extra attribute required, but explicit marks are fine
Open Shadow DOMTraversed when the browser exposes shadowRootMark inner controls or the host
Closed Shadow DOMNot traversedMark the host custom element
IframesIframe internals are not traversedMark the iframe element if the whole embedded frame is sensitive
Canvas, image, SVG, videoInternal pixels are not inspectedMark the element or a containing wrapper
Pseudo-elements and highly custom controlsGenerated pixels are not inspected separatelyMark a stable wrapper around the whole control
Native viewport capture fallbackElement masks cannot be appliedAvoid viewport fallback on pages that require masking

If a marked region includes embedded, media, or pixel-rendered content, BugDrop covers the marked element box and warns the user in manual screenshot review flows that it cannot inspect the internal pixels. Automatic screenshot mode still applies supported masks, but it submits without showing a review step.

Mask rectangles are measured at capture start. If the page reflows or reveals sensitive elements after that measurement but before rendering finishes, a mask can become stale. Keep sensitive marked regions stable during capture.

The only network requests BugDrop makes are:

  1. Loading the widget script from Cloudflare Workers
  2. Submitting the feedback form to the BugDrop Cloudflare Worker API

The API receives the form data, creates the issue and uploads its files, then discards the feedback content. After GitHub confirms Issue creation, the hosted service increments one anonymous global counter. The counter contains no account, repository, Issue, reporter, or submission data. To make delivery retries safe, it retains a bounded set of recent random deduplication tokens that encode no submission information. BugDrop excludes its first-party/test owners and publishes the total only as a rounded, defensible lower bound. The hosted service separately keeps short-lived rate-limit counters; see the Privacy Policy for the complete data-handling and retention details.

Rate Limiting

BugDrop includes built-in rate limiting to protect against abuse and ensure fair usage. Rate limits are applied at the Cloudflare Worker API level.

Rate Limit Tiers

ScopeLimitWindowDescription
Per IP10 requests15 minutesLimits individual users from flooding the API
Per Repository50 requests1 hourLimits total submissions to any single repository

Both limits are enforced simultaneously. A request must pass both the per-IP and per-repository checks to succeed.

Rate Limit Headers

Every API response includes rate limit headers so you can monitor usage:

HeaderDescriptionExample
X-RateLimit-LimitMaximum requests allowed in the window10
X-RateLimit-RemainingRequests remaining in the current window7
Retry-AfterSeconds until the rate limit resets (only on 429 responses)420

429 Response Behavior

When a rate limit is exceeded, the API returns an HTTP 429 Too Many Requests response with:

  • A JSON body containing an error message explaining which limit was hit
  • A Retry-After header indicating how many seconds to wait before retrying

The widget handles 429 responses gracefully by displaying a user-friendly message in the form. The user is told to try again later and shown approximately how long to wait.

Example 429 response:

{
  "error": "Rate limit exceeded. Please try again later.",
  "retryAfter": 420
}

Rate Limit Design Rationale

The rate limits are set to be generous enough for legitimate usage while preventing abuse:

  • 10 per IP / 15 minutes -- Even an active bug reporter rarely submits more than a few reports in 15 minutes. This limit stops automated scripts and spam while being invisible to real users.
  • 50 per repository / hour -- This allows a team of users to submit feedback without hitting limits, while preventing a single repository from being overwhelmed by a flood of submissions.

If these limits are too restrictive for your use case, consider self-hosting BugDrop with custom rate limit configuration.

Security Best Practices

For Site Owners

  1. Review app permissions -- Periodically check the BugDrop GitHub App's permissions in your GitHub settings
  2. Monitor the screenshots branch -- Occasionally review the bugdrop-screenshots branch for unexpected content
  3. Exclude screenshot storage from CI -- Do not run privileged CI/deploy workflows on bugdrop-screenshots; treat it as user-generated content storage, not application source
  4. Use branch protection -- Keep your main branch protected and limit deploy/build workflows to main or other trusted branches
  5. Set up CSP -- If you use a Content Security Policy, explicitly whitelist the required domains rather than using broad wildcards

The hosted service is intended for lightweight feedback collection. If your site is public, high-traffic, compliance-sensitive, or exposed to adversarial submissions, self-host BugDrop and place it behind your own WAF, CAPTCHA, logging, retention, and content filtering controls.

For Self-Hosters

If you run your own instance of BugDrop:

  1. Rotate your GitHub App credentials regularly
  2. Set appropriate rate limits for your expected traffic
  3. Monitor your Cloudflare Worker logs for unusual activity
  4. Add edge protections such as WAF rules, CAPTCHA, bot detection, and allowlists as needed
  5. Define retention/cleanup for the bugdrop-screenshots branch
  6. Use host-app auth tokens when the widget should only be available to authenticated users
  7. Keep your instance updated with the latest version

Host-App Auth Tokens

For private or authenticated applications, self-hosted workers can require a signed token on feedback submissions. Set AUTH_TOKEN_SECRET on the BugDrop worker, mint short-lived tokens from your own backend after checking the user's session, and configure the widget with data-auth-token-provider.

When rotating secrets or sharing one worker across multiple authenticated host apps, keep the current AUTH_TOKEN_SECRET in place and add comma- or newline-separated verifier secrets with AUTH_TOKEN_ADDITIONAL_SECRETS. BugDrop accepts tokens signed by any configured secret, so one app can migrate without breaking another app that still signs with the primary secret.

This protects the feedback endpoint from direct API calls that bypass browser CORS. CORS still matters for browser isolation, but it is not an authentication boundary. The token should include the exact target repository and expire quickly, usually within 5 minutes.

If installation status is also sensitive, set AUTH_TOKEN_REQUIRED_FOR_CHECK = "true" so the widget's /check/:owner/:repo request requires the same token.

Repository policy

Self-hosted Workers can restrict request targets with ALLOWED_REPOSITORIES. The server enforces it on GET /api/check/:owner/:repo, legacy POST /api/feedback, and structured POST /api/feedback. A reached denial returns 403 with { "error": "Repository is not allowed" } before installation lookup, token minting, uploads, repository visibility checks, or issue and label writes. It does not record successful feedback.

Both feedback formats still pass through the IP limiter, authentication middleware, and repository limiter before payload validation and repository policy. These earlier checks can return 401 or 429; an authenticated denied submission can consume quota. Installation checks enforce repository policy before optional token authentication, so a denied target returns 403 even without a token when check authentication is required. Successful authentication never overrides repository denial.

The setting applies across clients in the configured Worker environment. Unset, blank, or standalone * keeps the environment unrestricted by this policy. It grants no GitHub App permissions and replaces neither host-app authentication nor CORS. Limit the App's installed repositories separately. Health, static assets, public stats, webhook verification, and scheduled installation inventory are unaffected; this setting does not prohibit every GitHub API operation for unlisted repositories.

Reporting Security Issues

If you discover a security vulnerability in BugDrop, report it privately:

Ordinary email is not end-to-end encrypted. Use GitHub private vulnerability reporting for sensitive details, credentials, or unpublished exploit material. If GitHub reporting is unavailable, use either email fallback rather than disclosing the issue publicly.

BugDrop provides security fixes for the latest stable release and current hosted service. The maintainers target acknowledgment within 48 hours and an initial assessment within 7 days, but these are best-effort targets rather than guarantees. Reports about dependencies are in scope when the dependency is reachable through or materially affects BugDrop.

See the canonical BugDrop security policy for supported-version, scope, triage, and coordinated-disclosure details.

Next Steps