Skip to content

Commit 90f4963

Browse files
fix(ci): prevent command injection vulnerability in PR title validation [PSIRT-0974799192]
Replaces bash string interpolation with github-script action to safely validate PR titles and prevent command injection attacks. Also makes scope optional and adds security hardening (Unicode normalization, control character detection, length limits).
1 parent bdc477d commit 90f4963

1 file changed

Lines changed: 32 additions & 14 deletions

File tree

.github/workflows/pull-request.yml

Lines changed: 32 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,20 +45,38 @@ jobs:
4545
if: contains(toJson(github.event.pull_request.labels), 'validated')
4646

4747
steps:
48-
- name: Validate PR title
49-
run: |
50-
TITLE="${{ github.event.pull_request.title }}"
51-
echo "PR Title is: '$TITLE'"
52-
53-
regex="^(fix|feat|chore|docs)\([^()]+\): .+"
54-
55-
if [[ $TITLE =~ $regex ]]; then
56-
echo "PR title is valid."
57-
else
58-
echo "Error: PR title does NOT follow the required convention."
59-
echo "Expected format: fix|feat|chore(some-text): description..."
60-
exit 1
61-
fi
48+
- name: Validate PR title safely
49+
uses: actions/github-script@v7
50+
with:
51+
script: |
52+
const title = (context.payload.pull_request?.title ?? "").normalize("NFC");
53+
54+
// 1) Required
55+
if (!title.length) {
56+
core.setFailed("PR title is empty.");
57+
return;
58+
}
59+
60+
// 2) Length guard
61+
if (title.length > 200) {
62+
core.setFailed("PR title is too long (max 200 chars).");
63+
return;
64+
}
65+
66+
// 3) Hardened control-char guard (all Unicode control/invisible chars)
67+
if (/[\p{C}]/u.test(title)) {
68+
core.setFailed("PR title contains disallowed control/invisible characters.");
69+
return;
70+
}
71+
72+
// 4) Enforce convention (scope is optional)
73+
const regex = /^(fix|feat|chore|docs)(\([^()\s][^()]{0,78}[^()\s]?\))?: [^\s].*$/u;
74+
if (!regex.test(title)) {
75+
core.setFailed("PR title must follow: fix|feat|chore|docs(scope): description OR fix|feat|chore|docs: description");
76+
return;
77+
}
78+
79+
core.info(`PR title is valid: "${title}"`);
6280
6381
install:
6482
runs-on: ubuntu-latest

0 commit comments

Comments
 (0)