mirror of
https://github.com/anthropics/claude-code-action.git
synced 2026-08-03 09:48:31 +08:00
fix: allow @ in branch names (valid per git-check-ref-format) (#1411)
`validateBranchName` rejects branch names containing `@`, even though `git check-ref-format` permits `@` and GitHub itself accepts such branches. PRs whose head or base branch contains an `@` fail validation in-process before any git operation, so the action errors out immediately. Branch names with `@` show up in real workflows: ticket conventions like "TICKET-123@add-feature" (#998), leading-prefix conventions like "@hotfix/...", and agent tooling that appends "@<sessionid>" (#1305). There is no workaround other than renaming the branch, which is often not under the user's control. Branch names are never passed through a shell (git calls use execFileSync argv arrays), so `@` carries no injection risk. This is the same reasoning used to add `#` in #1167, `+` in #1248, and `,` in #1310. The bare name "@" (HEAD shorthand in git revision syntax) and the "@{" reflog sequence are still rejected. - Add `@` to the validateBranchName whitelist regex, including the leading position (the leading-character rule blocks option injection via `-`, which `@` cannot cause) - Reject the bare name "@" with a dedicated check - Update the surrounding comment, JSDoc, and error message to match - Add test cases for @-containing names and bare "@" Fixes #998 Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
e452eb9dce
commit
360be9c8fc
@ -27,14 +27,15 @@ function extractFirstLabel(githubData: FetchDataResult): string | undefined {
|
|||||||
* This prevents command injection by ensuring only safe characters are used.
|
* This prevents command injection by ensuring only safe characters are used.
|
||||||
*
|
*
|
||||||
* Valid branch names:
|
* Valid branch names:
|
||||||
* - Start with alphanumeric character (not dash, to prevent option injection)
|
* - Start with alphanumeric character or @ (not dash, to prevent option injection)
|
||||||
* - Contain only alphanumeric, forward slash, hyphen, underscore, period, or hash (#)
|
* - Contain only alphanumeric, forward slash, hyphen, underscore, period, hash (#), plus (+), comma (,), or at sign (@)
|
||||||
* - Do not start or end with a period
|
* - Do not start or end with a period
|
||||||
* - Do not end with a slash
|
* - Do not end with a slash
|
||||||
* - Do not contain '..' (path traversal)
|
* - Do not contain '..' (path traversal)
|
||||||
* - Do not contain '//' (consecutive slashes)
|
* - Do not contain '//' (consecutive slashes)
|
||||||
* - Do not end with '.lock'
|
* - Do not end with '.lock'
|
||||||
* - Do not contain '@{'
|
* - Do not contain '@{'
|
||||||
|
* - Are not the single character '@' (HEAD shorthand in git revision syntax)
|
||||||
* - Do not contain control characters or special git characters (~^:?*[\])
|
* - Do not contain control characters or special git characters (~^:?*[\])
|
||||||
*/
|
*/
|
||||||
export function validateBranchName(branchName: string): void {
|
export function validateBranchName(branchName: string): void {
|
||||||
@ -58,18 +59,21 @@ export function validateBranchName(branchName: string): void {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Strict whitelist pattern: alphanumeric start, then alphanumeric/slash/hyphen/underscore/period/hash/plus/comma.
|
// Strict whitelist pattern: alphanumeric or @ start, then alphanumeric/slash/hyphen/underscore/period/hash/plus/comma/at-sign.
|
||||||
// # is valid per git-check-ref-format and commonly used in branch names like "fix/#123-description".
|
// # is valid per git-check-ref-format and commonly used in branch names like "fix/#123-description".
|
||||||
// + is valid per git-check-ref-format and generated by Claude Code's EnterWorktree tool when
|
// + is valid per git-check-ref-format and generated by Claude Code's EnterWorktree tool when
|
||||||
// converting worktree names containing "/" (e.g. "feat/foo" becomes "worktree-feat+foo").
|
// converting worktree names containing "/" (e.g. "feat/foo" becomes "worktree-feat+foo").
|
||||||
// , is valid per git-check-ref-format and commonly appears in branch names derived from titles
|
// , is valid per git-check-ref-format and commonly appears in branch names derived from titles
|
||||||
// or external identifiers (e.g. place names like "feature/paris,france").
|
// or external identifiers (e.g. place names like "feature/paris,france").
|
||||||
|
// @ is valid per git-check-ref-format anywhere in a ref name, including the first character
|
||||||
|
// (e.g. ticket conventions like "TICKET-123@add-feature" or prefixes like "@hotfix/...");
|
||||||
|
// the bare name "@" (HEAD shorthand) and the "@{" sequence (reflog syntax) are rejected below.
|
||||||
// All git calls use execFileSync (not shell interpolation), so none of these characters carry injection risk.
|
// All git calls use execFileSync (not shell interpolation), so none of these characters carry injection risk.
|
||||||
const validPattern = /^[a-zA-Z0-9][a-zA-Z0-9/_.#+,-]*$/;
|
const validPattern = /^[a-zA-Z0-9@][a-zA-Z0-9/_.#+,@-]*$/;
|
||||||
|
|
||||||
if (!validPattern.test(branchName)) {
|
if (!validPattern.test(branchName)) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Invalid branch name: "${branchName}". Branch names must start with an alphanumeric character and contain only alphanumeric characters, forward slashes, hyphens, underscores, periods, hashes (#), plus signs (+), or commas (,).`,
|
`Invalid branch name: "${branchName}". Branch names must start with an alphanumeric character or '@' and contain only alphanumeric characters, forward slashes, hyphens, underscores, periods, hashes (#), plus signs (+), commas (,), or at signs (@).`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,6 +116,15 @@ export function validateBranchName(branchName: string): void {
|
|||||||
`Invalid branch name: "${branchName}". Branch names cannot contain '@{'`,
|
`Invalid branch name: "${branchName}". Branch names cannot contain '@{'`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Per git-check-ref-format, a refname cannot be the single character "@"; "@" also
|
||||||
|
// resolves to HEAD in git revision syntax, so a bare "@" must never reach git as a
|
||||||
|
// branch argument where it could be interpreted as a revision instead.
|
||||||
|
if (branchName === "@") {
|
||||||
|
throw new Error(
|
||||||
|
`Invalid branch name: "@". Branch names cannot be the single character '@'.`,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -64,6 +64,16 @@ describe("validateBranchName", () => {
|
|||||||
expect(() => validateBranchName("feature/paris,france")).not.toThrow();
|
expect(() => validateBranchName("feature/paris,france")).not.toThrow();
|
||||||
expect(() => validateBranchName("fix/issue-1,2,3")).not.toThrow();
|
expect(() => validateBranchName("fix/issue-1,2,3")).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should accept branch names containing @ (git-valid, used in team and tooling conventions)", () => {
|
||||||
|
// Reported in #998: branches like "TICKET-123@add-feature" were rejected, even
|
||||||
|
// though git check-ref-format and GitHub both accept @ anywhere in a ref name.
|
||||||
|
// Also common as a leading prefix (e.g. "@hotfix/...") and in agent-generated
|
||||||
|
// names ("task@sessionid"). Bare "@" and "@{" are still rejected.
|
||||||
|
expect(() => validateBranchName("TICKET-123@add-feature")).not.toThrow();
|
||||||
|
expect(() => validateBranchName("@hotfix/login-timeout")).not.toThrow();
|
||||||
|
expect(() => validateBranchName("agent/task@abc123")).not.toThrow();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("command injection attempts", () => {
|
describe("command injection attempts", () => {
|
||||||
@ -137,6 +147,12 @@ describe("validateBranchName", () => {
|
|||||||
expect(() => validateBranchName("HEAD@{yesterday}")).toThrow(/@{/);
|
expect(() => validateBranchName("HEAD@{yesterday}")).toThrow(/@{/);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it("should reject the single character @", () => {
|
||||||
|
// Per git-check-ref-format, a refname cannot be the single character "@";
|
||||||
|
// "@" also resolves to HEAD in git revision syntax.
|
||||||
|
expect(() => validateBranchName("@")).toThrow(/single character '@'/);
|
||||||
|
});
|
||||||
|
|
||||||
it("should reject .lock suffix", () => {
|
it("should reject .lock suffix", () => {
|
||||||
expect(() => validateBranchName("branch.lock")).toThrow(/\.lock/);
|
expect(() => validateBranchName("branch.lock")).toThrow(/\.lock/);
|
||||||
expect(() => validateBranchName("feature.lock")).toThrow(/\.lock/);
|
expect(() => validateBranchName("feature.lock")).toThrow(/\.lock/);
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user