chore(renovate): P4a — durable lock-regen (fleet pip-compile manager + auto-lock backstop) - #2406
Conversation
📝 WalkthroughWalkthroughThe PR introduces Renovate's ChangesRenovate pip-compile Lock Regeneration + Workflow Backstop
Estimated code review effort🎯 2 (Simple) | ⏱️ ~12 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
Workflow source neededPR #2406 needs either a linked GitHub issue or one valid non-issue Workflow Source before PR metadata automation can manage it safely. Please do one of:
Once a valid source is present, this warning will not be reposted. |
Automated Status SummaryHead SHA: a83d900
Coverage Overview
Coverage Trend
Top Coverage Hotspots (lowest coverage)
Low Coverage Files (<50.0%)
Updated automatically; will refresh on subsequent CI/Docker completions. Keepalive checklistScopeNo scope information available Tasks
Acceptance criteria
|
dce23aa to
f717942
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: dce23aaa45
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| - 'tools/requirements-llm.txt' | ||
| branches: | ||
| - 'dependabot/**' | ||
| - 'renovate/**' |
There was a problem hiding this comment.
Filter Renovate PRs with head_ref instead
For pull_request, the branches filter matches the PR target/base branch, not the source branch (GitHub docs describe this as PRs that target matching branches). Normal Renovate PRs come from renovate/... into main, so this new pattern prevents the workflow from being queued before the job-level actor check can run, and the advertised Renovate backstop never runs when Renovate fails to regenerate the lock. Use a base branch filter such as main (or remove it) and gate source branches with github.head_ref in the job if.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/maint-dependabot-auto-lock.yml:
- Around line 67-75: The `args` variable extracted from the `requirements.lock`
file header via sed is directly expanded unquoted in the `uv pip compile
${args}` command invocation, creating a shell injection vulnerability. To fix
this, validate that the extracted `args` contains only safe, whitelisted tokens
(such as package names, version operators like ==, >=, etc., and no shell
metacharacters), and then use proper quoting or bash array semantics when
passing the arguments to the uv command to prevent unintended command execution.
Consider splitting the args into an array and using the safe argument expansion
pattern to eliminate the injection vector.
- Line 35: The `if` condition uses `github.actor` which is exploitable through
workflow reruns and does not securely verify the actual pull request author.
Replace the `github.actor` checks in the condition with
`github.event.pull_request.user.login` to gate the workflow on the actual PR
author identity instead, following GitHub Actions security best practices for
pull request events.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: e0adf792-d9b0-4a21-a928-d22da150bf69
⛔ Files ignored due to path filters (2)
requirements.lockis excluded by!**/*.lock,!**/*.lockuv.lockis excluded by!**/*.lock,!**/*.lock
📒 Files selected for processing (3)
.github/workflows/maint-dependabot-auto-lock.ymlrenovate-presets/fleet.jsonrequirements.txt
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
stranske/Template(auto-detected)
| name: Regenerate requirements.lock | ||
| runs-on: ubuntu-latest | ||
| if: github.actor == 'dependabot[bot]' | ||
| if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# First, find and inspect the workflow file
find . -name "maint-dependabot-auto-lock.yml" -type f | head -5Repository: stranske/Workflows
Length of output: 113
🏁 Script executed:
#!/bin/bash
# Read the entire workflow file with line numbers
cat -n .github/workflows/maint-dependabot-auto-lock.ymlRepository: stranske/Workflows
Length of output: 4483
🌐 Web query:
GitHub Actions github.actor context spoofable rerun security
💡 Result:
The github.actor context in GitHub Actions is considered spoofable and insecure for gatekeeping privileged operations [1][2][3]. This is because it refers to the user who triggered the specific event that initiated the current workflow run [4], not necessarily the original creator of a pull request or the author of the code being executed [5][3]. Attackers can exploit this behavior, particularly in workflows using events like pull_request_target, by performing actions—such as commenting on a pull request or triggering a re-run—that cause the workflow to re-trigger [4]. If an attacker controls an event that triggers a re-run, they can manipulate the workflow into executing with a github.actor value that appears to be a trusted account (e.g., 'dependabot[bot]') [4][6]. Relying on this value to grant permissions or execute code can lead to a Confused Deputy attack, where a privileged process is tricked into performing malicious actions [5]. To securely validate actors, you should avoid relying on github.actor for security-critical decisions [2][3]. Instead, use immutable, event-specific contexts that represent the true initiator or author of the event [1][7][6]: 1. For pull request events, use github.event.pull_request.user.login to verify the original PR author [1][7]. 2. For comment-based triggers, use github.event.comment.user.login [1]. 3. For general security-critical validation, consider comparing against the actor's unique numeric ID (github.actor_id or event-specific IDs) rather than their username, which can be impersonated or registered by attackers [1][8]. 4. Always implement additional defense-in-depth measures for workflows that handle secrets or privileged operations, such as explicitly checking that the pull request does not originate from a fork (github.event.pull_request.head.repo.fork == false) [7][9].
Citations:
- 1: https://sisaku-security.github.io/lint/docs/rules/botconditions/
- 2: https://github.com/jt24680/gha-exploit-guard
- 3: https://github.com/woodruffw/gha-hazmat/blob/main/.github/workflows/bot-conditions.yml
- 4: https://labs.boostsecurity.io/articles/weaponizing-dependabot-pwn-request-at-its-finest/
- 5: https://boostsecurityio.github.io/poutine/rules/confused_deputy_auto_merge/
- 6: VirtoCommerce/vc-github-actions@0132962
- 7: security: fix GitHub Actions actor impersonation vulnerability newrelic/nrdot-collector-components#227
- 8: https://docs.openrewrite.org/recipes/github/security/botconditions
- 9: newrelic/nrdot-collector-components@af3f254
🏁 Script executed:
#!/bin/bash
# Search for any comments, issues, or discussions about github.actor in workflows
rg "github\.actor|github\.event\.pull_request\.user" --type yaml -C 2Repository: stranske/Workflows
Length of output: 44
🏁 Script executed:
#!/bin/bash
# Check if other workflows in this repo use the safer pattern
find .github/workflows -name "*.yml" -o -name "*.yaml" | xargs grep -l "if:" | head -5 | xargs cat | grep -A 2 "github.event.pull_request"Repository: stranske/Workflows
Length of output: 251
Use PR author identity for bot gating, not github.actor.
Line 35 uses github.actor, which is exploitable through workflow reruns. For a pull request event, gate on github.event.pull_request.user.login instead, following GitHub Actions security best practices.
Suggested fix
- if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]'
+ if: contains(fromJSON('["dependabot[bot]","renovate[bot]"]'), github.event.pull_request.user.login)📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]' | |
| if: contains(fromJSON('["dependabot[bot]","renovate[bot]"]'), github.event.pull_request.user.login) |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/maint-dependabot-auto-lock.yml at line 35, The `if`
condition uses `github.actor` which is exploitable through workflow reruns and
does not securely verify the actual pull request author. Replace the
`github.actor` checks in the condition with
`github.event.pull_request.user.login` to gate the workflow on the actual PR
author identity instead, following GitHub Actions security best practices for
pull request events.
Source: Coding guidelines
| args=$(sed -n 's/^#[[:space:]]*uv pip compile[[:space:]]\{1,\}\(.*\)$/\1/p' requirements.lock | head -n1) | ||
| if [ -z "${args}" ]; then | ||
| echo "::error::Could not parse a 'uv pip compile' command from the requirements.lock header." | ||
| exit 1 | ||
| fi | ||
| echo "Recorded command: uv pip compile ${args}" | ||
|
|
||
| - name: Check if lock file needs update | ||
| id: check | ||
| run: | | ||
| out=requirements.check | ||
| log=compile.log | ||
| compile_args="${{ steps.compile_args.outputs.compile_args }}" | ||
| uv pip compile ${compile_args} "$out" 2>&1 | tee "$log" || { | ||
| # shellcheck disable=SC2086 | ||
| uv pip compile ${args} 2>&1 | tee compile.log || { |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Confirm the file exists and check the exact code at lines 67-75
echo "=== Checking file existence and content ==="
if [ -f ".github/workflows/maint-dependabot-auto-lock.yml" ]; then
echo "File found"
echo "Lines 67-75:"
sed -n '67,75p' .github/workflows/maint-dependabot-auto-lock.yml
else
echo "File not found"
fi
# Check if requirements.lock exists
echo -e "\n=== Checking for requirements.lock ==="
find . -name "requirements.lock" -type f | head -20Repository: stranske/Workflows
Length of output: 638
🏁 Script executed:
#!/bin/bash
# Examine actual requirements.lock files to understand the header format
echo "=== Sample requirements.lock headers ==="
find . -name "requirements.lock" -type f | while read -r file; do
echo "--- $file ---"
head -n 3 "$file"
echo ""
done | head -50Repository: stranske/Workflows
Length of output: 367
🏁 Script executed:
#!/bin/bash
# Check the full context around lines 60-80 to understand the workflow better
echo "=== Full context of the workflow (lines 60-85) ==="
sed -n '60,85p' .github/workflows/maint-dependabot-auto-lock.ymlRepository: stranske/Workflows
Length of output: 1372
🏁 Script executed:
#!/bin/bash
# Check Template's requirements.lock header format to understand what gets stored
echo "=== Template requirements.lock header ==="
if [ -f "requirements.lock" ]; then
head -n 5 requirements.lock
else
echo "No requirements.lock found"
fiRepository: stranske/Template
Length of output: 307
Harden header-command replay against shell injection.
Lines 67-75 parse command text from requirements.lock and execute it via unquoted expansion. Although the lock file is generated by uv itself and commits are subject to review, this is still exploitable if a malicious requirements.lock reaches main. Validate safe tokens and use argv-array semantics to eliminate the vector.
Suggested fix
- args=$(sed -n 's/^#[[:space:]]*uv pip compile[[:space:]]\{1,\}\(.*\)$/\1/p' requirements.lock | head -n1)
- if [ -z "${args}" ]; then
+ args=$(sed -n 's/^#[[:space:]]*uv pip compile[[:space:]]\{1,\}\(.*\)$/\1/p' requirements.lock | head -n1)
+ if [ -z "${args}" ]; then
echo "::error::Could not parse a 'uv pip compile' command from the requirements.lock header."
exit 1
fi
- echo "Recorded command: uv pip compile ${args}"
+ if printf '%s' "${args}" | grep -Eq '[;&|`$()<>]'; then
+ echo "::error::Unsafe token detected in lock header command."
+ exit 1
+ fi
+ read -r -a uv_args <<< "${args}"
+ echo "Recorded command: uv pip compile ${args}"
# shellcheck disable=SC2086
- uv pip compile ${args} 2>&1 | tee compile.log || {
+ uv pip compile "${uv_args[@]}" 2>&1 | tee compile.log || {
echo "❌ Compilation failed:"
cat compile.log
exit 1
}As per coding guidelines, ".github/workflows/**: Flag template-injection, unpinned third-party actions, and spoofable bot-actor checks — this workflow YAML is synced to 9 consumer repos, so one bug replicates fleet-wide."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/maint-dependabot-auto-lock.yml around lines 67 - 75, The
`args` variable extracted from the `requirements.lock` file header via sed is
directly expanded unquoted in the `uv pip compile ${args}` command invocation,
creating a shell injection vulnerability. To fix this, validate that the
extracted `args` contains only safe, whitelisted tokens (such as package names,
version operators like ==, >=, etc., and no shell metacharacters), and then use
proper quoting or bash array semantics when passing the arguments to the uv
command to prevent unintended command execution. Consider splitting the args
into an array and using the safe argument expansion pattern to eliminate the
injection vector.
Source: Coding guidelines
…+ auto-lock backstop) Builds on #2404, which fixed the one-time symptoms (regenerated the stale lock and reconciled the pytest pin to 9.1.0). This PR adds the SYSTEMIC fix so the gap cannot recur, and wires the consumer story Dependabot's native lock update used to cover. - renovate-presets/fleet.json: enable the pip-compile manager against requirements.lock. The hosted Renovate app re-runs the uv command recorded in the lock header and regenerates the lock IN the same PR — fleet-wide, for Workflows and every consumer (all extend this preset). #2404 noted the manager was not enabled; this enables it. lockFileMaintenance is on by default in the manager (no postUpdateOptions needed). - maint-dependabot-auto-lock.yml: extend to renovate[bot] + renovate/** as a Workflows-local backstop, and rewrite it to re-run the SAME header command verbatim instead of recomputing args. The old dynamic args added the empty `app` extra + tools/requirements-llm.txt, which diverged from the header and would have flip-flopped commits against Renovate's regen. - requirements.txt: remove the pytest pin entirely (a dev/test tool). #2404 aligned it to 9.1.0, but sync_dev_dependencies.py syncs env -> pyproject + lock and NOT requirements.txt, so a pin here re-drifts on the next pytest bump and re-breaks the lock — defeating the pip-compile manager. Removing it keeps the dev-tool pin in a single owner; the lock still carries pytest 9.1.0 via the dev extra (lock diff is the one stale `# -r requirements.txt` annotation). Verified: uv recompile succeeds; dependency-alignment + dev-version sync tests pass (30); actionlint clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
f717942 to
c841cf0
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In @.github/workflows/maint-dependabot-auto-lock.yml:
- Around line 19-21: The `branches` filter in the `pull_request` trigger (lines
19-21) incorrectly filters the base branch rather than the source branch, which
prevents proper detection of dependabot and renovate PRs. Remove the entire
`branches` filter from the pull_request configuration, and instead add an `if`
condition at the job level that uses `github.head_ref` to match against the
patterns 'dependabot/**' and 'renovate/**'. This will properly detect when the
source branch of the PR originates from dependabot or renovate, which is the
intended behavior for automatically locking these bot-generated pull requests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 8cbf9c6e-fc4f-4eb6-8561-b2d02229b50e
⛔ Files ignored due to path filters (1)
requirements.lockis excluded by!**/*.lock,!**/*.lock
📒 Files selected for processing (3)
.github/workflows/maint-dependabot-auto-lock.ymlrenovate-presets/fleet.jsonrequirements.txt
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
stranske/Template(auto-detected)
| branches: | ||
| - 'dependabot/**' | ||
| - 'renovate/**' |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/maint-dependabot-auto-lock.yml | head -50Repository: stranske/Workflows
Length of output: 1926
🌐 Web query:
GitHub Actions pull_request branches filter base branch or head branch documentation
💡 Result:
In GitHub Actions, the branches and branches-ignore filters used within the on.pull_request (or on.pull_request_target) configuration exclusively target the pull request's base branch (the branch you are merging into) [1][2][3]. There is no native syntax in the on configuration to filter by the head branch (the source branch or the branch being merged from) [4]. To filter by the head branch, you must use a conditional if expression at the job or step level [1][5][4]. 1. Filtering by Base Branch (on configuration): Use the branches filter under the pull_request event. This will cause the workflow to run only when a pull request targets a branch matching the specified pattern [1][2]. on: pull_request: branches: - main - 'releases/' 2. Filtering by Head Branch (if condition): To filter by the head branch, use the github.head_ref context within a job's if condition [1][6]. This allows you to restrict specific jobs to only run when the pull request originates from a particular source branch [1][4]. jobs: my_job: if: github.head_ref == 'feature-branch' runs-on: ubuntu-latest steps: - run: echo "This job only runs if the head branch is 'feature-branch'" You can also combine these techniques to filter by both base and head branches simultaneously [5][4]. For example: on: pull_request: branches: - main jobs: my_job: if: startsWith(github.head_ref, 'feature/') runs-on: ubuntu-latest steps: - run: echo "Runs for PRs from 'feature/*' into 'main'"
Citations:
- 1: https://docs.github.com/actions/using-workflows/events-that-trigger-workflows
- 2: https://docs.github.com/actions/using-workflows/workflow-syntax-for-github-actions
- 3: https://stackoverflow.com/questions/73296689/run-workflow-on-some-pull-request-type-that-are-only-in-a-certain-branch
- 4: https://stackoverflow.com/questions/76592824/use-on-to-filter-by-source-and-target-branch-for-pr
- 5: https://stackoverflow.com/questions/77380678/how-can-i-trigger-a-workflow-on-pull-request-specifying-base-and-head-branch
- 6: https://docs.github.com/en/actions/reference/workflows-and-actions/contexts
🏁 Script executed:
cat -n .github/workflows/maint-dependabot-auto-lock.yml | sed -n '50,80p'Repository: stranske/Workflows
Length of output: 1624
🏁 Script executed:
head -n 3 requirements.lockRepository: stranske/Template
Length of output: 236
pull_request.branches filters the base branch, not the source branch—this prevents the intended bot-PR detection.
The branches filter at lines 19–21 applies to the PR's target branch (e.g., main), not the source branch (dependabot/**, renovate/**). This means the filter will not reliably trigger on bot PRs originating from those branches. Additionally, the job-level actor check (github.actor == 'dependabot[bot]') is spoofable.
Use github.head_ref in the job if condition to detect the source branch, and remove the branches filter:
Suggested fix
on:
pull_request:
paths:
- 'pyproject.toml'
- 'requirements.txt'
- 'tools/requirements-llm.txt'
- branches:
- - 'dependabot/**'
- - 'renovate/**'- if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]'
+ if: >
+ startsWith(github.head_ref, 'dependabot/') || startsWith(github.head_ref, 'renovate/')🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In @.github/workflows/maint-dependabot-auto-lock.yml around lines 19 - 21, The
`branches` filter in the `pull_request` trigger (lines 19-21) incorrectly
filters the base branch rather than the source branch, which prevents proper
detection of dependabot and renovate PRs. Remove the entire `branches` filter
from the pull_request configuration, and instead add an `if` condition at the
job level that uses `github.head_ref` to match against the patterns
'dependabot/**' and 'renovate/**'. This will properly detect when the source
branch of the PR originates from dependabot or renovate, which is the intended
behavior for automatically locking these bot-generated pull requests.
There was a problem hiding this comment.
♻️ Duplicate comments (3)
.github/workflows/maint-dependabot-auto-lock.yml (3)
35-35:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winUse PR author identity for bot gating, not
github.actor.
github.actoris exploitable through workflow reruns. An attacker can re-run the workflow, causinggithub.actorto reflect the rerun triggerer rather than the original PR author. For pull request events, gate ongithub.event.pull_request.user.logininstead, following GitHub Actions security best practices.🔒 Suggested fix
- if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]' + if: contains(fromJSON('["dependabot[bot]","renovate[bot]"]'), github.event.pull_request.user.login)As per coding guidelines, ".github/workflows/**: Flag template-injection, unpinned third-party actions, and spoofable bot-actor checks — this workflow YAML is synced to 9 consumer repos, so one bug replicates fleet-wide."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/maint-dependabot-auto-lock.yml at line 35, The condition uses `github.actor` which is exploitable during workflow reruns, as it reflects the rerun triggerer rather than the original PR author. Replace the `github.actor` checks in the if condition with `github.event.pull_request.user.login` to properly gate on the actual pull request author's identity, ensuring the workflow only runs when the PR was originally created by dependabot or renovate, not when an attacker reruns the workflow.Source: Coding guidelines
19-21:⚠️ Potential issue | 🟠 Major | ⚡ Quick win
pull_request.branchesfilters the base branch, not the source branch—this prevents the intended bot-PR detection.The
branchesfilter applies to the PR's target branch (e.g.,main), not the source branch (dependabot/**,renovate/**). Bot PRs originate from branches matching those patterns, but targetmainor similar, so this filter will not trigger as intended.Remove the
branchesfilter and usegithub.head_refin the jobifcondition to detect the source branch pattern.🔧 Suggested fix
Remove the branches filter:
on: pull_request: paths: - 'pyproject.toml' - 'requirements.txt' - 'tools/requirements-llm.txt' - branches: - - 'dependabot/**' - - 'renovate/**'Update the job condition at line 35 to check the head branch:
- if: github.actor == 'dependabot[bot]' || github.actor == 'renovate[bot]' + if: startsWith(github.head_ref, 'dependabot/') || startsWith(github.head_ref, 'renovate/')🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/maint-dependabot-auto-lock.yml around lines 19 - 21, The pull_request.branches filter checks the target branch (main), not the source branch where bot PRs originate (dependabot/**, renovate/**), so it will not trigger the workflow as intended. Remove the entire branches filter block containing dependabot/** and renovate/**, and instead update the job if condition at line 35 to use github.head_ref to check whether the source branch matches the pattern for dependabot or renovate bot PRs.Source: Coding guidelines
67-75:⚠️ Potential issue | 🔴 Critical | ⚡ Quick winHarden header-command replay against shell injection.
Lines 67-75 extract command arguments from
requirements.lockviasedand execute them with unquoted expansion (uv pip compile ${args}). Although the lock is generated byuvand commits are reviewed, this creates a shell injection vector if a maliciousrequirements.lockreaches main—particularly critical given this workflow syncs to 9 consumer repos.Validate that extracted args contain only safe tokens (flags, paths, version operators) and use bash array semantics to eliminate the injection risk.
🛡️ Suggested fix
args=$(sed -n 's/^#[[:space:]]*uv pip compile[[:space:]]\{1,\}\(.*\)$/\1/p' requirements.lock | head -n1) if [ -z "${args}" ]; then echo "::error::Could not parse a 'uv pip compile' command from the requirements.lock header." exit 1 fi + # Validate that args contains only safe tokens (no shell metacharacters) + if printf '%s' "${args}" | grep -Eq '[;&|`$()<>]'; then + echo "::error::Unsafe token detected in lock header command." + exit 1 + fi + # Split into array for safe expansion + read -r -a uv_args <<< "${args}" echo "Recorded command: uv pip compile ${args}" # shellcheck disable=SC2086 - uv pip compile ${args} 2>&1 | tee compile.log || { + uv pip compile "${uv_args[@]}" 2>&1 | tee compile.log || { echo "❌ Compilation failed:" cat compile.log exit 1 }As per coding guidelines, ".github/workflows/**: Flag template-injection, unpinned third-party actions, and spoofable bot-actor checks — this workflow YAML is synced to 9 consumer repos, so one bug replicates fleet-wide."
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In @.github/workflows/maint-dependabot-auto-lock.yml around lines 67 - 75, The shell variable args extracted from requirements.lock via sed is used with unquoted expansion in the uv pip compile command, creating a shell injection vulnerability if malicious content reaches the file. Add validation after the sed extraction to ensure args contains only safe tokens such as flags (dashes), paths (alphanumeric, dots, slashes), and version operators (equals, greater-than, less-than, exclamation, tilde), and exit with an error if validation fails. Then convert the args into a bash array and pass it using array expansion syntax (args_array[@]) instead of the unquoted variable expansion to safely prevent shell interpretation of any special characters.Source: Coding guidelines
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Duplicate comments:
In @.github/workflows/maint-dependabot-auto-lock.yml:
- Line 35: The condition uses `github.actor` which is exploitable during
workflow reruns, as it reflects the rerun triggerer rather than the original PR
author. Replace the `github.actor` checks in the if condition with
`github.event.pull_request.user.login` to properly gate on the actual pull
request author's identity, ensuring the workflow only runs when the PR was
originally created by dependabot or renovate, not when an attacker reruns the
workflow.
- Around line 19-21: The pull_request.branches filter checks the target branch
(main), not the source branch where bot PRs originate (dependabot/**,
renovate/**), so it will not trigger the workflow as intended. Remove the entire
branches filter block containing dependabot/** and renovate/**, and instead
update the job if condition at line 35 to use github.head_ref to check whether
the source branch matches the pattern for dependabot or renovate bot PRs.
- Around line 67-75: The shell variable args extracted from requirements.lock
via sed is used with unquoted expansion in the uv pip compile command, creating
a shell injection vulnerability if malicious content reaches the file. Add
validation after the sed extraction to ensure args contains only safe tokens
such as flags (dashes), paths (alphanumeric, dots, slashes), and version
operators (equals, greater-than, less-than, exclamation, tilde), and exit with
an error if validation fails. Then convert the args into a bash array and pass
it using array expansion syntax (args_array[@]) instead of the unquoted variable
expansion to safely prevent shell interpretation of any special characters.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: c918b8e8-f52a-4c84-a730-a11bbd9d11aa
⛔ Files ignored due to path filters (1)
requirements.lockis excluded by!**/*.lock,!**/*.lock
📒 Files selected for processing (3)
.github/workflows/maint-dependabot-auto-lock.ymlrenovate-presets/fleet.jsonrequirements.txt
🔗 Linked repositories identified
CodeRabbit considers these linked repositories for cross-repo context during reviews:
stranske/Template(auto-detected)
…1170) Renovate's pip-compile manager (enabled fleet-wide in the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option --extra must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (verified: header-only diff). Confirmed via `npx renovate@43 --platform=local --dry-run=extract`: pip-compile now extracts the lock (depCount 195) with no error. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1965) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1391) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…5565) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…1210) Renovate's pip-compile manager (enabled fleet-wide via the shared preset, stranske/Workflows#2406) parses the lock header to reconstruct the uv command and requires every option in equals form (--extra=, --output-file=). This lock's header used space form, so Renovate raised a non-fatal "Option ... must have equal sign" warning and SILENTLY SKIPPED the lock — meaning it was never regenerated on dependency bumps. This rewrites only the header line to equals form; the resolved pins are byte-identical (header-only diff). Verified with `npx renovate@43 --platform=local --dry-run=extract`: pip-compile extracts the lock with no error after the change. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…tenance (#2413) Renovate's pip-compile manager (enabled on requirements.lock via the canary renovate.json #2411 + fleet preset #2406) already owns periodic lock regeneration: supportsLockFileMaintenance=true and its defaultConfig enables lockFileMaintenance by default (branchTopic `pip-compile-refresh`), recompiling the lock from scratch on a schedule. That makes maint-51-dependency-refresh.yml's scheduled `uv pip compile --upgrade` a duplicate refresher (and it had failed every run since ~March until #2404). Retire it. Verified the feared `--upgrade`-in-header risk does NOT exist: - uv omits `--upgrade` from the recorded lock header (empirically: compiles with and without `--upgrade` produce byte-identical headers). - Even if it appeared, Renovate's pip-compile header parser (common.ts throwForUnknownOption) would throw `Option --upgrade not supported (yet)` and skip the file; it never re-runs a mass `--upgrade` (per-dep bumps are scoped via `--upgrade-package` in artifacts.ts). maint-51's other steps remain covered: sync_test_dependencies.py --verify runs in reusable-10-ci-python on every PR; dev-tool pin alignment is enforced on schedule by maint-auto-update-pypi-versions + maint-sync-env-from-pyproject (--apply). - delete .github/workflows/maint-51-dependency-refresh.yml - drop its EXPECTED_NAMES entry in tests/workflows/test_workflow_naming.py - remove it from docs/ci/WORKFLOWS.md (link guard), WORKFLOW_SYSTEM.md, WORKFLOW_GUIDE.md, DEPENDENCY_TESTING.md; document lockFileMaintenance ownership in renovate.json Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The fleet preset (renovate-presets/fleet.json) has provided the byte-identical
`"pip-compile": {"managerFilePatterns": ["/(^|/)requirements\\.lock$/"]}` block
since #2406, and Workflows extends that preset — so the copy in renovate.json
was dead weight (Renovate merged duplicate manager config to no effect).
Removes the redundant block and refreshes the now-stale description: it still
said "CANARY (do not promote to the fleet preset until proven here)" even though
the manager WAS promoted in #2406. Keeps the lockFileMaintenance / --upgrade
notes from #2413 and the one genuine Workflows-specific override (pip_requirements
disabled for requirements.txt, which is a lock SOURCE here — consumer locks source
pyproject.toml only, which is why that disable is intentionally not in the preset).
Verified: `renovate-config-validator` passes; `renovate --platform=local
--dry-run` still extracts requirements.lock via the preset (depCount 217) with
no error after removal.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…2417) The fleet preset (renovate-presets/fleet.json) has provided the byte-identical `"pip-compile": {"managerFilePatterns": ["/(^|/)requirements\\.lock$/"]}` block since #2406, and Workflows extends that preset — so the copy in renovate.json was dead weight (Renovate merged duplicate manager config to no effect). Removes the redundant block and refreshes the now-stale description: it still said "CANARY (do not promote to the fleet preset until proven here)" even though the manager WAS promoted in #2406. Keeps the lockFileMaintenance / --upgrade notes from #2413 and the one genuine Workflows-specific override (pip_requirements disabled for requirements.txt, which is a lock SOURCE here — consumer locks source pyproject.toml only, which is why that disable is intentionally not in the preset). Verified: `renovate-config-validator` passes; `renovate --platform=local --dry-run` still extracts requirements.lock via the preset (depCount 217) with no error after removal. Co-authored-by: stranske <tim@stranskemo.com> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Priority 1 — lock-regen gap (durable / systemic fix)
Builds on #2404, which already fixed the one-time symptoms (regenerated the stale lock after Renovate #2389, and reconciled the conflicting
pytestpin to 9.1.0 souv pip compileis satisfiable again). This PR adds the systemic fix so the gap cannot recur, and wires the consumer story that Dependabot's native lock update used to cover.Why #2404 alone isn't enough
#2404noted "Renovate's pip-compile manager is not enabled in the fleet preset, so it does not regenerate this uv-pip-compile lock." That's the root gap — without it, every future Renovate source bump leavesrequirements.lockstale (and consumers, which have no auto-lock workflow, never regenerate at all).Changes
renovate-presets/fleet.json— enable the pip-compile manager againstrequirements.lock. The hosted Renovate app re-runs theuv pip compilecommand recorded in the lock header and regenerates the lock in the same PR, fleet-wide (Workflows + every consumer extends this preset). This is the answer to the consumer investigation: consumers relied on Dependabot's native lock update; this preset restores it.lockFileMaintenanceis on by default in the manager — nopostUpdateOptionsneeded.maint-dependabot-auto-lock.yml— extend torenovate[bot]+renovate/**as a Workflows-local backstop, and rewrite it to re-run the same header command verbatim instead of recomputing args. The old dynamic args added the emptyappextra +tools/requirements-llm.txt, diverging from the header → would have flip-flopped commits against Renovate's regen (the "fight automerge" failure mode). Re-running the header guarantees byte-identical output, so the backstop no-ops when Renovate already regenerated and only acts if that ever fails.requirements.txt— remove thepytestpin entirely. fix(deps): regenerate stale requirements.lock + reconcile pytest pin #2404 aligned it to 9.1.0, butsync_dev_dependencies.pysyncsautofix-versions.env→pyproject+requirements.lockand notrequirements.txt, so a pin here re-drifts on the next pytest bump and re-breaks the lock — defeating the pip-compile manager. Removing it keeps the dev-tool pin in a single owner; the lock still carries pytest 9.1.0 via thedevextra (lock diff is the single now-stale# -r requirements.txtannotation).Verification
uv pip compile(main's header command) succeeds; lock diff vsmainis one annotation line.tests/test_dependency_version_alignment.py,tests/scripts/test_sync_{dev_dependencies,tool_versions}.py— 30 passed.actionlintclean on the rewritten workflow.Part of P4 (finish retiring Dependabot). Follows #2386/#2394/#2401/#2404.
Summary by CodeRabbit
pip-compile/uv pip compile-based regeneration scoped torequirements.lock.pytestline fromrequirements.txtand clarifying that dev/test versions are managed via existing synchronization tooling and config.