Skip to content
Merged
Show file tree
Hide file tree
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
30 changes: 21 additions & 9 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,42 @@
name: Publish Extensions

on:
push:
tags:
- v*
workflow_dispatch:
pull_request:
types: [closed]
branches: [main]

jobs:
publish-extension:
if: >-
github.event.pull_request.merged == true &&
github.event.pull_request.head.repo.full_name == github.repository &&
startsWith(github.event.pull_request.head.ref, 'release/')

runs-on: ubuntu-latest
permissions:
id-token: write
contents: write
actions: write

runs-on: ubuntu-slim
steps:
- name: Setup JS
uses: sxzz/workflows/setup-js@dd67f194be6388e12ee61ad0cd3b81cf74bfd294
with:
fetch-all: true

- name: Create tag
env:
GH_TOKEN: ${{ github.token }}
run: |
VERSION=$(jq -r '.version' package.json)
gh api "repos/$GITHUB_REPOSITORY/git/refs" \
-f ref="refs/tags/v$VERSION" \
-f sha="${{ github.event.pull_request.merge_commit_sha }}"
Comment on lines +31 to +33

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu
printf '%s\n' '--- workflow ---'
sed -n '1,90p' .github/workflows/publish.yml
printf '%s\n' '--- related references ---'
rg -n 'git/refs|merge_commit_sha|VERSION|publish' .github README.md 2>/dev/null || true

Repository: npmx-dev/vscode-npmx

Length of output: 2705


🌐 Web query:

GitHub REST API create a reference existing ref conflict get a reference tag object SHA annotated tag documentation

💡 Result:

To create a reference (like a branch or tag) using the GitHub REST API, you use the POST /repos/{owner}/{repo}/git/refs endpoint [1][2]. If you attempt to create a reference that already exists, the API will return a 409 Conflict status code [1][3]. Regarding annotated tags and their object SHAs: 1. An annotated tag in Git is an object that contains its own unique SHA (the tag object SHA), which is distinct from the SHA of the commit it points to [4]. 2. When you retrieve a reference for an annotated tag (e.g., using GET /repos/{owner}/{repo}/git/refs/tags/{tag_name}), the response contains an object block. This block includes the tag's type (which will be "tag") and its object SHA [4]. 3. To get the full details of the annotated tag object (such as the tagger name, date, and the underlying commit SHA), you use the GET /repos/{owner}/{repo}/git/tags/{tag_sha} endpoint, passing the tag object SHA obtained from the reference [5][4]. Creating an annotated tag via the API is a two-step process: first, create the annotated tag object (POST /repos/{owner}/{repo}/git/tags), then create the reference pointing to that object (POST /repos/{owner}/{repo}/git/refs) [5][6]. Creating only the reference without the tag object results in a lightweight tag [5][6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu
python3 - <<'PY'
import json
import re
import urllib.request
from pathlib import Path

workflow = Path(".github/workflows/publish.yml").read_text()
create = re.search(
    r'gh api "repos/\$GITHUB_REPOSITORY/git/refs"\s*\\\n'
    r'\s*-f ref="refs/tags/v\$VERSION"\s*\\\n'
    r'\s*-f sha="\$\{\{ github\.event\.pull_request\.merge_commit_sha \}\}"',
    workflow,
)
steps = re.findall(r'^\s+- name: (.+)$', workflow, re.MULTILINE)
print("create_request_shape:", bool(create))
print("step_order:", steps)
print("create_before_build:", steps.index("Create tag") < steps.index("Build"))
print("create_before_publish:", steps.index("Create tag") < steps.index("Publish npm packages to registry"))

repo = "npmx-dev/vscode-npmx"
req = urllib.request.Request(
    f"https://api.github.com/repos/{repo}/git/refs/tags",
    headers={"Accept": "application/vnd.github+json", "User-Agent": "read-only-review-probe"},
)
try:
    with urllib.request.urlopen(req, timeout=10) as response:
        refs = json.load(response)
    print("tag_listing_status: 200")
    print("tag_count:", len(refs))
    print("sample_refs:", [item.get("ref") for item in refs[:5]])
except urllib.error.HTTPError as exc:
    print("tag_listing_status:", exc.code)
    print("tag_listing_error:", exc.read().decode("utf-8", "replace")[:300])
except Exception as exc:
    print("tag_listing_error:", type(exc).__name__, str(exc))
PY

Repository: npmx-dev/vscode-npmx

Length of output: 566


Make tag creation safe to retry.

If a later step fails, a rerun receives 409 Conflict because the tag already exists. Read refs/tags/v$VERSION first. Continue only when it points to the merge commit. Fail when it points elsewhere. If creation races with another job, re-read the tag after 409 and apply the same comparison. Dereference annotated tags before comparing their target commit.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 33-33: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🔒 Security & Privacy | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -eu

printf '%s\n' '--- workflow files ---'
git ls-files '.github/workflows/*' | sort

printf '%s\n' '--- publish workflow size ---'
wc -l .github/workflows/publish.yml

printf '%s\n' '--- publish workflow relevant sections ---'
cat -n .github/workflows/publish.yml | sed -n '1,75p'

printf '%s\n' '--- zizmor references and merge_commit_sha uses ---'
rg -n -C 3 'zizmor|merge_commit_sha|template-injection|pull_request_target|pull_request' \
  .github pyproject.toml package.json Makefile 2>/dev/null || true

Repository: npmx-dev/vscode-npmx

Length of output: 5310


🌐 Web query:

GitHub pull_request webhook payload merge_commit_sha type documentation zizmor template-injection environment variable remediation

💡 Result:

Regarding GitHub pull_request webhooks, the merge_commit_sha field is technically present in live payloads; however, it has been observed that some recent API schema definitions (e.g., the 2026-03-10 spec) inaccurately omit this field [1]. This discrepancy between the live runtime contract and the formal schema specification can cause issues for tools or integrations relying solely on the generated API schemas [1]. Zizmor is a static analysis tool designed to secure GitHub Actions workflows [2][3]. One of its key capabilities is detecting template injection vulnerabilities, which occur when user-controlled inputs (like issue titles or PR data) are improperly expanded within ${{... }} delimiters in workflow files [4]. Because these expansions happen before execution, they can lead to unintended code injection if placed inside sensitive contexts like run: blocks [4]. To remediate these template injection vulnerabilities, zizmor recommends replacing the direct template expression with an environment variable [4]. The remediation process involves two steps: 1. Defining the environment variable within the step's env: block, assigning it the value of the template expression (e.g., FOO: ${{ github.event.issue.title }}) [5]. 2. Using the shell-specific syntax to reference that variable in the script block (e.g., echo ${FOO} for bash/sh) [4][5]. This approach ensures that the data is handled via the shell's variable expansion mechanisms rather than the GitHub Actions workflow runner's template expansion, effectively preventing the injection [4]. Users should ensure their remediation uses the correct shell syntax (e.g., ${VAR} for sh/bash, %VAR% for cmd, or $env:VAR for PowerShell) [5]. Note that some zizmor versions have refined this logic to preserve case sensitivity for specific contexts, such as existing env.* variables [6].

Citations:


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import subprocess

script = r'''
set -u
MERGE_COMMIT_SHA_PLACEHOLDER="$1"
# Model the workflow's current shell source after GitHub expression interpolation.
eval 'printf "current:%s\n" "' + MERGE_COMMIT_SHA_PLACEHOLDER + '"'
'''

values = {
    "valid_sha": "0123456789abcdef0123456789abcdef01234567",
    "quoted_sha": '0123456789abcdef0123456789abcdef01234567"; printf "injected"',
    "command_substitution": '$(printf injected)',
}

for name, value in values.items():
    print(f"--- {name} ---")
    result = subprocess.run(
        ["bash", "-c", script, "probe", value],
        text=True,
        capture_output=True,
    )
    print("exit:", result.returncode)
    print("stdout:", result.stdout.rstrip())
    print("stderr:", result.stderr.rstrip())

print("--- environment-variable form ---")
for name, value in values.items():
    result = subprocess.run(
        ["bash", "-c", 'MERGE_COMMIT_SHA="$1"; printf "env:%s\\n" "$MERGE_COMMIT_SHA"', "probe", value],
        text=True,
        capture_output=True,
    )
    print(name, "exit:", result.returncode, "stdout:", result.stdout.rstrip())
PY

Repository: npmx-dev/vscode-npmx

Length of output: 682


🏁 Script executed:

#!/bin/bash
set -eu

python3 - <<'PY'
import subprocess

values = {
    "valid_sha": "0123456789abcdef0123456789abcdef01234567",
    "quoted_sha": '0123456789abcdef0123456789abcdef01234567"; printf "injected"',
    "command_substitution": '$(printf injected)',
}

for name, value in values.items():
    print(f"--- {name}: direct interpolation ---")
    source = f'printf "direct:%s\\n" "{value}"'
    result = subprocess.run(
        ["bash", "-c", source],
        text=True,
        capture_output=True,
    )
    print("exit:", result.returncode)
    print("stdout:", result.stdout.rstrip())
    print("stderr:", result.stderr.rstrip())

    print(f"--- {name}: environment variable ---")
    result = subprocess.run(
        ["bash", "-c", 'printf "env:%s\\n" "$MERGE_COMMIT_SHA"'],
        env={"MERGE_COMMIT_SHA": value},
        text=True,
        capture_output=True,
    )
    print("exit:", result.returncode)
    print("stdout:", result.stdout.rstrip())
    print("stderr:", result.stderr.rstrip())
PY

Repository: npmx-dev/vscode-npmx

Length of output: 833


Keep event data out of shell source.

Pass github.event.pull_request.merge_commit_sha through env and use $MERGE_COMMIT_SHA in the gh api command. This prevents shell injection and addresses the zizmor template-injection finding.

🧰 Tools
🪛 zizmor (1.29.0)

[error] 33-33: code injection via template expansion (template-injection): may expand into attacker-controllable code

(template-injection)

Source: Linters/SAST tools


- name: Generate Github Changelog
run: npx changelogithub
continue-on-error: true
env:
GITHUB_TOKEN: ${{secrets.GITHUB_TOKEN}}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

- name: Build
run: pnpm build
Expand All @@ -41,8 +53,8 @@ jobs:
run: npx vsxpub --no-dependencies
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
VSCE_PAT: ${{secrets.VSCE_PAT}}
OVSX_PAT: ${{secrets.OVSX_PAT}}
VSCE_PAT: ${{ secrets.VSCE_PAT }}
OVSX_PAT: ${{ secrets.OVSX_PAT }}

# - name: Publish Zed Extension
# uses: huacnlee/zed-extension-action@v2
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
"fmt:rust": "cargo fmt --manifest-path extensions/zed/Cargo.toml",
"fmt:rust:check": "cargo fmt --manifest-path extensions/zed/Cargo.toml --check",
"typecheck": "tsc -b --noEmit",
"release": "npx bumpp -r -a -x \"node scripts/sync-zed-version.ts\"",
"release": "npx bumpp --pr -r -a -x \"node scripts/sync-zed-version.ts\"",
"prepare": "husky"
},
"nano-staged": {
Expand Down