From 450ff5e58a92f37127800954d751ee468c5b395c Mon Sep 17 00:00:00 2001 From: Mike Odnis Date: Sun, 10 May 2026 09:10:42 -0400 Subject: [PATCH] fix(api-docs/rust): close template-injection on ref + pin cargo-doc-md MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- .../source-repo-templates/api-docs.rust.yml | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/automation/source-repo-templates/api-docs.rust.yml b/automation/source-repo-templates/api-docs.rust.yml index 27096bb5..e1b772be 100644 --- a/automation/source-repo-templates/api-docs.rust.yml +++ b/automation/source-repo-templates/api-docs.rust.yml @@ -42,8 +42,17 @@ jobs: # Single source of truth for the ref this run documents. # workflow_dispatch can pass an alternate ref via inputs.ref; # fall back to github.ref_name (already stripped of refs/...). + # + # The ref is routed through env: instead of being inlined via + # ${{ }}. Inlining at template-expansion time would interpolate + # the raw string into the shell literal, so a tag name with a + # single quote (Git allows it) could break out of the quoted + # context. Env indirection keeps user-controlled data on the + # variable side of the shell parser, where it cannot escape. + env: + REF_RAW: ${{ inputs.ref || github.ref_name }} run: | - raw='${{ inputs.ref || github.ref_name }}' + raw="$REF_RAW" raw="${raw#refs/tags/}" raw="${raw#refs/heads/}" slug="${raw//\//-}" @@ -68,9 +77,15 @@ jobs: # `cargo install` rebuilds quickly when its version doesn't # change between runs because the runner's cache reuses the # cargo registry. + # + # 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. run: | rustup toolchain install nightly --profile minimal --no-self-update - cargo install cargo-doc-md --locked + cargo install --locked --version 0.11.0 cargo-doc-md - name: Generate rustdoc markdown # Run cargo-doc-md across the workspace. Output goes to