Skip to content

fix(api-docs/rust): close template-injection on ref + pin cargo-doc-md#70

Merged
WomB0ComB0 merged 1 commit intomainfrom
fix/api-docs-rust-template-hardening
May 10, 2026
Merged

fix(api-docs/rust): close template-injection on ref + pin cargo-doc-md#70
WomB0ComB0 merged 1 commit intomainfrom
fix/api-docs-rust-template-hardening

Conversation

@WomB0ComB0
Copy link
Copy Markdown
Member

Summary

Two hardening fixes for automation/source-repo-templates/api-docs.rust.yml. Both surfaced during a security review of resq-software/crates/.github/workflows/api-docs.yml (the synced copy of this template).

H-1 — Template-injection on inputs.ref / github.ref_name

Before:

run: |
  raw='${{ inputs.ref || github.ref_name }}'

The ${{ }} interpolation happens at workflow-template-expansion time, before shell parsing. A tag name containing a single quote (Git allows it — only space, ~, ^, :, ?, *, [, \, .. are forbidden ref chars) would terminate the literal and inject shell. The runner has the source-tree checkout and DOCS_REPO_PR_TOKEN in env at that point.

After:

env:
  REF_RAW: ${{ inputs.ref || github.ref_name }}
run: |
  raw="\$REF_RAW"

Env indirection routes the value through the runner's env layer where shell metacharacters survive intact and cannot escape the variable boundary. This is the canonical zizmor template-injection remediation.

H-2 — cargo install cargo-doc-md was unpinned

Before:

cargo install cargo-doc-md --locked

--locked only locks the resolution graph for transitive dependencies. It does not pin cargo-doc-md itself. Any future maintainer takeover or compromised crates.io credentials silently lands new code on the runner — which has read on the source tree and DOCS_REPO_PR_TOKEN in env.

After:

cargo install --locked --version 0.11.0 cargo-doc-md

Pinned to the current latest stable. Bumping requires reviewing the upstream release diff.

Out of scope (follow-ups)

  • H-1 is systemic. Same pattern exists in api-docs.cpp.yml, api-docs.dotnet.yml, api-docs.python.yml, api-docs.typescript.yml (lines 65-75 in each). Each should get its own focused PR. Not bundled here to keep the diff reviewable per-language.
  • M-1 — none of these templates use step-security/harden-runner even though the org's reusable rust-ci workflow does. Adding it consistently across the templates is a separate, broader hardening change worth its own discussion.

Effect on downstream

  • automation/sync-templates.sh will produce a sync PR in resq-software/crates (and only there — Rust template only ships there) on its next run. That sync PR will bring this fix into .github/workflows/api-docs.yml automatically.
  • No behavior change to documentation generation. DOCS_REF_NAME / DOCS_REF_SLUG env outputs are byte-identical for the same input ref.

Test plan

  • actionlint . (run locally if you have it)
  • Visual diff: only 17 added lines, all comments + the env-block + 1 changed raw= assignment + 1 changed cargo install line.
  • Once merged, kick the rust template sync (manual automation/sync-templates.sh or wait for the next cron) and confirm the resulting crates PR contains the expected diff.
  • On resq-software/crates, fire workflow_dispatch for the synced workflow with a benign tag name to confirm no behavior regression.

H-1: route inputs.ref / github.ref_name through env: instead of
inlining via ${{ }}. Tag names can legally contain single quotes,
which would break out of the shell literal at template-expansion
time and run arbitrary code with the runner's checkout + secret
context. Env indirection keeps the value on the variable side of
the shell parser.

H-2: pin cargo-doc-md to 0.11.0. `cargo install --locked` only
locks transitive resolution — it does not pin the cargo-doc-md
package itself, so a future maintainer takeover or compromised
crates.io credentials would land arbitrary code in the runner.
The runner has read access to the source tree and DOCS_REPO_PR_TOKEN
in env, so this is a real supply-chain surface.

The same H-1 pattern exists in the sister templates (cpp, dotnet,
python, typescript) and should be fixed in follow-up PRs. They
were not changed here to keep this PR focused.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 10, 2026

Warning

Rate limit exceeded

@WomB0ComB0 has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 39 minutes and 11 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 441f75d5-1c09-4852-bffa-3d94fbae1c07

📥 Commits

Reviewing files that changed from the base of the PR and between 5b07bb1 and 450ff5e.

📒 Files selected for processing (1)
  • automation/source-repo-templates/api-docs.rust.yml
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/api-docs-rust-template-hardening

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions github-actions Bot added the area:content MDX/MD documentation content label May 10, 2026
@WomB0ComB0 WomB0ComB0 merged commit e6909a5 into main May 10, 2026
13 checks passed
@WomB0ComB0 WomB0ComB0 deleted the fix/api-docs-rust-template-hardening branch May 10, 2026 13:12
Copy link
Copy Markdown

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request improves the security of the Rust API documentation workflow by using environment variables to prevent shell injection and pinning the version of the cargo-doc-md tool. Review feedback identifies a potential environment variable injection vulnerability when writing user-controlled input to $GITHUB_ENV and suggests correcting an inaccuracy in the added comments regarding the scope of secret availability.

Comment on lines +52 to +53
env:
REF_RAW: ${{ inputs.ref || github.ref_name }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

security-high high

While routing the ref through an environment variable successfully prevents shell injection in the run block, the subsequent logic that writes this value to $GITHUB_ENV (lines 61-63) remains vulnerable to environment variable injection. Since inputs.ref is user-controlled and can contain newlines, a malicious input could be used to set arbitrary environment variables for subsequent steps. Consider using a unique delimiter for the $GITHUB_ENV assignment or validating the input format.

Comment on lines +81 to +85
# The version is pinned because this binary runs with the
# repo's checkout in scope and the DOCS_REPO_PR_TOKEN secret in
# env. `--locked` alone only locks transitive resolution; it
# does NOT pin the cargo-doc-md package itself. Bumping
# requires reviewing the upstream release diff.
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

The comment states that DOCS_REPO_PR_TOKEN is in the environment (env), but this secret is only passed as an input to specific actions in later steps. It is not available in the environment of this cargo install step. While pinning the version is a good security practice, the comment should be updated for accuracy to avoid misleading future maintainers about secret exposure.

        # The version is pinned because this binary runs with the
        # repo's checkout in scope. Pinning prevents supply-chain
        # attacks where a compromised package could attempt to
        # exfiltrate secrets (like DOCS_REPO_PR_TOKEN) in later steps.
        # --locked alone does NOT pin the package itself. Bumping
        # requires reviewing the upstream release diff.

WomB0ComB0 added a commit that referenced this pull request May 10, 2026
…python/typescript (#71)

Mirrors the rust-template fix (#70) across the remaining four sister
templates. Each has the same `inputs.ref || github.ref_name` inlined
into a single-quoted shell literal at template-expansion time. Tag
names containing a single quote (Git permits them) would break out of
the literal and run arbitrary shell with the runner's checkout and
DOCS_REPO_PR_TOKEN in scope.

Routes the value through a step-level env: REF_RAW: instead. Each
template's existing slug-substitution rules are preserved (cpp uses
\@/-, typescript uses \@/<empty>, dotnet/python only normalize /);
only the source of $raw changes from interpolated literal to env var.

DOCS_REF_NAME / DOCS_REF_SLUG outputs are byte-identical for every
input ref. Behavior unchanged.

Co-authored-by: Mike Odnis <mikeodnis3242004@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:content MDX/MD documentation content

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant