diff --git a/.github/workflows/update_actions.yml b/.github/workflows/update_actions.yml
deleted file mode 100644
index 54baa5d0..00000000
--- a/.github/workflows/update_actions.yml
+++ /dev/null
@@ -1,91 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-name: Handle Dependabot update
-on:
- workflow_dispatch:
- push:
- branches:
- - main
- paths:
- - ".github/actions/for-dependabot-triggered-reviews/action.yml"
- pull_request:
- paths:
- - ".github/workflows/update_actions.yml"
- - ".github/actions/for-dependabot-triggered-reviews/action.yml"
- - gateway/*
-
-permissions:
- contents: read
-
-# We want workflows on main to run in order to avoid losing data through race conditions
-concurrency: "${{ github.ref }}-${{ github.workflow }}"
-
-jobs:
- update_actions:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
-
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: true
- token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
-
- - name: Print token details
- if: ${{ github.event_name != 'pull_request' }}
- env:
- GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env]
- run: |
- echo "::group::Token details"
- echo "Token user and permissions:"
- gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"'
- echo ""
- echo "Token expiration:"
- gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)"
- echo ""
- echo "Token scopes:"
- curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)"
- echo "::endgroup::"
-
- - run: pipx install uv
-
- - name: Update actions.yml
- run: |
- uv run python << 'PYEOF'
- import sys
- sys.path.append("./gateway/")
-
- import gateway as g
- g.update_actions(".github/actions/for-dependabot-triggered-reviews/action.yml", "actions.yml")
- g.update_patterns("approved_patterns.yml", "actions.yml")
- PYEOF
-
- - name: Commit and push changes
- if: ${{ github.event_name != 'pull_request' }}
- env:
- GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
- run: |
- AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit")
- AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com")
- git config --local user.name "${AUTHOR_NAME}"
- git config --local user.email "${AUTHOR_EMAIL}"
- git add -f actions.yml approved_patterns.yml
- git commit -m "Update actions.yml and approved_patterns.yml based on .github/actions/for-dependabot-triggered-reviews/action.yml" -m "Generated by .github/workflows/update_actions.yml" || echo "No changes"
- git push origin
diff --git a/.github/workflows/update_allowlist.yml b/.github/workflows/update_allowlist.yml
new file mode 100644
index 00000000..6d590fc2
--- /dev/null
+++ b/.github/workflows/update_allowlist.yml
@@ -0,0 +1,167 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+# Replaces the former pair `update_actions.yml` (composite -> actions.yml)
+# and `update_composite_action.yml` (actions.yml -> composite). Those ran
+# in separate concurrency groups and could race when both files changed in
+# overlapping pushes, silently overwriting one direction's edit. This
+# single workflow always runs both directions in order, so neither edit
+# is lost regardless of which file triggered the run. See #866.
+name: Update Allowlist
+on:
+ workflow_dispatch:
+ push:
+ branches:
+ - main
+ paths:
+ - "actions.yml"
+ - ".github/actions/for-dependabot-triggered-reviews/action.yml"
+ pull_request:
+ paths:
+ - ".github/workflows/update_allowlist.yml"
+ - ".github/actions/for-dependabot-triggered-reviews/action.yml"
+ - "actions.yml"
+ - "gateway/*"
+
+permissions:
+ contents: read
+
+# Single group across both inputs so no two updates touch the allowlist
+# files in parallel. Don't cancel — every queued run must finish so we
+# don't drop a dependabot bump or a manual actions.yml edit.
+concurrency:
+ group: "${{ github.ref }}-update-allowlist"
+ cancel-in-progress: false
+
+jobs:
+ update:
+ runs-on: ubuntu-latest
+ permissions:
+ contents: write
+ steps:
+
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ persist-credentials: true
+ token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
+ # On push/dispatch, check out the current tip of main rather
+ # than the trigger SHA. A queued run that started on an older
+ # SHA would otherwise regenerate from stale inputs and either
+ # undo the prior run's commit or fail to push.
+ ref: ${{ (github.event_name == 'push' || github.event_name == 'workflow_dispatch') && 'main' || '' }}
+
+ - name: Print token details
+ if: ${{ github.event_name != 'pull_request' }}
+ env:
+ GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env]
+ run: |
+ echo "::group::Token details"
+ echo "Token user and permissions:"
+ gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"'
+ echo ""
+ echo "Token expiration:"
+ gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)"
+ echo ""
+ echo "Token scopes:"
+ curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)"
+ echo "::endgroup::"
+
+ - run: pipx install uv
+
+ - name: Sync composite action, actions.yml, and approved_patterns.yml
+ run: |
+ uv run python << 'PYEOF'
+ import sys
+ sys.path.append("./gateway/")
+
+ import gateway as g
+
+ composite = ".github/actions/for-dependabot-triggered-reviews/action.yml"
+ actions = "actions.yml"
+ patterns = "approved_patterns.yml"
+
+ # Always run both directions. The previous two-workflow split
+ # raced on overlapping pushes; running both here means the
+ # outcome is the same regardless of which file triggered us.
+ #
+ # 1. Pull any new refs from the composite (e.g. dependabot
+ # bumps) into actions.yml. Additive: existing entries stay
+ # and get their expiry refreshed.
+ g.update_actions(composite, actions)
+
+ # 2. Regenerate the composite from the (now-merged)
+ # actions.yml so a manual actions.yml edit is reflected.
+ g.update_workflow(composite, actions)
+
+ # 3. Regenerate the approved patterns from actions.yml.
+ g.update_patterns(patterns, actions)
+ PYEOF
+
+ - name: Commit and push changes
+ if: ${{ github.event_name != 'pull_request' }}
+ env:
+ GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
+ run: |
+ AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit")
+ AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com")
+ git config --local user.name "${AUTHOR_NAME}"
+ git config --local user.email "${AUTHOR_EMAIL}"
+
+ composite=".github/actions/for-dependabot-triggered-reviews/action.yml"
+ if git diff --quiet -- actions.yml approved_patterns.yml "${composite}"; then
+ echo "No changes"
+ exit 0
+ fi
+
+ git add -f actions.yml approved_patterns.yml "${composite}"
+ git commit \
+ -m "Sync actions.yml, composite action, and approved_patterns.yml" \
+ -m "Generated by .github/workflows/update_allowlist.yml"
+
+ # If a concurrent push (e.g. remove_expired.yml) advanced main
+ # while we were computing, rebase and retry. The sync script is
+ # idempotent so re-running on the rebased tree is safe.
+ for attempt in 1 2 3; do
+ if git push origin HEAD:main; then
+ exit 0
+ fi
+ echo "Push rejected on attempt ${attempt}; rebasing and re-running sync"
+ git fetch origin main
+ git reset --hard origin/main
+ uv run python << 'PYEOF'
+ import sys
+ sys.path.append("./gateway/")
+ import gateway as g
+ composite = ".github/actions/for-dependabot-triggered-reviews/action.yml"
+ actions = "actions.yml"
+ patterns = "approved_patterns.yml"
+ g.update_actions(composite, actions)
+ g.update_workflow(composite, actions)
+ g.update_patterns(patterns, actions)
+ PYEOF
+ if git diff --quiet -- actions.yml approved_patterns.yml "${composite}"; then
+ echo "Already in sync after rebase; nothing to push"
+ exit 0
+ fi
+ git add -f actions.yml approved_patterns.yml "${composite}"
+ git commit \
+ -m "Sync actions.yml, composite action, and approved_patterns.yml" \
+ -m "Generated by .github/workflows/update_allowlist.yml"
+ done
+ echo "Failed to push after 3 attempts"
+ exit 1
diff --git a/.github/workflows/update_composite_action.yml b/.github/workflows/update_composite_action.yml
deleted file mode 100644
index 2b3d58d2..00000000
--- a/.github/workflows/update_composite_action.yml
+++ /dev/null
@@ -1,90 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-name: Update Approved Patterns and Composite Action
-on:
- workflow_dispatch:
- push:
- branches:
- - main
- paths:
- - "actions.yml"
- pull_request:
- paths:
- - ".github/workflows/update_composite_action.yml"
- - "actions.yml"
- - gateway/*
-
-permissions: {}
-
-# We want workflows on main to run in order to avoid losing data through race conditions
-concurrency: "${{ github.ref }}-${{ github.workflow }}"
-
-jobs:
- update:
- name: Update Workflow
- runs-on: ubuntu-latest
- steps:
-
- - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- with:
- persist-credentials: true
- # We have to use a PAT to commit the workflow file
- token: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
-
- - name: Print token details
- if: ${{ github.event_name != 'pull_request' }}
- env:
- GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN }} # zizmor: ignore[secrets-outside-env]
- run: |
- echo "::group::Token details"
- echo "Token user and permissions:"
- gh api /user --jq '"Login: \(.login)\nName: \(.name)\nEmail: \(.email)"'
- echo ""
- echo "Token expiration:"
- gh api /installation/token --jq '.expires_at' 2>/dev/null || echo "Token expiration not available (likely a PAT, not an installation token)"
- echo ""
- echo "Token scopes:"
- curl -sS -H "Authorization: token ${GH_TOKEN}" -I https://api.github.com/ 2>/dev/null | grep -i 'x-oauth-scopes' || echo "No OAuth scopes header (fine-grained or app token)"
- echo "::endgroup::"
-
- - run: pipx install uv
-
- - name: Update Workflow
- run: |
- uv run python << 'PYEOF'
- import sys
- sys.path.append("./gateway/")
-
- import gateway as g
- g.update_workflow(".github/actions/for-dependabot-triggered-reviews/action.yml", "actions.yml")
- g.update_patterns("approved_patterns.yml", "actions.yml")
- PYEOF
-
- - name: Commit and push changes
- if: ${{ github.event_name != 'pull_request' }}
- env:
- GH_TOKEN: ${{ secrets.ALLOWLIST_WORKFLOW_TOKEN || github.token }} # zizmor: ignore[secrets-outside-env]
- run: |
- AUTHOR_NAME=$(gh api /user --jq '.login' 2>/dev/null || echo "asfgit")
- AUTHOR_EMAIL=$(gh api /user --jq '.email // "\(.login)@users.noreply.github.com"' 2>/dev/null || echo "asfgit@users.noreply.github.com")
- git config --local user.name "${AUTHOR_NAME}"
- git config --local user.email "${AUTHOR_EMAIL}"
- git add -f .github/actions/for-dependabot-triggered-reviews/action.yml approved_patterns.yml
- git commit -m "Update approved_patterns.yml and .github/actions/for-dependabot-triggered-reviews/action.yml based on actions.yml" -m "Generated by .github/workflows/update_composite_action.yml" || echo "No changes"
- git push origin
diff --git a/README.md b/README.md
index 27e74991..b702db81 100644
--- a/README.md
+++ b/README.md
@@ -121,11 +121,11 @@ graph LR
dependabot-->composite
dependabot-.verified by.-verify["verify_dependabot_action.yml
(rebuild & diff)"]
- composite=="update_actions.yml
(on merge)"==>actions
+ composite=="update_allowlist.yml
(on merge)"==>actions
cron=="remove_expired.yml"==>actions
- actions=="update_composite_action.yml"==>composite
- actions=="update_composite_action.yml"==>approved
+ actions=="update_allowlist.yml"==>composite
+ actions=="update_allowlist.yml"==>approved
guard["check_approved_limit.yml
(fails at 800 / 1000)"]-.monitors.-approved
@@ -149,11 +149,11 @@ Solid arrows (`==>`) are workflow regeneration edges — the "source → generat
```mermaid
graph TD;
manual["manual PR"]--new entry-->actions.yml
- actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"]
- actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"]
+ actions.yml--"update_allowlist.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"]
+ actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"]
```
-A human-authored PR edits `actions.yml` directly. Once it merges to `main`, the **`update_composite_action.yml`** workflow regenerates both `.github/actions/for-dependabot-triggered-reviews/action.yml` and `approved_patterns.yml` from the new entries, so contributors never have to hand-edit the generated files.
+A human-authored PR edits `actions.yml` directly. Once it merges to `main`, the **`update_allowlist.yml`** workflow regenerates both `.github/actions/for-dependabot-triggered-reviews/action.yml` and `approved_patterns.yml` from the new entries, so contributors never have to hand-edit the generated files.
To request addition of an action to the allow list:
@@ -185,14 +185,14 @@ The infrastructure team will review your request and either approve, request cha
graph TD;
dependabot--"PR updates"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"]
dependabot-.verified by.-verify["verify_dependabot_action.yml"]
- composite--"update_actions.yml (on merge)"-->actions.yml
- actions.yml--"update_actions.yml"-->approved["approved_patterns.yml"]
+ composite--"update_allowlist.yml (on merge)"-->actions.yml
+ actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"]
```
In most cases, new versions are automatically added through Dependabot:
- Dependabot opens PRs against `.github/actions/for-dependabot-triggered-reviews/action.yml` to update actions to the newest releases
- **`verify_dependabot_action.yml`** runs on each such PR, rebuilds the action's compiled JavaScript in Docker, and diffs it against the published version (see [Automated Verification in CI](#automated-verification-in-ci))
-- Once a reviewer merges the PR, **`update_actions.yml`** reflects the new commit SHAs back into `actions.yml` and regenerates `approved_patterns.yml`
+- Once a reviewer merges the PR, **`update_allowlist.yml`** reflects the new commit SHAs back into `actions.yml` and regenerates `approved_patterns.yml`
- The previously approved version is marked with an `expires_at` date 3 months out, giving projects a grace period to update their workflows; see [Automatic Expiration of Old Versions](#automatic-expiration-of-old-versions) for how the cleanup runs
Projects are encouraged to help review updates to actions they use. Please have a look at the diff and mention in your approval what you have checked and why you think the action is safe.
@@ -356,15 +356,15 @@ If you add older version of the action and want to set an expiration date for it
```mermaid
graph TD;
entry["actions.yml entry
with expires_at"]--"remove_expired.yml (daily, 02:04 UTC)"-->actions.yml
- actions.yml--"update_composite_action.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"]
- actions.yml--"update_composite_action.yml"-->approved["approved_patterns.yml"]
+ actions.yml--"update_allowlist.yml"-->composite[".github/actions/for-dependabot-triggered-reviews/action.yml"]
+ actions.yml--"update_allowlist.yml"-->approved["approved_patterns.yml"]
```
Routine cleanup of superseded versions is automated:
- Any entry in `actions.yml` with an `expires_at: YYYY-MM-DD` field is a candidate for removal.
- Dependabot-driven updates (see [Updating Version of Already Approved Action](#updating-version-of-already-approved-action)) set `expires_at` to **3 months out** on the previously approved version. For manually added older versions, set `expires_at` explicitly (see [Manual Addition of Specific Versions](#manual-addition-of-specific-versions)).
-- The **`remove_expired.yml`** workflow runs daily at **02:04 UTC**. Every entry whose `expires_at` date has passed is deleted from `actions.yml`; the workflow then commits the change and lets `update_composite_action.yml` regenerate `approved_patterns.yml` and the dependabot composite.
+- The **`remove_expired.yml`** workflow runs daily at **02:04 UTC**. Every entry whose `expires_at` date has passed is deleted from `actions.yml`; the workflow then commits the change and lets `update_allowlist.yml` regenerate `approved_patterns.yml` and the dependabot composite.
- Entries without `expires_at` (for example, `keep: true` wildcards and the current approved version) are never auto-removed — removal of those requires a manual PR.
No human action is required for the routine case: projects get a 3-month grace window after a version bump, and the old entry disappears on its own afterwards.
diff --git a/utils/check_cache_settings/audit-actions-cache.py b/utils/check_cache_settings/audit-actions-cache.py
index 588ac3cc..98c0df64 100644
--- a/utils/check_cache_settings/audit-actions-cache.py
+++ b/utils/check_cache_settings/audit-actions-cache.py
@@ -1,4 +1,23 @@
#!/usr/bin/env python3
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
"""
audit-actions-cache.py
diff --git a/utils/check_cache_settings/test_audit_actions_cache.py b/utils/check_cache_settings/test_audit_actions_cache.py
index 895fe251..8fefa526 100644
--- a/utils/check_cache_settings/test_audit_actions_cache.py
+++ b/utils/check_cache_settings/test_audit_actions_cache.py
@@ -1,3 +1,22 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements. See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership. The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License. You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations
+# under the License.
+#
+
"""
test_audit_actions_cache.py