-
-
Notifications
You must be signed in to change notification settings - Fork 841
103 lines (90 loc) · 3.5 KB
/
Copy pathbase_ref_check.yml
File metadata and controls
103 lines (90 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
name: Fork PR target policy
on:
pull_request_target:
types: [opened, reopened, synchronize, edited, ready_for_review]
permissions: {}
jobs:
validate:
name: Fork PR target check
if: ${{ github.event.pull_request.head.repo.fork == true }}
runs-on: ubuntu-latest
permissions:
pull-requests: read
steps:
- name: Enforce target policy
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3
with:
script: |
const base = context.payload.pull_request.base.ref;
const pull_number = context.payload.pull_request.number;
const allowedPatterns = [
/^README\.md$/,
/^CONTRIBUTING\.md$/,
/^SECURITY\.md$/,
/^LICENSE$/,
/^THIRD_PARTY_LICENSES$/,
/^\.gitignore$/,
/^crowdin\.yml$/,
/^\.github\/.+$/
];
const isAllowedPath = (path) =>
allowedPatterns.some((pattern) => pattern.test(path));
const toSafeLiteral = (value) =>
JSON.stringify(String(value))
.replace(/[\u200E\u200F\u202A-\u202E\u2066-\u2069]/g, (c) =>
`\\u${c.codePointAt(0).toString(16).padStart(4, '0')}`
)
.replace(/</g, '\\u003c')
.replace(/>/g, '\\u003e')
.replace(/&/g, '\\u0026')
.replace(/@/g, '\\u0040')
.replace(/#/g, '\\u0023')
.replace(/`/g, '\\u0060');
const describeFile = (file) => {
if (file.previous_filename) {
return `- ${toSafeLiteral(file.previous_filename)} -> ${toSafeLiteral(file.filename)}`;
}
return `- ${toSafeLiteral(file.filename)}`;
};
if (base === 'dev') {
core.info('PR targets dev: allowed.');
return;
}
if (base !== 'main') {
core.setFailed(
`PR targets ${toSafeLiteral(base)}: not allowed. Only "dev" and "main" are permitted.`
);
return;
}
const files = await github.paginate(github.rest.pulls.listFiles, {
owner: context.repo.owner,
repo: context.repo.repo,
pull_number,
per_page: 100
});
// Treat renames conservatively: both old and new paths must be allowed.
const disallowed = files.filter((file) => {
const pathsToCheck = [file.filename, file.previous_filename].filter(Boolean);
return pathsToCheck.some((path) => !isAllowedPath(path));
});
if (disallowed.length > 0) {
core.setFailed([
"PR targets 'main' but includes changes outside the allowed paths.",
"Only allowlisted repository metadata changes may target 'main'.",
'',
'Allowed paths:',
'- "README.md"',
'- "CONTRIBUTING.md"',
'- "SECURITY.md"',
'- "LICENSE"',
'- "THIRD_PARTY_LICENSES"',
'- ".gitignore"',
'- "crowdin.yml"',
'- ".github/**"',
'',
'Disallowed changed paths:',
...disallowed.map(describeFile)
].join('\n'));
return;
}
core.info("PR targets main and only changes allowed paths: allowed.");