Restrict github token (#140)

This commit is contained in:
Colin McDonnell
2026-01-21 02:17:46 +00:00
committed by pullfrog[bot]
parent 04cc24bf64
commit 01ee59a96c
6 changed files with 48 additions and 18 deletions
+3 -5
View File
@@ -30,11 +30,9 @@ function filterEnv(isPublicRepo: boolean): Record<string, string> {
if (isPublicRepo && isSensitive(key)) continue;
filtered[key] = value;
}
// restore original GITHUB_TOKEN (the one set by GitHub Actions, not our installation token)
// this allows git operations in subprocesses to work while keeping our installation token secure
if (process.env.ORIGINAL_GITHUB_TOKEN) {
filtered.GITHUB_TOKEN = process.env.ORIGINAL_GITHUB_TOKEN;
}
// never restore GITHUB_TOKEN - agents must use MCP tools for git/gh operations
// this ensures all git operations go through auditable MCP tools where we can
// enforce branch protection, scan for secrets, etc.
return filtered;
}
+13 -3
View File
@@ -1,8 +1,8 @@
import { type } from "arktype";
import type { ToolContext } from "./server.ts";
import { log } from "../utils/cli.ts";
import { containsSecrets } from "../utils/secrets.ts";
import { $ } from "../utils/shell.ts";
import type { ToolContext } from "./server.ts";
import { execute, tool } from "./shared.ts";
export function CreateBranchTool(ctx: ToolContext) {
@@ -141,11 +141,13 @@ export const PushBranch = type({
force: type.boolean.describe("Force push (use with caution)").default(false),
});
export function PushBranchTool(_ctx: ToolContext) {
export function PushBranchTool(ctx: ToolContext) {
const defaultBranch = ctx.repo.repo.default_branch || "main";
return tool({
name: "push_branch",
description:
"Push the current branch (or specified branch) to the remote repository. Git automatically determines the correct remote based on branch config (set by checkout_pr for fork PRs). Never force push unless explicitly requested.",
"Push the current branch (or specified branch) to the remote repository. Git automatically determines the correct remote based on branch config (set by checkout_pr for fork PRs). Never force push unless explicitly requested. Pushes to the default branch are blocked.",
parameters: PushBranch,
execute: execute(async ({ branchName, force }) => {
const branch = branchName || $("git", ["rev-parse", "--abbrev-ref", "HEAD"], { log: false });
@@ -168,6 +170,14 @@ export function PushBranchTool(_ctx: ToolContext) {
// no configured merge ref, use local branch name
}
// block pushes to default branch
if (remoteBranch === defaultBranch) {
throw new Error(
`Push blocked: cannot push directly to default branch '${remoteBranch}'. ` +
`Create a feature branch and open a PR instead.`
);
}
// use refspec when local and remote branch names differ
const refspec = branch === remoteBranch ? branch : `${branch}:${remoteBranch}`;
const args = force
+3
View File
@@ -8,6 +8,9 @@ import { execute, tool } from "./shared.ts";
// fragment for nested replyTo (5 levels deep covers most threads)
// because in_reply_to_id generally points to the top-level comment
// this doesn't actually work as expected
// TOD: implement an API endpoint with aggressive caching that fetches the PR's reviewThreads via graphql
// separately fetch all the comments associated with the particular review
// merge them, then construct the full thread context
const REPLY_TO_FRAGMENT = `
replyTo {
databaseId