The first question any serious engineering team asks before installing Branchpost is some version of "what can it do to my repo?" The answer should be specific, not reassuring. "We follow least privilege" is the kind of phrase that means nothing unless you can recite the exact scopes you ask for and defend each one. So here are ours, in full, with the reasoning, and a list of the scopes we deliberately refused even when they would have made the product easier to build.
This post is meant to be a trust artifact. If you're evaluating any AI tool that touches your codebase, this is the shape of disclosure you should demand from it.
What Branchpost actually does to your repo
Before the scopes make sense, the workflow needs to be clear. Branchpost does four things against a connected repository:
- Reads recent commits, file paths, and a configured set of context files (README, style guides, existing posts) to assemble publication context.
- Creates a new branch off your default branch.
- Writes one or more
.mdxfiles to a configured content directory on that branch. - Opens a pull request from that branch with the draft inside.
That's it. No merges. No force pushes. No writes outside the content directory. No actions on issues, releases, or deployments. Every scope below maps to one of those four operations.
The scopes we request
Branchpost is a GitHub App, not an OAuth app, which matters: GitHub Apps install per repository, get short-lived tokens, and can be revoked with one click without affecting any human user's account. The permissions are scoped to the installation, not to a person.
Here is the full permission set from our app manifest:
default_permissions:
contents: write
pull_requests: write
metadata: read
default_events: []Three permissions. No webhooks. Let me walk through each.
contents: write
This is the one that makes everything else possible and also the one that deserves the most scrutiny. contents: write lets Branchpost read repository contents and create commits on branches.
We need read to assemble publication context: your existing posts inform tone, your README informs what the project is, your recent commits inform what might be worth writing about. You can read more about that pipeline in writing prompts that produce code-aware blog drafts.
We need write to commit the generated .mdx file. Every commit Branchpost makes is to a fresh branch named branchpost/draft-{timestamp}-{slug}. We never commit to your default branch. We never commit to any branch we did not create. This is enforced in our Octokit wrapper, not just in documentation:
async function commitDraft(installation: Installation, draft: Draft) {
const branch = `branchpost/draft-${Date.now()}-${draft.slug}`
if (branch === installation.defaultBranch) {
throw new Error('refusing to commit to default branch')
}
if (!branch.startsWith('branchpost/')) {
throw new Error('refusing to commit to non-branchpost branch')
}
await octokit.git.createRef({ ref: `refs/heads/${branch}`, sha: baseSha, ...repo })
await octokit.repos.createOrUpdateFileContents({
path: `${installation.contentDir}/${draft.slug}.mdx`,
branch,
content: Buffer.from(draft.body).toString('base64'),
message: `draft: ${draft.title}`,
...repo,
})
}Two refusals, hardcoded. The default branch check is belt; the prefix check is suspenders. Either one would catch a bug; we wanted both.
pull_requests: write
This permission lets Branchpost open the PR with the draft. It does not let us merge, approve, or dismiss reviews. GitHub splits those into separate scopes that we do not request. The most a Branchpost installation can do at the PR layer is open one, comment on one it opened, and close one it opened. It cannot accept its own work.
The reason we built the product around pull requests in the first place is that the review machinery already exists and your team already trusts it. That argument lives in why we make every blog draft a pull request.
metadata: read
This is mandatory for every GitHub App. It grants read access to basic repo metadata: name, default branch, visibility. We use the default branch to know what to fork from. You cannot decline this scope; GitHub requires it on every install.
The scopes we refused
This is the more important list. Every one of these would have made some part of the product simpler. We declined them anyway.
contents: admin. Would let us change branch protection rules. There is no scenario where a content tool should be loosening your protections to merge its own work. Refused.
pull_requests: admin (in the form of merge permissions via auto-merge). Would let us auto-merge approved PRs. We explicitly want a human to click merge. The whole point of reviewing drafts like code collapses if the tool can merge itself. Refused.
workflows: write. Would let us modify your GitHub Actions. We do not need to. Branchpost runs on our infrastructure, not yours. Touching your workflows would mean we could change what runs on your CI, which is a much larger blast radius than writing a markdown file. Refused.
issues: write. Would let us file issues for topic ideas. Tempting, because topic queues are part of the product. We chose to manage them in our own UI instead, because once you grant issue write you have granted the ability to spam, mention users, and trigger notifications across the whole project. The cost was not worth it. Refused.
members: read. Would let us see your org structure to attribute commits more intelligently. We don't need it. The author of a Branchpost commit is the Branchpost bot, not a human. Refused.
Webhook events on push, pull_request, issues, etc. We register zero webhook events. Branchpost is poll-based on a schedule you control, not reactive to every push in your repo. This is a deliberate cost and complexity tradeoff: we accept slightly less freshness in exchange for not having a firehose of your repo activity sitting in our queue.
What this means in practice
If you install Branchpost and then revoke it tomorrow, here is what you find in your repo: some branches named branchpost/draft-*, possibly some open PRs from those branches, and the commits inside them. Nothing in your default branch unless you merged it. No modified workflows. No changed settings. No filed issues. No comments on anything we did not open ourselves.
That's the bar. When you evaluate the next AI tool that asks for repo access, ask for its manifest. If the answer is "we need repo scope" (the OAuth equivalent of full read/write on everything including settings, webhooks, and deployments), that is not a least-privilege design. That is a tool that wants the keys to the building because writing the actual permissions list was too much work.
Branchpost watches your repo and opens PRs with blog drafts. You review them like code. branchpost.dev