Drop permissions from webhook payload, fix potential vuln, simplify dispatch options
This commit is contained in:
committed by
pullfrog[bot]
parent
93d74a9bea
commit
b64721edcf
@@ -138179,12 +138179,13 @@ var JsonPayload = type({
|
|||||||
"agent?": AgentName.or("null"),
|
"agent?": AgentName.or("null"),
|
||||||
"prompt?": "string",
|
"prompt?": "string",
|
||||||
"event?": "object",
|
"event?": "object",
|
||||||
"effort?": Effort,
|
"effort?": Effort
|
||||||
"web?": ToolPermissionInput,
|
|
||||||
"search?": ToolPermissionInput,
|
|
||||||
"write?": ToolPermissionInput,
|
|
||||||
"bash?": BashPermissionInput
|
|
||||||
});
|
});
|
||||||
|
var COLLABORATOR_PERMISSIONS = ["admin", "maintain", "write"];
|
||||||
|
function isCollaborator(event) {
|
||||||
|
const perm = event.authorPermission;
|
||||||
|
return perm !== void 0 && COLLABORATOR_PERMISSIONS.includes(perm);
|
||||||
|
}
|
||||||
var Inputs = type({
|
var Inputs = type({
|
||||||
prompt: "string",
|
prompt: "string",
|
||||||
"effort?": Effort,
|
"effort?": Effort,
|
||||||
@@ -138234,6 +138235,7 @@ function resolvePayload(repoSettings) {
|
|||||||
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
const event = isPayloadEvent(rawEvent) ? rawEvent : { trigger: "unknown" };
|
||||||
const jsonAgent = jsonPayload?.agent;
|
const jsonAgent = jsonPayload?.agent;
|
||||||
const resolvedAgent = agent2 ?? (jsonAgent !== void 0 && jsonAgent !== "null" && isAgentName(jsonAgent) ? jsonAgent : null);
|
const resolvedAgent = agent2 ?? (jsonAgent !== void 0 && jsonAgent !== "null" && isAgentName(jsonAgent) ? jsonAgent : null);
|
||||||
|
const shouldRestrict = !isCollaborator(event);
|
||||||
return {
|
return {
|
||||||
"~pullfrog": true,
|
"~pullfrog": true,
|
||||||
agent: resolvedAgent,
|
agent: resolvedAgent,
|
||||||
@@ -138243,11 +138245,12 @@ function resolvePayload(repoSettings) {
|
|||||||
event,
|
event,
|
||||||
effort: inputs.effort ?? jsonPayload?.effort ?? "auto",
|
effort: inputs.effort ?? jsonPayload?.effort ?? "auto",
|
||||||
cwd: resolveCwd(inputs.cwd),
|
cwd: resolveCwd(inputs.cwd),
|
||||||
// permissions: inputs > jsonPayload > repoSettings > fallbacks
|
// permissions: inputs > repoSettings > fallbacks
|
||||||
web: inputs.web ?? jsonPayload?.web ?? repoSettings.web ?? "enabled",
|
// bash is restricted for non-collaborators regardless of repoSettings
|
||||||
search: inputs.search ?? jsonPayload?.search ?? repoSettings.search ?? "enabled",
|
web: inputs.web ?? repoSettings.web ?? "enabled",
|
||||||
write: inputs.write ?? jsonPayload?.write ?? repoSettings.write ?? "enabled",
|
search: inputs.search ?? repoSettings.search ?? "enabled",
|
||||||
bash: inputs.bash ?? jsonPayload?.bash ?? repoSettings.bash ?? "restricted"
|
write: inputs.write ?? repoSettings.write ?? "enabled",
|
||||||
|
bash: inputs.bash ?? (shouldRestrict ? "restricted" : repoSettings.bash) ?? "restricted"
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+3
-12
@@ -93,6 +93,8 @@ interface BasePayloadEvent {
|
|||||||
comment_ids?: number[] | "all";
|
comment_ids?: number[] | "all";
|
||||||
/** permission level of the user who triggered this event */
|
/** permission level of the user who triggered this event */
|
||||||
authorPermission?: AuthorPermission;
|
authorPermission?: AuthorPermission;
|
||||||
|
/** when true, runs silently without progress comments (e.g., auto-labeling) */
|
||||||
|
silent?: boolean;
|
||||||
[key: string]: any;
|
[key: string]: any;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -239,19 +241,8 @@ export type PayloadEvent =
|
|||||||
| ImplementPlanEvent
|
| ImplementPlanEvent
|
||||||
| UnknownEvent;
|
| 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
|
// writeable payload type for building payloads
|
||||||
export interface WriteablePayload extends DispatchOptions {
|
export interface WriteablePayload {
|
||||||
"~pullfrog": true;
|
"~pullfrog": true;
|
||||||
/** agent slug identifier (e.g., "claude", "codex", "gemini") */
|
/** agent slug identifier (e.g., "claude", "codex", "gemini") */
|
||||||
agent: AgentName | null;
|
agent: AgentName | null;
|
||||||
|
|||||||
+23
-10
@@ -4,6 +4,7 @@ import { type } from "arktype";
|
|||||||
import {
|
import {
|
||||||
AgentName,
|
AgentName,
|
||||||
type AgentName as AgentNameType,
|
type AgentName as AgentNameType,
|
||||||
|
type AuthorPermission,
|
||||||
Effort,
|
Effort,
|
||||||
type PayloadEvent,
|
type PayloadEvent,
|
||||||
} from "../external.ts";
|
} from "../external.ts";
|
||||||
@@ -14,18 +15,25 @@ const ToolPermissionInput = type.enumerated("disabled", "enabled");
|
|||||||
const BashPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
const BashPermissionInput = type.enumerated("disabled", "restricted", "enabled");
|
||||||
|
|
||||||
// schema for JSON payload passed via prompt (internal dispatch invocation)
|
// 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({
|
const JsonPayload = type({
|
||||||
"~pullfrog": "true",
|
"~pullfrog": "true",
|
||||||
"agent?": AgentName.or("null"),
|
"agent?": AgentName.or("null"),
|
||||||
"prompt?": "string",
|
"prompt?": "string",
|
||||||
"event?": "object",
|
"event?": "object",
|
||||||
"effort?": Effort,
|
"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()
|
// inputs schema - action inputs from core.getInput()
|
||||||
// note: tool permissions use .or("undefined") because getInput() || undefined
|
// note: tool permissions use .or("undefined") because getInput() || undefined
|
||||||
// explicitly sets the property to undefined when empty, which is different from
|
// explicitly sets the property to undefined when empty, which is different from
|
||||||
@@ -103,7 +111,11 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
|||||||
agent ??
|
agent ??
|
||||||
(jsonAgent !== undefined && jsonAgent !== "null" && isAgentName(jsonAgent) ? jsonAgent : null);
|
(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()
|
// note: modes are NOT in payload - they come from repoSettings in main()
|
||||||
return {
|
return {
|
||||||
"~pullfrog": true as const,
|
"~pullfrog": true as const,
|
||||||
@@ -115,11 +127,12 @@ export function resolvePayload(repoSettings: RepoSettings) {
|
|||||||
effort: inputs.effort ?? jsonPayload?.effort ?? "auto",
|
effort: inputs.effort ?? jsonPayload?.effort ?? "auto",
|
||||||
cwd: resolveCwd(inputs.cwd),
|
cwd: resolveCwd(inputs.cwd),
|
||||||
|
|
||||||
// permissions: inputs > jsonPayload > repoSettings > fallbacks
|
// permissions: inputs > repoSettings > fallbacks
|
||||||
web: inputs.web ?? jsonPayload?.web ?? repoSettings.web ?? "enabled",
|
// bash is restricted for non-collaborators regardless of repoSettings
|
||||||
search: inputs.search ?? jsonPayload?.search ?? repoSettings.search ?? "enabled",
|
web: inputs.web ?? repoSettings.web ?? "enabled",
|
||||||
write: inputs.write ?? jsonPayload?.write ?? repoSettings.write ?? "enabled",
|
search: inputs.search ?? repoSettings.search ?? "enabled",
|
||||||
bash: inputs.bash ?? jsonPayload?.bash ?? repoSettings.bash ?? "restricted",
|
write: inputs.write ?? repoSettings.write ?? "enabled",
|
||||||
|
bash: inputs.bash ?? (shouldRestrict ? "restricted" : repoSettings.bash) ?? "restricted",
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user