diff --git a/entry b/entry index baa6fff..bd8b40e 100755 --- a/entry +++ b/entry @@ -138179,12 +138179,13 @@ var JsonPayload = type({ "agent?": AgentName.or("null"), "prompt?": "string", "event?": "object", - "effort?": Effort, - "web?": ToolPermissionInput, - "search?": ToolPermissionInput, - "write?": ToolPermissionInput, - "bash?": BashPermissionInput + "effort?": Effort }); +var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"]; +function isCollaborator(event) { + const perm = event.authorPermission; + return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm); +} var Inputs = type({ prompt: "string", "effort?": Effort, @@ -138234,6 +138235,7 @@ function resolvePayload(repoSettings) { const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" }; const jsonAgent = jsonPayload?.agent; const resolvedAgent = agent2 ?? (jsonAgent !== void 0 && jsonAgent !== "null" && isAgentName(jsonAgent) ? jsonAgent : null); + const shouldRestrict = !isCollaborator(event); return { "~pullfrog": true, agent: resolvedAgent, @@ -138243,11 +138245,12 @@ function resolvePayload(repoSettings) { event, effort: inputs.effort ?? jsonPayload?.effort ?? "auto", cwd: resolveCwd(inputs.cwd), - // permissions: inputs > jsonPayload > repoSettings > fallbacks - web: inputs.web ?? jsonPayload?.web ?? repoSettings.web ?? "enabled", - search: inputs.search ?? jsonPayload?.search ?? repoSettings.search ?? "enabled", - write: inputs.write ?? jsonPayload?.write ?? repoSettings.write ?? "enabled", - bash: inputs.bash ?? jsonPayload?.bash ?? repoSettings.bash ?? "restricted" + // permissions: inputs > repoSettings > fallbacks + // bash is restricted for non-collaborators regardless of repoSettings + web: inputs.web ?? repoSettings.web ?? "enabled", + search: inputs.search ?? repoSettings.search ?? "enabled", + write: inputs.write ?? repoSettings.write ?? "enabled", + bash: inputs.bash ?? (shouldRestrict ? "restricted" : repoSettings.bash) ?? "restricted" }; } diff --git a/external.ts b/external.ts index 3e15cfd..9528c5e 100644 --- a/external.ts +++ b/external.ts @@ -93,6 +93,8 @@ interface BasePayloadEvent { comment_ids?: number[] | "all"; /** permission level of the user who triggered this event */ authorPermission?: AuthorPermission; + /** when true, runs silently without progress comments (e.g., auto-labeling) */ + silent?: boolean; [key: string]: any; } @@ -239,19 +241,8 @@ export type PayloadEvent = | ImplementPlanEvent | UnknownEvent; -// | undefined needed on optional props for exactOptionalPropertyTypes -export interface DispatchOptions { - /** when true, disables progress comment (no "leaping into action" comment, no report_progress tool) */ - disableProgressComment?: true | undefined; - /** granular tool permissions set server-side for dispatch workflows */ - web?: ToolPermission | undefined; - search?: ToolPermission | undefined; - write?: ToolPermission | undefined; - bash?: BashPermission | undefined; -} - // writeable payload type for building payloads -export interface WriteablePayload extends DispatchOptions { +export interface WriteablePayload { "~pullfrog": true; /** agent slug identifier (e.g., "claude", "codex", "gemini") */ agent: AgentName | null; diff --git a/utils/payload.ts b/utils/payload.ts index 7fc476f..00eb26e 100644 --- a/utils/payload.ts +++ b/utils/payload.ts @@ -4,6 +4,7 @@ import { type } from "arktype"; import { AgentName, type AgentName as AgentNameType, + type AuthorPermission, Effort, type PayloadEvent, } from "../external.ts"; @@ -14,18 +15,25 @@ const ToolPermissionInput = type.enumerated("disabled", "enabled"); const BashPermissionInput = type.enumerated("disabled", "restricted", "enabled"); // schema for JSON payload passed via prompt (internal dispatch invocation) +// note: permissions are intentionally NOT included here to prevent injection attacks +// permissions are derived from event.authorPermission instead const JsonPayload = type({ "~pullfrog": "true", "agent?": AgentName.or("null"), "prompt?": "string", "event?": "object", "effort?": Effort, - "web?": ToolPermissionInput, - "search?": ToolPermissionInput, - "write?": ToolPermissionInput, - "bash?": BashPermissionInput, }); +// permission levels that indicate collaborator status (have push access) +const COLLABORATOR_PERMISSIONS: AuthorPermission[] = ["admin", "maintain", "write"]; + +// check if the event author has collaborator-level permissions +function isCollaborator(event: PayloadEvent): boolean { + const perm = event.authorPermission; + return perm !== undefined && COLLABORATOR_PERMISSIONS.includes(perm); +} + // inputs schema - action inputs from core.getInput() // note: tool permissions use .or("undefined") because getInput() || undefined // explicitly sets the property to undefined when empty, which is different from @@ -103,7 +111,11 @@ export function resolvePayload(repoSettings: RepoSettings) { agent ?? (jsonAgent !== undefined && jsonAgent !== "null" && isAgentName(jsonAgent) ? jsonAgent : null); - // build payload - precedence: inputs > jsonPayload > repoSettings > fallbacks + // determine if permissions should be restricted based on event author + // non-collaborators (read, triage, none, or missing) get restricted bash access + const shouldRestrict = !isCollaborator(event); + + // build payload - precedence: inputs > repoSettings > fallbacks // note: modes are NOT in payload - they come from repoSettings in main() return { "~pullfrog": true as const, @@ -115,11 +127,12 @@ export function resolvePayload(repoSettings: RepoSettings) { effort: inputs.effort ?? jsonPayload?.effort ?? "auto", cwd: resolveCwd(inputs.cwd), - // permissions: inputs > jsonPayload > repoSettings > fallbacks - web: inputs.web ?? jsonPayload?.web ?? repoSettings.web ?? "enabled", - search: inputs.search ?? jsonPayload?.search ?? repoSettings.search ?? "enabled", - write: inputs.write ?? jsonPayload?.write ?? repoSettings.write ?? "enabled", - bash: inputs.bash ?? jsonPayload?.bash ?? repoSettings.bash ?? "restricted", + // permissions: inputs > repoSettings > fallbacks + // bash is restricted for non-collaborators regardless of repoSettings + web: inputs.web ?? repoSettings.web ?? "enabled", + search: inputs.search ?? repoSettings.search ?? "enabled", + write: inputs.write ?? repoSettings.write ?? "enabled", + bash: inputs.bash ?? (shouldRestrict ? "restricted" : repoSettings.bash) ?? "restricted", }; }