-
Notifications
You must be signed in to change notification settings - Fork 0
fix(ci): package the Linux tarball reproducibly so a nightly can be re-run #718
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -550,17 +550,39 @@ jobs: | |
| cp "target/$TARGET/release/wfl-lsp" "dist/$DIR/wfl-lsp" | ||
| strip "dist/$DIR/wfl" "dist/$DIR/wfl-lsp" | ||
| cp README.md LICENSE "dist/$DIR/" | ||
|
|
||
| # publish_spaces.sh treats versioned keys as immutable: identical bytes | ||
| # are a no-op and different bytes abort the publish, which is what lets | ||
| # a publish that half-landed be repaired by re-running the nightly. | ||
| # That contract only holds if the same commit packages to the same | ||
| # bytes, so everything below that would otherwise vary run to run is | ||
| # pinned to the commit rather than to the wall clock: | ||
| # | ||
| # - BUILD_INFO's `built:` is the commit's own committer date. It is | ||
| # still the moment this artifact corresponds to, and unlike | ||
| # `date -u` it does not change when the same commit is rebuilt. | ||
| # - --sort=name fixes member order, which otherwise follows | ||
| # directory-read order and reshuffles between runs. | ||
| # - --mtime pins the header timestamps, which otherwise record when | ||
| # `cp` happened. | ||
| # - --owner/--group/--numeric-owner drop the runner's uid/gid names. | ||
| # | ||
| # gzip already records MTIME=0 here because tar -z compresses a pipe. | ||
| SOURCE_DATE_EPOCH="$(git show -s --format=%ct HEAD)" | ||
| cat > "dist/$DIR/BUILD_INFO" <<EOF | ||
| wfl ${VERSION} | ||
| commit: ${{ github.sha }} | ||
| branch: ${{ github.ref_name }} | ||
| built: $(date -u +%Y-%m-%dT%H:%M:%S+00:00) | ||
| built: $(date -u -d "@$SOURCE_DATE_EPOCH" +%Y-%m-%dT%H:%M:%S+00:00) | ||
|
Comment on lines
+571
to
+576
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Change to the published Linux archive ships without the required project history note The change alters the contents of the shipped Linux archive ( Rule source and comparable precedent
Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback.
Comment on lines
572
to
+576
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔒 Security & Privacy | 🟠 Major | ⚡ Quick win 🧩 Analysis chain🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
rg -n -C8 \
'build-linux|pull_request_target|pull_request|workflow_dispatch|schedule|push|permissions:' \
.github/workflows/nightly.ymlRepository: WebFirstLanguage/wfl Length of output: 8120 🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- workflow section ---'
sed -n '450,590p' .github/workflows/nightly.yml
printf '%s\n' '--- all event and ref usage ---'
rg -n -C5 '^[[:space:]]*(on:|schedule:|workflow_dispatch:|push:|pull_request|pull_request_target:|ref:|github\.ref_name|github\.ref)' .github/workflows/nightly.yml
printf '%s\n' '--- repository guidance ---'
if [ -f SECURITY.md ]; then
sed -n '1,180p' SECURITY.md
fiRepository: WebFirstLanguage/wfl Length of output: 15922 🌐 Web query:
💡 Result: To manually trigger a GitHub Actions workflow using the workflow_dispatch event in a public repository, the following requirements and behaviors apply: Requirement for Default Branch: The workflow file containing the workflow_dispatch trigger must exist on the repository's default branch [1][2][3]. Even if you intend to run the workflow on a different branch, the configuration must be present in the default branch to be discoverable by the GitHub UI [2][3]. Permissions: By default, only users with write access to the repository can manually trigger workflows using the workflow_dispatch event [2][3]. Granular Control (New): As of June 2026, GitHub introduced workflow execution protections (currently in public preview) that allow enterprise administrators and repository owners to implement more granular control [4]. These new policies allow you to define "Actor rules" that can further restrict or permit who can trigger workflows, separating the ability to contribute code (via write access) from the ability to execute workflows [4]. For example, you can now limit workflow_dispatch triggers to specific maintainers or repository roles rather than allowing every user with write access to perform manual runs [4]. Branch Selection: When triggering a workflow manually via the GitHub UI, you can select any branch or tag in the repository to run the workflow against [2][3]. When using the GitHub CLI or REST API, you can specify the target ref (branch or tag) using the --ref flag or the ref request body parameter, respectively [2][3]. Summary of Key Points: - Trigger Requirement: The workflow file must be on the default branch [2][3]. - Manual Trigger Permission: Requires at least write access, unless further restricted by organization or repository-level workflow execution policies [2][3][4]. - Target Branch: You can target any branch or tag during the manual execution, regardless of the branch where the workflow file resides [2][3]. - Execution Security: New workflow execution policies allow for stricter control over who can perform manual runs, helping prevent unauthorized or abusive triggers [4]. Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
python3 - <<'PY'
import subprocess
import tempfile
from pathlib import Path
candidates = ["$(id)", "$(hostname)", "`id`", "$(echo${IFS}pwned)"]
print("--- ref-name syntax probes ---")
for name in candidates:
result = subprocess.run(
["git", "check-ref-format", f"refs/heads/{name}"],
capture_output=True,
text=True,
)
print(f"{name!r}: {'valid' if result.returncode == 0 else 'invalid'}")
print("--- heredoc expansion probe ---")
with tempfile.TemporaryDirectory() as d:
script = Path(d) / "probe.sh"
script.write_text(
'set -eu\n'
'cat > "$1" <<EOF\n'
'branch: $(printf injected)\n'
'EOF\n'
)
output = subprocess.run(
["bash", str(script), str(Path(d) / "BUILD_INFO")],
capture_output=True,
text=True,
check=True,
)
print((Path(d) / "BUILD_INFO").read_text().rstrip())
PY
printf '%s\n' '--- workflow-level permissions and triggers ---'
sed -n '1,35p' .github/workflows/nightly.yml
rg -n -C3 'github\.ref_name|permissions:|secrets\.|environment:|workflow_dispatch|schedule:|push:|pull_request' .github/workflows/nightly.ymlRepository: WebFirstLanguage/wfl Length of output: 4384 🌐 Web query:
💡 Result: Using untrusted input like Citations:
🏁 Script executed: #!/usr/bin/env bash
set -euo pipefail
printf '%s\n' '--- publish step ---'
sed -n '690,716p' .github/workflows/nightly.yml
printf '%s\n' '--- publish script candidates and ref handling ---'
fd -i 'publish_spaces\.sh|publish.*spaces.*' .
rg -n -C5 'publish_spaces\.sh|BRANCH|ref_name|argv|process\.argv|\$4' .github scripts . 2>/dev/null | head -n 240
printf '%s\n' '--- shell quoting probe for the second use ---'
python3 - <<'PY'
import subprocess, tempfile
from pathlib import Path
# Check whether Git permits metacharacters that can terminate the quoted
# argument used at the publish call site.
names = ['evil"$(id)"', 'evil";id;echo "', 'evil`id`', 'evil$(id)']
for name in names:
r = subprocess.run(
['git', 'check-ref-format', f'refs/heads/{name}'],
capture_output=True, text=True,
)
print(f'{name!r}: {"valid" if r.returncode == 0 else "invalid"}')
with tempfile.TemporaryDirectory() as d:
script = Path(d) / 'probe.sh'
script.write_text('set -eu\nprintf "<%s>\\n" "prefix-${{VALUE}}"\n')
# This models expression substitution before bash parses the run script.
text = script.read_text().replace('${{VALUE}}', 'evil"; printf injected "')
script.write_text(text)
print(subprocess.run(['bash', str(script)], capture_output=True, text=True).stdout.rstrip())
PYRepository: WebFirstLanguage/wfl Length of output: 17637 Pass 🧰 Tools🪛 zizmor (1.29.0)[error] 575-575: code injection via template expansion (template-injection): may expand into attacker-controllable code (template-injection) 🤖 Prompt for AI AgentsSources: Coding guidelines, Linters/SAST tools |
||
| builder: GitHub Actions / Blacksmith (x86_64-unknown-linux-musl) | ||
| rustc: $(rustc --version) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🔍 The rebuild-safety goal still fails if the floating stable rustc moves
Was this helpful? React with 👍 or 👎 to provide feedback. |
||
| contents: wfl, wfl-lsp | ||
|
Comment on lines
+576
to
579
|
||
| note: statically linked against musl - no glibc floor (wfl#616) | ||
| EOF | ||
| tar czf "dist/${DIR}-${SHORT_SHA}.tar.gz" -C dist "$DIR" | ||
| tar --sort=name \ | ||
| --mtime="@$SOURCE_DATE_EPOCH" \ | ||
| --owner=0 --group=0 --numeric-owner \ | ||
| -czf "dist/${DIR}-${SHORT_SHA}.tar.gz" -C dist "$DIR" | ||
|
Comment on lines
+582
to
+585
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🟡 Packaging change is filed under the lowest risk tier and merges with no automated test The change is declared non-behavioral even though it alters the bytes of a published artifact (new packaging recipe at Policy text and why R0 does not apply
Prompt for agentsWas this helpful? React with 👍 or 👎 to provide feedback. |
||
| ls -la "dist/${DIR}-${SHORT_SHA}.tar.gz" | ||
|
|
||
| # The real boundary test, not a proxy for it, and it runs *after* packaging | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔍 Re-running a nightly can still abort on the MSI/VSIX, which were not made reproducible
scripts/publish_spaces.shapplies the same immutability check to the MSI and the VSIX viapublish_immutable(seescripts/publish_spaces.sh:150-177and the Phase 1 calls below it). The Windows MSI in particular embeds package GUIDs and timestamps and is very unlikely to be byte-identical across two builds of the same commit. So the PR's headline outcome — "a nightly can be re-run" — is only achieved for the Linux leg; the release job would still fail on the MSI at the same step. Worth confirming against a real re-run before relying on re-run as the remediation path.Was this helpful? React with 👍 or 👎 to provide feedback.