From a3f1593e28894637a0e4940baba22706ccba190c Mon Sep 17 00:00:00 2001 From: David Blass Date: Tue, 11 Nov 2025 17:08:20 -0500 Subject: [PATCH] revoke installation token after action run --- entry.js | 38 ++++++++++++++++++++++++++++++++------ fixtures/basic.txt | 2 +- main.ts | 17 +++++++++++++++-- todo.md | 21 ++++++++++++++------- utils/github.ts | 39 +++++++++++++++++++++++++++++++++------ 5 files changed, 95 insertions(+), 22 deletions(-) diff --git a/entry.js b/entry.js index 09355d7..b011bc1 100755 --- a/entry.js +++ b/entry.js @@ -41044,12 +41044,30 @@ async function setupGitHubInstallationToken() { if (existingToken) { core2.setSecret(existingToken); log.info("Using provided GitHub installation token"); - return existingToken; + return { githubInstallationToken: existingToken, wasAcquired: false }; + } + const githubInstallationToken = await acquireNewToken(); + core2.setSecret(githubInstallationToken); + process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; + return { githubInstallationToken, wasAcquired: true }; +} +async function revokeInstallationToken(token) { + const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; + try { + await fetch(`${apiUrl}/installation/token`, { + method: "DELETE", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28" + } + }); + log.info("Installation token revoked"); + } catch (error2) { + log.warning( + `Failed to revoke installation token: ${error2 instanceof Error ? error2.message : String(error2)}` + ); } - const token = await acquireNewToken(); - core2.setSecret(token); - process.env.GITHUB_INSTALLATION_TOKEN = token; - return token; } function parseRepoContext() { const githubRepo = process.env.GITHUB_REPOSITORY; @@ -41430,10 +41448,14 @@ var Inputs = type({ "anthropic_api_key?": "string | undefined" }); async function main(inputs) { + let tokenToRevoke = null; try { log.info(`\u{1F438} Running pullfrog/action@${package_default.version}...`); setupGitConfig(); - const githubInstallationToken = await setupGitHubInstallationToken(); + const { githubInstallationToken, wasAcquired } = await setupGitHubInstallationToken(); + if (wasAcquired) { + tokenToRevoke = githubInstallationToken; + } const repoContext = parseRepoContext(); const repoSettings = await getRepoSettings(githubInstallationToken, repoContext); const agent = repoSettings.defaultAgent || "claude"; @@ -41471,6 +41493,10 @@ async function main(inputs) { success: false, error: errorMessage }; + } finally { + if (tokenToRevoke) { + await revokeInstallationToken(tokenToRevoke); + } } } diff --git a/fixtures/basic.txt b/fixtures/basic.txt index 7685a2c..5036f58 100644 --- a/fixtures/basic.txt +++ b/fixtures/basic.txt @@ -1 +1 @@ -create a JSON file with the current process.env \ No newline at end of file +ribbit like a froggy diff --git a/main.ts b/main.ts index 9fd960e..95d1861 100644 --- a/main.ts +++ b/main.ts @@ -4,7 +4,11 @@ import { createMcpConfigs } from "./mcp/config.ts"; import packageJson from "./package.json" with { type: "json" }; import { getRepoSettings } from "./utils/api.ts"; import { log } from "./utils/cli.ts"; -import { parseRepoContext, setupGitHubInstallationToken } from "./utils/github.ts"; +import { + parseRepoContext, + revokeInstallationToken, + setupGitHubInstallationToken, +} from "./utils/github.ts"; import { setupGitAuth, setupGitConfig } from "./utils/setup.ts"; export const Inputs = type({ @@ -23,12 +27,17 @@ export interface MainResult { export type PromptJSON = {}; export async function main(inputs: Inputs): Promise { + let tokenToRevoke: string | null = null; + try { log.info(`🐸 Running pullfrog/action@${packageJson.version}...`); setupGitConfig(); - const githubInstallationToken = await setupGitHubInstallationToken(); + const { githubInstallationToken, wasAcquired } = await setupGitHubInstallationToken(); + if (wasAcquired) { + tokenToRevoke = githubInstallationToken; + } const repoContext = parseRepoContext(); // Fetch repo settings (agent, permissions, workflows) from API @@ -82,5 +91,9 @@ export async function main(inputs: Inputs): Promise { success: false, error: errorMessage, }; + } finally { + if (tokenToRevoke) { + await revokeInstallationToken(tokenToRevoke); + } } } diff --git a/todo.md b/todo.md index 8b4e77b..9a42224 100644 --- a/todo.md +++ b/todo.md @@ -1,13 +1,20 @@ +## CURRENT + +[] look into trigger.yml without installation +[] try to find heavy claude code user +[] investigate including terminal output from bash commands as collapsed groups +[] test initialization trade offs for pullfrog.yml + +## MAYBE + +[] investigate repo config file + +## DONE + [x] add modes to prompt [x] progressively update comment [x] don't allow rejecting prs [x] fix pnpm caching [x] fix prompt to avoid narration like "I just read all tools from MCP server" [x] avoid exposing env adding ## SECURITY prompt -[] investigate including terminal output from bash commands as collapsed groups -[] test initialization trade offs for pullfrog.yml -[] try to find heavy claude code user -[] investigate repo config file? -[] look into trigger.yml without installation -[] cancel installation token at the end of github aciton - \ No newline at end of file +[x] cancel installation token at the end of github aciton diff --git a/utils/github.ts b/utils/github.ts index c300f19..3ea071c 100644 --- a/utils/github.ts +++ b/utils/github.ts @@ -234,21 +234,48 @@ async function acquireNewToken(): Promise { /** * Setup GitHub installation token for the action + * Returns the token and whether it was acquired (needs revocation) */ -export async function setupGitHubInstallationToken(): Promise { +export async function setupGitHubInstallationToken(): Promise<{ + githubInstallationToken: string; + wasAcquired: boolean; +}> { const existingToken = checkExistingToken(); if (existingToken) { core.setSecret(existingToken); log.info("Using provided GitHub installation token"); - return existingToken; + return { githubInstallationToken: existingToken, wasAcquired: false }; } - const token = await acquireNewToken(); + const githubInstallationToken = await acquireNewToken(); - core.setSecret(token); - process.env.GITHUB_INSTALLATION_TOKEN = token; + core.setSecret(githubInstallationToken); + process.env.GITHUB_INSTALLATION_TOKEN = githubInstallationToken; - return token; + return { githubInstallationToken, wasAcquired: true }; +} + +/** + * Revoke GitHub installation token + */ +export async function revokeInstallationToken(token: string): Promise { + const apiUrl = process.env.GITHUB_API_URL || "https://api.github.com"; + + try { + await fetch(`${apiUrl}/installation/token`, { + method: "DELETE", + headers: { + Accept: "application/vnd.github+json", + Authorization: `Bearer ${token}`, + "X-GitHub-Api-Version": "2022-11-28", + }, + }); + log.info("Installation token revoked"); + } catch (error) { + log.warning( + `Failed to revoke installation token: ${error instanceof Error ? error.message : String(error)}` + ); + } } export interface RepoContext {