Add branch-deployable preview API infrastructure#108
Conversation
How to use the Graphite Merge QueueAdd the label add-to-gt-merge-queue to this PR to add it to the merge queue. You must have a Graphite account in order to use the merge queue. Sign up using this link. An organization admin has enabled the Graphite Merge Queue in this repository. Please do not merge from GitHub as this will restart CI on PRs being processed by the merge queue. This stack of pull requests is managed by Graphite. Learn more about stacking. |
📝 WalkthroughWalkthroughAdds end-to-end preview environment support: conditional Terraform preview resources/outputs, NixOS environment parameterization, flake and deploy tooling for preview targets, a GitHub Actions workflow to run preview deployments (service-only or full), preview runtime config, and documentation updates. ChangesPreview Environment Deployment Infrastructure
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 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)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (3)
.github/workflows/deploy-preview.yaml (2)
70-70: ⚡ Quick winDocument what prep.sh does.
The workflow runs
./prep.shwithout context. Consider adding a comment explaining its purpose (e.g., "Build service binary", "Prepare deployment artifacts", etc.) to help future maintainers understand the deployment flow.📝 Example documentation comment
+ # Prepare deployment artifacts (service binary, etc.) - run: ./prep.sh🤖 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/deploy-preview.yaml at line 70, Add a short inline comment above the run step that invokes ./prep.sh in the deploy-preview.yaml workflow that explains what prep.sh does (for example "Build service binary", "Prepare deployment artifacts", or "Install dependencies and package release assets"); reference the run step calling ./prep.sh so maintainers can see the intent and downstream effects without inspecting the script itself and ensure the comment briefly lists any major side effects (e.g., builds, artifact creation, env assumptions).
72-78: ⚡ Quick winConsider adding timeout-minutes to deployment steps.
Long-running deployments could hang indefinitely (up to the job-level default of 360 minutes). Adding explicit timeouts improves failure diagnostics and prevents runaway workflows.
⏱️ Proposed timeout configuration
- name: Deploy preview service if: inputs.deploy_scope == 'service' + timeout-minutes: 15 run: nix run .#deployPreviewService - name: Deploy preview system and service if: inputs.deploy_scope == 'all' + timeout-minutes: 30 run: nix run .#deployPreviewAll🤖 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/deploy-preview.yaml around lines 72 - 78, The deployment steps "Deploy preview service" and "Deploy preview system and service" currently can run indefinitely; add an explicit GitHub Actions timeout by adding a timeout-minutes field to each step that runs the nix commands (the steps named "Deploy preview service" running "nix run .#deployPreviewService" and "Deploy preview system and service" running "nix run .#deployPreviewAll"), choosing an appropriate minute value for your deployment (e.g., 30–60) so long-running runs fail fast and produce clearer diagnostics.DEPLOY.md (1)
276-276: ⚡ Quick winAvoid exposing credentials in shell history.
The example command passes
API_KEYandAPI_SECRETas environment variables on the command line, which will be saved to shell history in plaintext. Consider recommending a safer pattern.🔐 Safer alternative using environment file or prompt
After deploy, wait for `/health/detailed` to report a ready raindex state before running performance checks: ```bash -API_URL=https://api.preview.st0x.io API_KEY=... API_SECRET=... ./scripts/smoke.sh +# Option 1: Use .env file (ensure it's gitignored) +export API_URL=https://api.preview.st0x.io +export API_KEY=$(cat ~/.config/st0x/api_key) +export API_SECRET=$(cat ~/.config/st0x/api_secret) +./scripts/smoke.sh + +# Option 2: Prompt for secrets +read -sp "API_KEY: " API_KEY && export API_KEY +read -sp "API_SECRET: " API_SECRET && export API_SECRET +API_URL=https://api.preview.st0x.io ./scripts/smoke.sh🤖 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 `@DEPLOY.md` at line 276, The example in DEPLOY.md exposes API_KEY and API_SECRET on the command line; update the example to recommend safer patterns instead: instruct users to load secrets from a gitignored env file or secure storage (e.g., export API_KEY=$(cat ~/.config/st0x/api_key)) before running ./scripts/smoke.sh, or prompt for secrets interactively using a silent read (read -sp) and export them, and show using API_URL only on the command invocation if needed; reference the variables API_KEY, API_SECRET and the script ./scripts/smoke.sh so readers know where to apply the change.
🤖 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/deploy-preview.yaml:
- Around line 60-68: Add a short inline comment above the
PREVIEW_SSH_HOST_KEY/ssh-keyscan block warning that the ssh-keyscan fallback
(ssh-keyscan -H "$HOST_IP" writing to ~/.ssh/known_hosts) is susceptible to
man-in-the-middle attacks because it accepts the host key without verification,
and strongly recommend setting the PREVIEW_SSH_HOST_KEY secret instead;
reference the PREVIEW_SSH_HOST_KEY env var, the ssh-keyscan fallback branch, and
the known_hosts write so reviewers know exactly where to see the risk.
- Around line 54-58: The "Resolve preview host IP" step captures host_ip from
running .#resolvePreviewIp but doesn't validate it; update that step to verify
the output (host_ip) is a well-formed IP address before exporting to GITHUB_ENV
and masking: after obtaining host_ip from nix run .#resolvePreviewIp, check it
against an IPv4/IPv6 validation (e.g., a regex or use system tools like `ping -c
1`/`ip`/`grep -E` to ensure it's an IP) and fail the job with a clear error if
invalid; ensure you only call echo "::add-mask::$host_ip" and append
"HOST_IP=$host_ip" to GITHUB_ENV when the validation passes so subsequent SSH
steps receive a guaranteed valid IP.
In `@infra/default.nix`:
- Around line 62-64: The prod branch sets host_ip by running jq -r
'.outputs.reserved_ip.value' on tfState without validating it; add the same
null/empty guard used for preview so that after host_ip=$(jq -r
'.outputs.reserved_ip.value' ${tfState}) you check whether host_ip is empty or
"null" and if so log an error and exit (mirroring the preview validation) to
prevent later commands from running with an invalid reserved_ip value.
---
Nitpick comments:
In @.github/workflows/deploy-preview.yaml:
- Line 70: Add a short inline comment above the run step that invokes ./prep.sh
in the deploy-preview.yaml workflow that explains what prep.sh does (for example
"Build service binary", "Prepare deployment artifacts", or "Install dependencies
and package release assets"); reference the run step calling ./prep.sh so
maintainers can see the intent and downstream effects without inspecting the
script itself and ensure the comment briefly lists any major side effects (e.g.,
builds, artifact creation, env assumptions).
- Around line 72-78: The deployment steps "Deploy preview service" and "Deploy
preview system and service" currently can run indefinitely; add an explicit
GitHub Actions timeout by adding a timeout-minutes field to each step that runs
the nix commands (the steps named "Deploy preview service" running "nix run
.#deployPreviewService" and "Deploy preview system and service" running "nix run
.#deployPreviewAll"), choosing an appropriate minute value for your deployment
(e.g., 30–60) so long-running runs fail fast and produce clearer diagnostics.
In `@DEPLOY.md`:
- Line 276: The example in DEPLOY.md exposes API_KEY and API_SECRET on the
command line; update the example to recommend safer patterns instead: instruct
users to load secrets from a gitignored env file or secure storage (e.g., export
API_KEY=$(cat ~/.config/st0x/api_key)) before running ./scripts/smoke.sh, or
prompt for secrets interactively using a silent read (read -sp) and export them,
and show using API_URL only on the command invocation if needed; reference the
variables API_KEY, API_SECRET and the script ./scripts/smoke.sh so readers know
where to apply the change.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: b3c5924e-60da-41df-ae60-4b38b772d0ba
📒 Files selected for processing (12)
.github/workflows/deploy-preview.yamlDEPLOY.mdconfig/preview.tomldeploy.nixdocs/ops.mdflake.nixinfra/default.nixinfra/main.tfinfra/outputs.tfinfra/terraform.tfvars.exampleinfra/variables.tfos.nix
fc1a1bc to
a3c1859
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 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/deploy-preview.yaml:
- Around line 66-74: The "Commit encrypted Terraform state" step assumes a
branch checkout and fails on detached HEAD (tag/SHA runs); update that step to
detect detached HEAD (e.g., check if git symbolic-ref --short HEAD succeeds) and
when detached create a temporary branch name (using the run context like
GITHUB_REF_NAME or GITHUB_SHA) or push the current HEAD explicitly to a target
branch (e.g., push HEAD:refs/heads/<branch>) so the commit/push succeeds in
tag/SHA workflows; update the step logic around the git commit/git push commands
in the workflow to perform this detection and conditional branch creation/push.
- Line 32: Replace mutable action references with immutable commit SHAs: update
jlumbroso/free-disk-space@v1.3.1, actions/checkout@v4, and both
DeterminateSystems/* actions currently using `@main` to use their specific commit
SHAs instead of tags/branches; locate the workflow steps that reference
jlumbroso/free-disk-space, actions/checkout, DeterminateSystems/gha-download and
DeterminateSystems/gha-upload (or similarly named DeterminateSystems actions)
and replace each "@<tag|branch>" with the corresponding full 40-character commit
SHA from the upstream repo to ensure immutability.
In `@flake.nix`:
- Around line 247-263: The remote_cmd array is being forwarded as a single
unescaped string to ssh which allows label/owner (and other args) with spaces or
shell metacharacters to be re-split or injected; fix by escaping/quoting the
command before sending: build an escaped command string from remote_cmd (e.g.
via printf '%q' over "${remote_cmd[@]}" so each element is shell-escaped) and
use that escaped string in the exec ssh invocation (preserve handling of admin
via remote_cmd+=(--admin) and keep using identity and host_ip), replacing the
current exec ssh line so the remote command is safe.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 172a0ceb-e131-4788-ae72-a507cd2562e4
📒 Files selected for processing (13)
.github/workflows/deploy-preview.yamlDEPLOY.mdconfig/preview.tomldeploy.nixdocs/ops.mdflake.nixinfra/default.nixinfra/main.tfinfra/outputs.tfinfra/terraform.tfvars.exampleinfra/variables.tfkeys.nixos.nix
✅ Files skipped from review due to trivial changes (2)
- docs/ops.md
- DEPLOY.md
🚧 Files skipped from review as they are similar to previous changes (7)
- infra/outputs.tf
- infra/variables.tf
- infra/terraform.tfvars.example
- infra/default.nix
- config/preview.toml
- infra/main.tf
- deploy.nix
| runs-on: ubuntu-latest | ||
| steps: | ||
| - name: Free disk space | ||
| uses: jlumbroso/free-disk-space@v1.3.1 |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n .github/workflows/deploy-preview.yaml | sed -n '30,55p'Repository: ST0x-Technology/st0x.rest.api
Length of output: 983
🏁 Script executed:
cat -n .github/workflows/deploy-preview.yaml | grep -E "uses:"Repository: ST0x-Technology/st0x.rest.api
Length of output: 302
Pin third-party actions to immutable commit SHAs.
The workflow currently references actions using mutable tags and branches: jlumbroso/free-disk-space@v1.3.1, actions/checkout@v4, and both DeterminateSystems/* actions at @main. These can be updated by upstream maintainers without changes to this repo, which weakens supply-chain guarantees and breaks reproducibility. Replace all four action references with their full commit SHAs.
🤖 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/deploy-preview.yaml at line 32, Replace mutable action
references with immutable commit SHAs: update jlumbroso/free-disk-space@v1.3.1,
actions/checkout@v4, and both DeterminateSystems/* actions currently using `@main`
to use their specific commit SHAs instead of tags/branches; locate the workflow
steps that reference jlumbroso/free-disk-space, actions/checkout,
DeterminateSystems/gha-download and DeterminateSystems/gha-upload (or similarly
named DeterminateSystems actions) and replace each "@<tag|branch>" with the
corresponding full 40-character commit SHA from the upstream repo to ensure
immutability.
There was a problem hiding this comment.
Leaving this out of this PR intentionally. Pinning third-party Actions to immutable SHAs is valid supply-chain hardening, but the repository’s existing workflows still use tag/branch refs as well. This should be handled as a separate repo-wide policy change rather than only pinning the new preview workflow here.
There was a problem hiding this comment.
@findolor, that's a fair and consistent approach — applying the fix to only one workflow while the rest remain unpinned would create a false sense of security. I'll keep this in mind for future reviews.
Would you like me to open a follow-up GitHub issue to track the repo-wide SHA-pinning effort so it doesn't get lost?
✏️ Learnings added
Learnt from: findolor
Repo: ST0x-Technology/st0x.rest.api PR: 108
File: .github/workflows/deploy-preview.yaml:32-32
Timestamp: 2026-05-18T07:26:39.483Z
Learning: In the ST0x-Technology/st0x.rest.api repository, the team prefers not to pin individual GitHub Actions workflow steps to immutable commit SHAs on a per-PR basis. Pinning third-party action references (e.g. jlumbroso/free-disk-space, actions/checkout, DeterminateSystems/* actions) should be handled as a separate repo-wide policy change rather than flagged incrementally in individual PRs.
Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.
Merge activity
|
## Summary - add a separate preview NixOS/deploy-rs target for `api.preview.st0x.io` - add additive Terraform resources for a reusable preview droplet, volume, and reserved IP behind `preview_enabled` - add `deploy-preview-*`, `remote-preview`, `resolve-preview-ip`, and `preview-create-api-key` wrappers - add a manual GitHub Actions `Deploy Preview` workflow that deploys a selected branch/tag/SHA to preview - reset preview app/indexer DB state during preview service activation while preserving the private registry artifact - install the active service config at `/etc/st0x-rest-api/config.toml` so preview key management has a stable config path - document the preview provisioning, deploy, key creation, and GitHub workflow steps ## How to Use 1. Enable preview infrastructure in encrypted Terraform vars: ```bash nix develop -c tf-edit-vars # set preview_enabled = true nix develop -c tf-plan nix develop -c tf-apply ``` 2. Bootstrap the preview host: ```bash DEPLOY_ENV=preview nix develop -c bootstrap ``` 3. Point `api.preview.st0x.io` to: ```bash nix develop -c resolve-preview-ip ``` 4. Add/confirm GitHub secrets: - `SSH_KEY`: already used by production deploy; must be valid for the preview host too. - `PREVIEW_SSH_HOST_KEY`: optional but recommended. If absent, the workflow falls back to `ssh-keyscan`. 5. Use GitHub Actions > `Deploy Preview`: - `ref`: branch, tag, or SHA to deploy - `deploy_scope`: `service` for normal branch deploys, `all` when the preview NixOS config also changed 6. After a preview deploy, create a fresh preview key if authenticated checks are needed: ```bash nix develop -c preview-create-api-key "preview-benchmark" "arda@st0x.io" # or with admin access: nix develop -c preview-create-api-key "preview-admin" "arda@st0x.io" --admin ``` ## Validation - `nix eval .#deploy.nodes.st0x-rest-api-preview.profiles.rest-api.profilePath` - `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.services.nginx.virtualHosts --apply builtins.attrNames` - `nix eval .#nixosConfigurations.st0x-rest-api-prod.config.services.nginx.virtualHosts --apply builtins.attrNames` - `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.systemd.services.rest-api.serviceConfig.ExecStart` - `nix eval .#nixosConfigurations.st0x-rest-api-preview.config.fileSystems."/mnt/data".device` - `nix eval --impure --expr ... environment.etc."st0x-rest-api/config.toml".source` - `nix build .#deployPreviewService --no-link` - `nix build .#deployPreviewAll --no-link` - `nix build .#remotePreview --no-link` - `nix build .#previewCreateApiKey --no-link` - `nix develop -c terraform -chdir=infra fmt` - `nix develop -c terraform -chdir=infra validate` - `nix develop -c rainix-rs-static` - `git diff --check` `rainix-rs-static` passed with the existing cache dead-code warnings. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Add preview environment: deployable from the UI/CI with service-only or full-system scope; independent preview infra, host reservation, and deployment paths. * **Configuration** * New preview configuration and deployment profiles; preview-specific rate limits and data paths; tooling to target preview vs production. * **Documentation** * Full “Deploy a branch to preview” guide, updated operational/SSH instructions, and post-deploy smoke-test/runbook. <!-- review_stack_entry_start --> [](https://app.coderabbit.ai/change-stack/ST0x-Technology/st0x.rest.api/pull/108?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack) <!-- review_stack_entry_end --> <!-- end of auto-generated comment: release notes by coderabbit.ai -->
db8d0c8 to
0875f73
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (2)
.github/workflows/deploy-preview.yaml (1)
34-38: 💤 Low valueConsider setting
persist-credentials: falseto limit credential exposure.The checkout action persists credentials by default, which leaves a token available in the repository config for subsequent steps. While the workflow permissions are scoped, explicitly disabling credential persistence is a defense-in-depth measure.
🛡️ Suggested hardening
- uses: actions/checkout@v4 with: ref: ${{ inputs.ref || github.ref }} submodules: recursive fetch-depth: 0 + persist-credentials: false🤖 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/deploy-preview.yaml around lines 34 - 38, The checkout step currently leaves credentials persisted by default; update the actions/checkout@v4 invocation to add persist-credentials: false so the checkout action does not leave the GITHUB_TOKEN in the repo config (modify the block where actions/checkout@v4 is used and add the persist-credentials: false key alongside ref, submodules, and fetch-depth).deploy.nix (1)
18-24: 🏗️ Heavy liftConsider consolidating preview DB reset outside of service activation profiles.
The reset logic in
mkServiceProfile(lines 18–24) is included in every service profile. If multiple services are enabled and share the same data directory, each service's activation will deletest0x.dbandraindex.dbindependently. Currently onlyrest-apiis enabled, so there is no issue; however, if more services are added later, a second service activation would delete the database after the first service has already restarted. To future-proof, move the reset to a single operation before all service restarts (or use a per-service data directory if services should not share database files).🤖 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 `@deploy.nix` around lines 18 - 24, The reset logic inside mkServiceProfile (the resetCommands array that removes ${dataDir}/st0x.db and ${dataDir}/raindex.db) should be moved out of the per-service activation so it runs once before all services are restarted; update the deploy flow so that activate.custom calls (or the loop that invokes mkServiceProfile) no longer include resetCommands and instead run a single conditional step that checks resetState and deletes the preview DB files once (using the same ${dataDir}, markerFile and name context) before iterating services; alternatively, if services must not share the DB, change the per-service dataDir used by mkServiceProfile so each service has its own DB path instead of the shared files.
🤖 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/deploy-preview.yaml:
- Around line 40-54: The workflow is vulnerable because the GitHub expression
${{ inputs.ref }} is injected directly into shell commands when setting
deploy_branch and used in error messages and git operations; instead, write the
input into a safe environment variable (e.g., DEPLOY_REF) via GITHUB_ENV using
the GitHub expression, then use that environment variable inside the shell (read
into deploy_branch, check it, and pass it to git) so no untrusted string is
interpreted by the shell; update the uses of ${{ inputs.ref }} in the
deploy_branch assignment, error messages, and git fetch/checkout to reference
the environment variable (deploy_branch/DEPLOY_REF) and keep the rest of the
validation logic (empty/ref prefix check, git ls-remote, fetch, checkout, and
echo "DEPLOY_BRANCH=...") unchanged.
---
Nitpick comments:
In @.github/workflows/deploy-preview.yaml:
- Around line 34-38: The checkout step currently leaves credentials persisted by
default; update the actions/checkout@v4 invocation to add persist-credentials:
false so the checkout action does not leave the GITHUB_TOKEN in the repo config
(modify the block where actions/checkout@v4 is used and add the
persist-credentials: false key alongside ref, submodules, and fetch-depth).
In `@deploy.nix`:
- Around line 18-24: The reset logic inside mkServiceProfile (the resetCommands
array that removes ${dataDir}/st0x.db and ${dataDir}/raindex.db) should be moved
out of the per-service activation so it runs once before all services are
restarted; update the deploy flow so that activate.custom calls (or the loop
that invokes mkServiceProfile) no longer include resetCommands and instead run a
single conditional step that checks resetState and deletes the preview DB files
once (using the same ${dataDir}, markerFile and name context) before iterating
services; alternatively, if services must not share the DB, change the
per-service dataDir used by mkServiceProfile so each service has its own DB path
instead of the shared files.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: 4f985ef6-13a1-46c8-bb5e-b9dec30317e1
📒 Files selected for processing (13)
.github/workflows/deploy-preview.yamlDEPLOY.mdconfig/preview.tomldeploy.nixdocs/ops.mdflake.nixinfra/default.nixinfra/main.tfinfra/outputs.tfinfra/terraform.tfvars.exampleinfra/variables.tfkeys.nixos.nix
✅ Files skipped from review due to trivial changes (2)
- docs/ops.md
- DEPLOY.md
| - name: Verify deploy branch | ||
| run: | | ||
| deploy_branch="${{ inputs.ref }}" | ||
| deploy_branch="${deploy_branch#refs/heads/}" | ||
| if [ -z "$deploy_branch" ] || [[ "$deploy_branch" == refs/* ]]; then | ||
| echo "Deploy Preview requires a branch ref, got '${{ inputs.ref }}'" >&2 | ||
| exit 1 | ||
| fi | ||
| if ! git ls-remote --exit-code --heads origin "$deploy_branch" >/dev/null; then | ||
| echo "Deploy Preview requires an existing remote branch, got '${{ inputs.ref }}'" >&2 | ||
| exit 1 | ||
| fi | ||
| git fetch origin "refs/heads/$deploy_branch:refs/remotes/origin/$deploy_branch" | ||
| git checkout -B "$deploy_branch" "origin/$deploy_branch" | ||
| echo "DEPLOY_BRANCH=$deploy_branch" >> "$GITHUB_ENV" |
There was a problem hiding this comment.
Critical: Template injection vulnerability in shell script.
The ${{ inputs.ref }} expression is expanded directly into shell context on lines 42, 45, and 49. Since workflow_dispatch inputs are user-controlled, a malicious actor with workflow trigger access could inject arbitrary shell commands via a crafted ref value (e.g., "; malicious_command; #).
Use an environment variable to safely pass the input to the shell:
🔐 Proposed fix to prevent injection
- name: Verify deploy branch
+ env:
+ INPUT_REF: ${{ inputs.ref }}
run: |
- deploy_branch="${{ inputs.ref }}"
+ deploy_branch="$INPUT_REF"
deploy_branch="${deploy_branch#refs/heads/}"
if [ -z "$deploy_branch" ] || [[ "$deploy_branch" == refs/* ]]; then
- echo "Deploy Preview requires a branch ref, got '${{ inputs.ref }}'" >&2
+ echo "Deploy Preview requires a branch ref, got '$INPUT_REF'" >&2
exit 1
fi
if ! git ls-remote --exit-code --heads origin "$deploy_branch" >/dev/null; then
- echo "Deploy Preview requires an existing remote branch, got '${{ inputs.ref }}'" >&2
+ echo "Deploy Preview requires an existing remote branch, got '$INPUT_REF'" >&2
exit 1
fi
git fetch origin "refs/heads/$deploy_branch:refs/remotes/origin/$deploy_branch"
git checkout -B "$deploy_branch" "origin/$deploy_branch"
echo "DEPLOY_BRANCH=$deploy_branch" >> "$GITHUB_ENV"🧰 Tools
🪛 zizmor (1.25.2)
[error] 42-42: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 45-45: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
[error] 49-49: code injection via template expansion (template-injection): may expand into attacker-controllable code
(template-injection)
🤖 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/deploy-preview.yaml around lines 40 - 54, The workflow is
vulnerable because the GitHub expression ${{ inputs.ref }} is injected directly
into shell commands when setting deploy_branch and used in error messages and
git operations; instead, write the input into a safe environment variable (e.g.,
DEPLOY_REF) via GITHUB_ENV using the GitHub expression, then use that
environment variable inside the shell (read into deploy_branch, check it, and
pass it to git) so no untrusted string is interpreted by the shell; update the
uses of ${{ inputs.ref }} in the deploy_branch assignment, error messages, and
git fetch/checkout to reference the environment variable
(deploy_branch/DEPLOY_REF) and keep the rest of the validation logic (empty/ref
prefix check, git ls-remote, fetch, checkout, and echo "DEPLOY_BRANCH=...")
unchanged.

Summary
api.preview.st0x.iopreview_enableddeploy-preview-*,remote-preview,resolve-preview-ip, andpreview-create-api-keywrappersDeploy Previewworkflow that deploys a selected branch/tag/SHA to preview/etc/st0x-rest-api/config.tomlso preview key management has a stable config pathHow to Use
nix develop -c tf-edit-vars # set preview_enabled = true nix develop -c tf-plan nix develop -c tf-applyapi.preview.st0x.ioto:SSH_KEY: already used by production deploy; must be valid for the preview host too.PREVIEW_SSH_HOST_KEY: optional but recommended. If absent, the workflow falls back tossh-keyscan.Deploy Preview:ref: branch, tag, or SHA to deploydeploy_scope:servicefor normal branch deploys,allwhen the preview NixOS config also changedValidation
nix eval .#deploy.nodes.st0x-rest-api-preview.profiles.rest-api.profilePathnix eval .#nixosConfigurations.st0x-rest-api-preview.config.services.nginx.virtualHosts --apply builtins.attrNamesnix eval .#nixosConfigurations.st0x-rest-api-prod.config.services.nginx.virtualHosts --apply builtins.attrNamesnix eval .#nixosConfigurations.st0x-rest-api-preview.config.systemd.services.rest-api.serviceConfig.ExecStartnix eval .#nixosConfigurations.st0x-rest-api-preview.config.fileSystems."/mnt/data".devicenix eval --impure --expr ... environment.etc."st0x-rest-api/config.toml".sourcenix build .#deployPreviewService --no-linknix build .#deployPreviewAll --no-linknix build .#remotePreview --no-linknix build .#previewCreateApiKey --no-linknix develop -c terraform -chdir=infra fmtnix develop -c terraform -chdir=infra validatenix develop -c rainix-rs-staticgit diff --checkrainix-rs-staticpassed with the existing cache dead-code warnings.Summary by CodeRabbit
New Features
Configuration
Documentation