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 requests the minimum permissions necessary to function:

Permission Access Level Purpose
Issues Read & Write Create bug reports, feature requests, and questions as GitHub Issues
Contents Read & Write Store screenshots in the repository on the bugdrop-screenshots branch

GitHub App Contents permissions are repository-scoped, not branch-scoped. BugDrop's implementation writes screenshots only to .bugdrop/screenshots/ on the dedicated bugdrop-screenshots branch, but GitHub does not provide a narrower branch-only permission for this API.

BugDrop does not request access to:

  • Pull requests
  • Actions or workflows
  • Secrets or environment variables
  • Organization settings
  • Collaborators or team membership
  • Any other repository or account data

You can review and revoke the app's access at any time from your GitHub settings under Applications > Installed GitHub Apps.

Data Storage

BugDrop follows a "GitHub-native" storage model. All data stays within your GitHub repository -- nothing is stored on external servers.

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
  • No external data storage -- All data (issues and screenshots) is stored in your GitHub repository
  • 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>Customer 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.

Surface Behavior Recommended control
Regular DOM elements Marked element box is covered Mark the smallest stable container that fully encloses the sensitive content
Password and credit-card inputs Covered automatically No extra attribute required, but explicit marks are fine
Open Shadow DOM Traversed when the browser exposes shadowRoot Mark inner controls or the host
Closed Shadow DOM Not traversed Mark the host custom element
Iframes Iframe internals are not traversed Mark the iframe element if the whole embedded frame is sensitive
Canvas, image, SVG, video Internal pixels are not inspected Mark the element or a containing wrapper
Pseudo-elements and highly custom controls Generated pixels are not inspected separately Mark a stable wrapper around the whole control
Native viewport capture fallback Element masks cannot be applied Avoid 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 acts as a pass-through to the GitHub API -- it receives the form data, creates the issue and uploads the screenshot, and discards the data. Nothing is persisted on the Cloudflare Worker.

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

Scope Limit Window Description
Per IP 10 requests 15 minutes Limits individual users from flooding the API
Per Repository 50 requests 1 hour Limits 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:

Header Description Example
X-RateLimit-Limit Maximum requests allowed in the window 10
X-RateLimit-Remaining Requests remaining in the current window 7
Retry-After Seconds 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.

Reporting Security Issues

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

  • Do not create a public GitHub Issue for security vulnerabilities
  • Contact the maintainers directly through GitHub's private vulnerability reporting feature on the BugDrop repository

Next Steps