Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 18 additions & 10 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,16 +63,25 @@ const ENTERPRISE_USER = 'EnterpriseUserAccount';
const allowedChars = [
'0-9', // ASCII digits
'A-Za-z', // ASCII alphabets
'\\p{Script=Hiragana}', // Hiragana
'\\p{Script=Katakana}', // Katakana
'\\p{Script=Han}', // CJK Han (Kanji)
'\\p{Script=Hangul}', // Hangul
'._/@\\-+', // ASCII symbols (Common filename-safe symbols allowed by Git)
'\\u3005' // Ideographic iteration mark: 々
'\\p{Script_Extensions=Hiragana}', // Hiragana
'\\p{Script_Extensions=Katakana}', // Katakana
'\\p{Script_Extensions=Han}', // CJK Han (Kanji)
'\\p{Script_Extensions=Hangul}', // Hangul
'\\p{Script_Extensions=Greek}', // Greek (e.g. α β γ)
',._/#%@\\-+=()', // ASCII symbols (Common filename-safe symbols allowed by Git)
'\\u3005', // Ideographic iteration mark: 々
'\\u3010', // Full-width opening black lenticular bracket: 【
'\\u3011', // Full-width closing black lenticular bracket: 】
'\\uff1a', // Full-width colon: :
'\\uff08', // Full-width opening parenthesis: (
'\\uff09', // Full-width closing parenthesis: )
'\\u3000', // Full-width space
'\\uff10-\\uff19', // Full-width digits: 0-9
'\\uff21-\\uff3a', // Full-width uppercase letters: A-Z
'\\uff41-\\uff5a' // Full-width lowercase letters: a-z
];
const BRANCH_NAME_ALLOWED_CHAR_RE = new RegExp(`^[${allowedChars.join('')}]+$`, 'u');
const BRANCH_NAME_DANGEROUS_CHAR_RE = /['"`;!#$&<>|]/u;
const BRANCH_NAME_FORBIDDEN_SEQUENCE_RE = /(\/\/|(^|\/)\.|\/$|\.\.|@\{|\.lock$|\.$)/;
const BRANCH_NAME_DANGEROUS_CHAR_RE = /['"`;!$&<>|]/u;

/**
* Trim shell command indents
Expand Down Expand Up @@ -153,8 +162,7 @@ function isSafeBranchName(name) {
return (
BRANCH_NAME_ALLOWED_CHAR_RE.test(name) &&
!hasControlCharacters(name) &&
!BRANCH_NAME_DANGEROUS_CHAR_RE.test(name) &&
!BRANCH_NAME_FORBIDDEN_SEQUENCE_RE.test(name)
!BRANCH_NAME_DANGEROUS_CHAR_RE.test(name)
);
}

Expand Down
37 changes: 12 additions & 25 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -277,23 +277,24 @@ describe('index', function () {
{ category: 'ASCII digits', value: 'feature/20260615' },
{ category: 'ASCII uppercase', value: 'feature/RELEASE' },
{ category: 'ASCII lowercase', value: 'feature/release' },
{ category: 'hiragana', value: 'feature/ひらがなゔ' },
{ category: 'katakana', value: 'feature/カタカナヴ' },
{ category: 'hiragana', value: 'feature/ひらがなゔー' },
{ category: 'katakana', value: 'feature/カタカナヴー' },
{ category: 'cjk', value: 'feature/漢字東京' },
{ category: 'hangul', value: 'feature/한글테스트' }
{ category: 'hangul', value: 'feature/한글테스트' },
{ category: 'greek', value: 'feature/alpha-β' },
{ category: 'jp punctuation', value: 'feature/、。々' },
{ category: 'jp brackets', value: 'feature/「名」【称】' },
{ category: 'fullwidth symbols', value: 'feature/():' },
{ category: 'fullwidth digits', value: 'feature/987' },
{ category: 'fullwidth letters', value: 'feature/Mm' }
];
const rejectedBranchCategorySamples = [
{ category: 'ASCII symbols', value: 'feature/a+b.c=d,e@_](-)%' },
{ category: 'extended latin', value: 'feature/Angstrom-Ångström-Đ' },
{ category: 'greek', value: 'feature/alpha-β' },
{ category: 'control-special', value: 'feature/zero\u200bwidth※↑→−' },
{ category: 'circled numbers', value: 'feature/④⑥⑧' },
{ category: 'jp punctuation', value: 'feature/、。々' },
{ category: 'jp brackets', value: 'feature/「名」【称】' },
{ category: 'variation selector', value: 'feature/テスト\ufe0f' },
{ category: 'fullwidth symbols', value: 'feature/&():>_' },
{ category: 'fullwidth digits', value: 'feature/987' },
{ category: 'fullwidth letters', value: 'feature/Mm' }
{ category: 'fullwidth symbols', value: 'feature/#&>_' }
];

beforeEach(() => {
Expand Down Expand Up @@ -325,7 +326,7 @@ describe('index', function () {
);
});

[`'`, '"', '`', ';', '!', '#', '&', '$', '<', '>', '|'].forEach(char => {
[`'`, '"', '`', ';', '!', '&', '$', '<', '>', '|'].forEach(char => {
it(`rejects branch names containing shell metacharacter: ${char}`, () => {
config.branch = `branch${char}name`;

Expand All @@ -339,20 +340,6 @@ describe('index', function () {
});
});

['branch..name', 'branch@{name', 'branch/.name', 'branch/', '.branch', 'branch.lock'].forEach(branchName => {
it(`rejects branch names with forbidden git ref sequence: ${branchName}`, () => {
config.branch = branchName;

return scm.getCheckoutCommand(config).then(
() => assert.fail('expected getCheckoutCommand to reject'),
err => {
assert.match(err.message, /Invalid branch name/);
assert.equal(err.statusCode, 400);
}
);
});
});

it('accepts branch names with conservatively-safe special characters', () => {
config.branch = 'feature/some_module.v1+rc1-final@host';

Expand Down Expand Up @@ -468,7 +455,7 @@ describe('index', function () {
);
});

[`'`, '"', '`', ';', '!', '#', '&', '$', '<', '>', '|'].forEach(char => {
[`'`, '"', '`', ';', '!', '&', '$', '<', '>', '|'].forEach(char => {
it(`rejects PR branch names containing shell metacharacter: ${char}`, () => {
config.prRef = 'pull/3/merge';
config.prBranchName = `branch${char}name`;
Expand Down