Skip to content

Commit cd264a3

Browse files
authored
[SECURITY] Harden GitHub Workflows & Actions (#12)
* Pin actions and harden CI workflows Pin GitHub Actions to specific SHAs and tighten workflow security/behavior. Key changes: - Pin multiple actions to commit SHAs (checkout, create-github-app-token, github-script, setup-python, setup-uv, semantic PR linter, sticky comment, etc.) to ensure immutable behavior. - Improve secret/input handling by exporting inputs into env vars (INPUTS_*) and using those in scripts so values can be masked safely (e.g., privateKey masking). - Add persist-credentials: false for cross-repo checkout and use env-driven Black version install to avoid leaking inputs. - Narrow job permissions and add explicit permissions where needed (pull-requests, issues, statuses, actions, security-events) with explanatory comments. - Add concurrency groups to workflows to cancel in-progress runs for the same PR/ref and set an environment for the check-linked-issue job. These changes improve reproducibility and security of the CI workflows and actions. Signed-off-by: John McCall <john@overturemaps.org> * use local refs Signed-off-by: John McCall <john@overturemaps.org> * Create dependabot.yml Signed-off-by: John McCall <john@overturemaps.org> --------- Signed-off-by: John McCall <john@overturemaps.org>
1 parent faa3fa4 commit cd264a3

8 files changed

Lines changed: 98 additions & 35 deletions

File tree

.github/actions/check-linked-issue/action.yml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,22 @@ runs:
3636
shell: bash
3737
run: |
3838
echo "::group::Masking private key"
39-
echo "::add-mask::${{ inputs.privateKey }}"
39+
echo "::add-mask::${INPUTS_PRIVATEKEY}"
4040
echo "::endgroup::"
41+
env:
42+
INPUTS_PRIVATEKEY: ${{ inputs.privateKey }}
4143

4244
- name: Generate GitHub App token
4345
id: app-token
4446
if: ${{ inputs.privateKey != '' }}
45-
uses: actions/create-github-app-token@v1
47+
uses: actions/create-github-app-token@d72941d797fd3113feb6b93fd0dec494b13a2547 # v1.12.0
4648
with:
4749
app-id: ${{ inputs.appId }}
4850
private-key: ${{ inputs.privateKey }}
4951
owner: ${{ github.repository_owner }}
5052

5153
- name: Check for linked issue via GraphQL
52-
uses: actions/github-script@v7
54+
uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7.1.0
5355
with:
5456
github-token: ${{ steps.app-token.outputs.token || github.token }}
5557
script: |

.github/actions/generate-schema-docs/action.yml

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,12 @@ runs:
3535
- name: Check out schema repo
3636
id: get-sha
3737
if: inputs.skip-checkout == 'false'
38-
uses: actions/checkout@v6
38+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
3939
with:
4040
repository: OvertureMaps/schema
4141
ref: ${{ inputs.schema-ref }}
4242
path: ${{ inputs.schema-path }}
43+
persist-credentials: false
4344

4445
- name: Resolve schema SHA (skip-checkout true)
4546
id: get-sha-fallback
@@ -51,12 +52,12 @@ runs:
5152
echo "ref=$(git symbolic-ref --short HEAD 2>/dev/null || git rev-parse HEAD)" >> $GITHUB_OUTPUT
5253
5354
- name: Set up Python
54-
uses: actions/setup-python@v6
55+
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
5556
with:
5657
python-version-file: ${{ inputs.schema-path }}/.python-version
5758

5859
- name: Install uv
59-
uses: astral-sh/setup-uv@v7
60+
uses: astral-sh/setup-uv@37802adc94f370d6bfd71619e3f0bf239e1f3b78 # v7.6.0
6061

6162
- name: Install schema packages
6263
shell: bash
@@ -66,4 +67,6 @@ runs:
6667
- name: Generate markdown docs
6768
shell: bash
6869
working-directory: ${{ inputs.schema-path }}
69-
run: uv run overture-codegen generate --format markdown --output-dir "${{ inputs.output-dir }}"
70+
run: uv run overture-codegen generate --format markdown --output-dir "${INPUTS_OUTPUT_DIR}"
71+
env:
72+
INPUTS_OUTPUT_DIR: ${{ inputs.output-dir }}

.github/actions/s5cmd/action.yml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ runs:
6464
- name: Validate inputs
6565
shell: bash
6666
run: |
67-
CMD="${{ inputs.command }}"
68-
SRC="${{ inputs.source }}"
69-
DST="${{ inputs.destination }}"
70-
BATCH="${{ inputs.batch-file }}"
67+
CMD="${INPUTS_COMMAND}"
68+
SRC="${INPUTS_SOURCE}"
69+
DST="${INPUTS_DESTINATION}"
70+
BATCH="${INPUTS_BATCH_FILE}"
7171
7272
case "$CMD" in
7373
cp|mv|rm|ls|sync|run) ;;
@@ -89,15 +89,20 @@ runs:
8989
echo "::error::'batch-file' is only valid with the 'run' command. Did you mean to use 'source'?"
9090
exit 1
9191
fi
92+
env:
93+
INPUTS_COMMAND: ${{ inputs.command }}
94+
INPUTS_SOURCE: ${{ inputs.source }}
95+
INPUTS_DESTINATION: ${{ inputs.destination }}
96+
INPUTS_BATCH_FILE: ${{ inputs.batch-file }}
9297

9398
- name: s5cmd ${{ inputs.command }}
9499
shell: bash
95100
run: |
96-
CMD="${{ inputs.command }}"
97-
SRC="${{ inputs.source }}"
98-
DST="${{ inputs.destination }}"
99-
BATCH="${{ inputs.batch-file }}"
100-
FLAGS="${{ inputs.flags }}"
101+
CMD="${INPUTS_COMMAND}"
102+
SRC="${INPUTS_SOURCE}"
103+
DST="${INPUTS_DESTINATION}"
104+
BATCH="${INPUTS_BATCH_FILE}"
105+
FLAGS="${INPUTS_FLAGS}"
101106
102107
# Build argument array: global flags first, then subcommand, then positional args
103108
ARGS=()
@@ -119,3 +124,9 @@ runs:
119124
120125
echo "Running: s5cmd ${ARGS[*]}"
121126
s5cmd "${ARGS[@]}"
127+
env:
128+
INPUTS_COMMAND: ${{ inputs.command }}
129+
INPUTS_SOURCE: ${{ inputs.source }}
130+
INPUTS_DESTINATION: ${{ inputs.destination }}
131+
INPUTS_BATCH_FILE: ${{ inputs.batch-file }}
132+
INPUTS_FLAGS: ${{ inputs.flags }}

.github/dependabot.yml

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
version: 2
3+
updates:
4+
5+
# Maintain GitHub Actions dependencies
6+
- package-ecosystem: "github-actions"
7+
directory: "/"
8+
schedule:
9+
interval: "weekly"
10+
open-pull-requests-limit: 5
11+
labels:
12+
- "bot"
13+
commit-message:
14+
prefix: "[CHORE](gha)"
15+
include: "scope"
16+
cooldown:
17+
default-days: 7

.github/workflows/lint-python.yml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,16 +35,23 @@ on:
3535

3636
permissions:
3737
contents: read
38-
pull-requests: write
3938

4039
jobs:
4140
black:
41+
name: Black Formatter
4242
runs-on: ubuntu-slim
43+
permissions:
44+
contents: read
45+
pull-requests: write # to post Black formatting annotations on PRs
4346
steps:
4447
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
48+
with:
49+
persist-credentials: false
4550

4651
- name: Install Black
47-
run: pip install black=="${{ inputs.black_version || '25.12.0' }}"
52+
run: pip install "black==${BLACK_VERSION}"
53+
env:
54+
BLACK_VERSION: ${{ inputs.black_version || '25.12.0' }}
4855

4956
- uses: reviewdog/action-black@644053a260402bc4278a865906107bd8aef7fae8 # v3.22.4
5057
with:

.github/workflows/omf_pr_checks.yml

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,29 +10,40 @@
1010
name: OMF PR Checks
1111

1212
on:
13-
pull_request_target:
13+
pull_request_target: # zizmor: ignore[dangerous-triggers]
1414
types: [opened, reopened, edited, synchronize]
1515

1616
# pull_request_target runs with write permissions so this workflow works for fork PRs.
1717
# SECURITY: This workflow must never check out or execute code from the PR branch.
1818
# If you need to add a step that does so, use the workflow_run pattern instead.
1919
permissions:
20-
contents: none
21-
issues: write
22-
pull-requests: write
23-
statuses: write
20+
contents: none # no repository content access needed
21+
22+
concurrency:
23+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
24+
cancel-in-progress: true
2425

2526
jobs:
2627

2728
validate-pr-title:
2829
name: title
29-
uses: OvertureMaps/workflows/.github/workflows/validate-pr-title.yml@main
30+
permissions:
31+
contents: none # no repository content access needed
32+
pull-requests: write # to post PR title validation comments
33+
issues: write # to post PR-related issue comments
34+
statuses: write # to update commit status checks
35+
uses: ./.github/workflows/validate-pr-title.yml
3036

3137
check-linked-issue:
3238
name: issue
3339
runs-on: ubuntu-latest
40+
environment: check-linked-issue-app
41+
permissions:
42+
contents: none # no repository content access needed
43+
pull-requests: read # to query PR metadata and closing issue references
44+
statuses: write # to update commit status checks
3445
steps:
3546
- name: Check for linked issue
36-
uses: OvertureMaps/workflows/.github/actions/check-linked-issue@main
47+
uses: ./.github/actions/check-linked-issue
3748
with:
3849
privateKey: ${{ secrets.CHECK_LINKED_ISSUE_APP_PEM }}

.github/workflows/omf_sec_checks.yml

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,14 +19,20 @@ on:
1919

2020
permissions:
2121
contents: read
22-
actions: read
23-
security-events: write
22+
23+
concurrency:
24+
group: ${{ github.workflow }}-${{ github.ref }}
25+
cancel-in-progress: true
2426

2527
jobs:
2628

2729
zizmor:
2830
name: zizmor
2931
runs-on: ubuntu-latest
32+
permissions:
33+
contents: read
34+
actions: read # to read workflow files for security auditing
35+
security-events: write # to upload SARIF results to GitHub Advanced Security
3036
steps:
3137
- name: Checkout
3238
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2

.github/workflows/validate-pr-title.yml

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ name: Validate PR Title
2323

2424
on:
2525
workflow_call:
26-
pull_request_target:
26+
pull_request_target: # zizmor: ignore[dangerous-triggers]
2727
types:
2828
- opened
2929
- reopened
@@ -34,17 +34,23 @@ on:
3434
# SECURITY: This workflow must never check out or execute code from the PR branch.
3535
# If you need to add a step that does so, use the workflow_run pattern instead.
3636
permissions:
37-
contents: none
38-
pull-requests: write
39-
issues: write
40-
statuses: write
37+
contents: none # no repository content access needed
38+
39+
concurrency:
40+
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.run_id }}
41+
cancel-in-progress: true
4142

4243
jobs:
4344
validate:
4445
name: Validate PR Title
4546
runs-on: ubuntu-latest
47+
permissions:
48+
contents: none # no repository content access needed
49+
pull-requests: write # to post PR title validation comments
50+
issues: write # to post PR-related issue comments via sticky comment action
51+
statuses: write # to update commit status checks
4652
steps:
47-
- uses: amannn/action-semantic-pull-request@v6
53+
- uses: amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 # v6.1.1
4854
id: lint_pr_title
4955
env:
5056
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -79,7 +85,7 @@ jobs:
7985
8086
- name: Post validation failure comment
8187
if: ${{ failure() && steps.lint_pr_title.outputs.error_message }}
82-
uses: marocchino/sticky-pull-request-comment@v2
88+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
8389
with:
8490
header: pr-title-validation-error
8591
message: |
@@ -97,7 +103,7 @@ jobs:
97103
98104
- name: Delete comment on success
99105
if: ${{ success() }}
100-
uses: marocchino/sticky-pull-request-comment@v2
106+
uses: marocchino/sticky-pull-request-comment@773744901bac0e8cbb5a0dc842800d45e9b2b405 # v2.9.4
101107
with:
102108
header: pr-title-validation-error
103109
delete: true

0 commit comments

Comments
 (0)