Skip to content

Security: JeelGajera/Dispatch

SECURITY.md

Security policy

Reporting a vulnerability

Do not open a public issue.

Use GitHub's private vulnerability reporting, which goes only to the maintainers.

If that is unavailable to you, email jeelgajera200@gmail.com with SECURITY in the subject.

Please include what you can: the version or commit, what an attacker gains, and the smallest reproduction you have. A rough report sent early beats a polished one sent late.

What to expect

Acknowledgement Within 3 working days
Initial assessment Within 7 days
Fix or mitigation plan Communicated with the assessment
Credit Offered in the advisory unless you prefer otherwise

This is a solo-maintained project, not a company with an on-call rota. If something is being actively exploited, say so and I will treat it accordingly.

Scope

In scope

  • Webhook signature bypass
  • Anything that lets an unauthorised person run a /dispatch command
  • Leaking a provider key, a GitHub token, or an installation secret — including into logs, comments, decision logs, or recorded fixtures
  • Privilege escalation through a crafted issue, comment, pull request, or config
  • Any path by which Dispatch executes attacker-controlled code
  • Redaction failing to strip what data.redact says it strips

Out of scope

  • The model returning a wrong answer. Miscalibration is a quality bug, not a vulnerability — report it as a normal issue with dispatch eval output.
  • Vulnerabilities in a dependency with no exploitable path through Dispatch. Report those upstream; a Dependabot alert on its own is not a finding.
  • Anything requiring an already-compromised maintainer account or runner.
  • A pull_request_target workflow that someone added a checkout step to. That is documented as a repository takeover in three places; doing it anyway is a misconfiguration, not a vulnerability here.

Security properties Dispatch tries to hold

These are the promises. If you can break one, that is a finding.

Webhooks are verified over raw bytes

HMAC-SHA256 over the raw request body, compared with timingSafeEqual, with a length check first because timingSafeEqual throws on a mismatch and that throw is itself an oracle. Re-serialising a parsed body changes key order and whitespace; the verifier never sees a re-encoded payload.

Commands require write access

/dispatch commands run only for OWNER, MEMBER, or COLLABORATOR, taken from the author_association GitHub puts on the comment. A command must be alone on its line, and commands inside markdown quotes are ignored — otherwise quoting someone's comment, or writing "you can run /dispatch undo to revert this", would execute it.

An unrecognised verb is rejected rather than falling back to plain triage.

Pull request code is never executed

Dispatch reads pull request metadata through the API. It never checks out a head, never runs a build, and never evaluates anything from a contributor's branch. This is what makes the pull_request_target sample safe, and the sample carries the warning inline.

Redaction happens before state leaves the process

Every pattern in data.redact is applied on the way out, including into recorded fixtures — the recorder asks the provider what it actually sent rather than recording what it was handed. A test plants a credential-shaped string and asserts it does not appear in any cassette.

Logs redact fields whose names look like credentials.

Installation keys are encrypted at rest

In the App, each installation's provider key is sealed with AES-256-GCM under a master key held in the environment and never in the database. GCM specifically for the authentication tag: tampered ciphertext would otherwise decrypt to garbage and then be sent to the provider as an API key. A dump of the database alone does not let an attacker spend a maintainer's provider budget.

Least privilege

The App manifest requests contents: read, issues: write, pull_requests: write, metadata: read. It does not request contents: write, administration, or members.

Destructive actions require opt-in

close, lock, minimize and convert_to_discussion are suppressed unless the operation is named in allowDestructive. No shipped default enables one, and a test asserts that across the whole fixture corpus.

What Dispatch sends to the model

Documented in full in the privacy guide and above the fold in the README. In short: the item being triaged and its repository context — not your repository contents, not files you did not change, not your other repositories.

Setting data.sendBodies: false produces a working, weaker triage from structural signals alone, and is covered by a test.

Supported versions

Pre-1.0. Security fixes land on master and in the next release; there are no maintained backport branches yet. Once 1.0 ships, the current major will receive fixes.

Hardening your own installation

  • Pin the Action to a commit rather than a moving tag
  • Keep mode: shadow until dispatch eval shows calibration you believe
  • Leave allowDestructive empty unless you have a specific reason
  • Set budget.maxRequestsPerDay so a runaway loop has a ceiling
  • Self-host the App if issue text must not leave your infrastructure

There aren't any published security advisories