Back to skill

Security audit

rust-dev

Security checks for vulnerabilities and agentic risk

Overview

This is a Rust development guide with no hidden executable files, but it includes copyable setup and release instructions that can run remote code, install a persistent global build wrapper, and expose publishing credentials.

Review this skill before relying on its setup and release snippets. Prefer official package-manager installs or separately downloaded and verified installers over `curl | sh`; pin kache and GitHub Actions to reviewed versions or SHAs; avoid global compiler-wrapper daemons unless you intentionally want them; and use least-privilege, short-lived release credentials such as OIDC Trusted Publishing where possible.

Vulnerability Patterns
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
  • System PersistenceInstalls backdoors, hooks, services, or scheduled tasks that survive the run
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
Findings (4)

T03 · Remote Payload Retrieval and Execution

Error
Location
SKILL.md:31
Finding
Remote Rust Installer Is Executed Directly Without Integrity Verification## Vulnerability Details **File Location**: `SKILL.md`, line 31 **Vulnerability Type**: Remote payload retrieval and immediate shell execution **Risk Level**: Critical ### Vulnerable Code ```bash # 1. Install the toolchain (rustup is the toolchain manager) curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh ``` ### Technical Analysis The command downloads a mutable response from an external URL and immediately sends it to a shell. There is no opportunity to inspect the downloaded script, pin an immutable version, or verify a cryptographic signature or expected checksum before execution. The HTTPS and TLS restrictions protect the transport connection, but they do not independently establish the integrity of the script served by the remote endpoint. Compromise of the upstream service, its deployment infrastructure, or a trusted TLS component could therefore change the effective payload after the Skill has been audited. Installing Rust is relevant to the Skill's declared purpose, but executing unverified remote content is not the minimum privilege or safest installation method necessary to achieve that purpose. ### Attack Path 1. An attacker compromises the installer endpoint, its hosting infrastructure, or another trusted delivery component. 2. The endpoint returns a modified shell script. 3. A user follows the Skill's installation command. 4. `curl` retrieves the modified response and pipes it directly to `sh`. 5. The payload executes with all privileges available to the invoking user. 6. The payload may modify user files, install additional programs, collect accessible data, or establish persistence. ### Impact Assessment Successful exploitation provides arbitrary command execution under the invoking user's account. The payload could access source repositories and user-readable credentials, modify shell or development configuration, tamper with future builds, or install persistent user-level services ...[truncated 98 chars]
Remediation
## Remediation Suggestions - Do not pipe a network response directly into a shell. - Prefer an operating-system package manager or another installation channel with package signature verification. - If the upstream installer must be used, download it to a local file first. - Pin a documented installer or toolchain version instead of implicitly accepting the current remote response. - Verify an official cryptographic signature or a checksum obtained through an independent trusted channel. - Allow the user to inspect the downloaded script before executing it. - Run installation without administrative privileges unless a specific system-wide installation explicitly requires them. A safer workflow should separate download, verification, inspection, and execution into distinct steps.

T06 · System Persistence

Error
Location
references/dev-environment.md:26
Finding
Mutable Third-Party Build Wrapper Is Installed Globally and Persisted as a Daemon## Vulnerability Details **File Location**: `references/dev-environment.md`, lines 26-29; supporting persistence instructions at lines 33 and 57 **Vulnerability Type**: Mutable dependency installation, compiler-tool wrapping, and cross-session persistence **Risk Level**: High ### Vulnerable Code ```sh # Install (mise, or brew on macOS) mise use -g github:kunobi-ninja/kache@latest brew install kunobi-ninja/kunobi/kache kache init # wires RUSTC_WRAPPER into ~/.cargo/config.toml, installs + starts the daemon kache doctor # verify ``` The document further states: ```text `kache init` is idempotent - re-run it any time to repair the setup. To wire it by hand instead, set `rustc-wrapper = "kache"` under `[build]` in `$CARGO_HOME/config.toml`. ``` ```text Re-run `kache init` (or `kache daemon install`) after an upgrade. ``` ### Technical Analysis The `mise` command installs from the mutable `@latest` reference rather than an immutable release and verified digest. The subsequent initialization modifies global Cargo configuration so future compiler invocations pass through the installed wrapper. It also installs and starts a launchd or systemd daemon that survives the immediate setup operation. A compiler wrapper is in a high-trust position: it receives build command lines, paths, environment context, source-related inputs, and control over compiler execution and resulting artifacts. Registering it globally means the trust decision affects unrelated future Rust projects rather than only the project for which caching was requested. The cache is a legitimate optional development tool, and the document discloses the daemon and configuration changes. Nevertheless, the combination of a mutable source, global tool interception, and persistent service materially expands the supply-chain impact of an upstream compromise. ### Attack Path 1. An attacker compromises the third-party repository, package publicati ...[truncated 1144 chars]
Remediation
## Remediation Suggestions - Replace `@latest` with a specific audited release. - Verify the downloaded executable using an official signature or pinned checksum. - Prefer project-local Cargo configuration instead of modifying the user's global Cargo configuration. - Do not install or start a daemon automatically as part of ordinary setup; require a separate, explicit opt-in. - Explain exactly which launchd or systemd files and Cargo configuration entries are created. - Provide complete disable and removal instructions for the daemon, wrapper configuration, cache, and executable. - Recommend testing the wrapper in a restricted environment before making it global. - Limit remote-cache credentials to a dedicated bucket and prefix with only the required read/write permissions. - Where supported, pin package-manager formula provenance or use reproducible release artifacts. The default guidance should begin with non-persistent, project-scoped caching and present global daemon installation only as an optional advanced configuration.

T08 · Insecure Dependencies

Error
Location
references/releasing.md:51
Finding
Long-Lived Release Credentials Are Passed to a Mutable Third-Party GitHub Action## Vulnerability Details **File Location**: `references/releasing.md`, lines 51-56 **Vulnerability Type**: CI supply-chain exposure of repository and package-publishing credentials **Risk Level**: High ### Vulnerable Code ```yaml - uses: release-plz/action@v0.5 # NOW publish crates.io + cut the tag id: release-plz with: { command: release } env: GITHUB_TOKEN: ${{ secrets.RELEASE_TOKEN }} # a PAT, not the workflow token - see below CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }} ``` ### Technical Analysis The workflow invokes a third-party Action through the mutable `v0.5` tag and makes both a GitHub personal access token and a crates.io registry token available to it. GitHub Actions execute as code inside the job, so the Action can read environment variables supplied to that step. A version tag is not an immutable trust anchor. If the tag is moved, the upstream repository is compromised, or its release process is subverted, different code can execute without a corresponding change in the consuming repository. Because the Action receives two high-value credentials, an upstream compromise can immediately cross repository and package-registry trust boundaries. The document later recommends crates.io Trusted Publishing, which is safer because it removes the stored long-lived registry secret. However, the earlier worked pipeline remains directly copyable and exposes the long-lived credentials to a mutable dependency. ### Attack Path 1. An attacker compromises the Action repository, a maintainer account, or the process controlling the `v0.5` tag. 2. The mutable tag is changed to reference malicious Action code. 3. The release workflow runs on an event authorized to access repository secrets. 4. GitHub downloads and executes the malicious Action. 5. The Action reads `RELEASE_TOKEN` and `CARGO_REGISTRY_TOKEN` from its environment. 6. The attacker exfiltrates or directly uses the token ...[truncated 604 chars]
Remediation
## Remediation Suggestions - Pin `release-plz/action` to a reviewed full commit SHA rather than a mutable version tag. - Use crates.io Trusted Publishing with OIDC so no long-lived registry token is stored in repository secrets. - Use a narrowly scoped, short-lived GitHub App token instead of a broad personal access token where possible. - Restrict token permissions to the exact repository and operations needed for release automation. - Separate build, verification, and publication jobs, and place publication in a protected GitHub environment requiring approval. - Prevent workflows triggered by untrusted forks or pull requests from accessing release credentials. - Review Action updates before changing the pinned commit. - Add provenance attestations and verify that uploaded artifacts correspond to the reviewed source commit. The safer OIDC example should replace the long-lived-token example as the primary recommended pipeline rather than appearing only as a later migration option.

T08 · Insecure Dependencies

Warning
Location
references/dev-environment.md:114
Finding
CI Examples Execute Multiple GitHub Actions Through Mutable Version Tags## Vulnerability Details **File Location**: `references/dev-environment.md`, lines 114-115 and 138; `references/releasing.md`, lines 45, 189-190 **Vulnerability Type**: Unpinned CI dependencies with access to repository, cache, and publishing context **Risk Level**: Medium ### Vulnerable Code ```yaml - uses: actions/checkout@v7 - uses: actions-rust-lang/setup-rust-toolchain@v1 ``` ```yaml - uses: kunobi-ninja/kache-action@v1 ``` The release guidance additionally uses: ```yaml - uses: actions/checkout@v7 - uses: rust-lang/crates-io-auth-action@v1 id: auth - run: cargo publish env: CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }} ``` ### Technical Analysis Major-version GitHub Action tags are mutable references. They are convenient for receiving updates, but they allow the code executed by an unchanged workflow file to change over time. Each Action runs within the security context of its job and can access the workspace, job token permissions, environment variables, caches, and outputs available to that step. The risk is especially significant for the cache Action and crates.io authentication Action. A compromised cache Action could inspect or poison build artifacts, while compromised authentication logic could misuse a short-lived publishing token during its validity period. OIDC reduces the duration of credential exposure but does not eliminate the need to authenticate the Action implementation itself. There is no evidence that the listed Actions are currently malicious. The vulnerability is the lack of immutable pinning at sensitive CI trust boundaries. ### Attack Path 1. An attacker compromises an Action maintainer account, repository, release process, or mutable major-version tag. 2. A consuming workflow continues to reference the same tag and therefore shows no local workflow change. 3. A later CI run downloads the altered Action implementation. 4. The altered code executes wit ...[truncated 707 chars]
Remediation
## Remediation Suggestions - Pin every GitHub Action to a reviewed full commit SHA. - Retain a human-readable comment showing the corresponding release version. - Use an automated dependency updater to propose SHA changes through reviewed pull requests. - Configure explicit job-level `permissions` and grant only the capabilities each job needs. - Isolate publication from ordinary build and test jobs. - Use protected environments and approval gates for package publication. - Avoid exposing secrets or OIDC token permissions to steps that do not require them. - Treat cache contents as untrusted inputs and ensure release artifacts are rebuilt or verified before publication. - Generate and verify artifact provenance so consumers can associate releases with a specific source commit.
Vulnerability Patterns
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
Findings (15)

Anti-Refusal Statement

High
Category
Anti-Refusal
Content
### Deprecated

- references/crate-shortlist.md: `serde_yaml` and `bincode` were listed as plain "other formats" with no caveat. `serde_yaml` is archived at `0.9.34+deprecated` with no official successor; `bincode` 3.0.0 is a tombstone release whose entire `src/lib.rs` is `compile_error!("https://xkcd.com/2347/")`, so `bincode = "3"` fails to compile rather than failing at runtime - 2.0.1 is the last usable version.

### Security
Confidence
80% confidence
Finding
Skill instructs the agent to omit warnings, disclaimers, or ethical commentary. Stripping safety caveats hides risk from the user and is a common jailbreak preamble.

Chaining Abuse

High
Category
Tool Misuse
Content
```bash
# 1. Install the toolchain (rustup is the toolchain manager)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. Confirm components (rustfmt and clippy ship with stable, rust-src enables IDE features)
rustup component add rustfmt clippy rust-src
Confidence
99% confidence
Finding
The `| sh` construct is the dangerous sink that turns a network fetch into immediate code execution. In a skill meant to guide development setup, users may copy-paste commands without scrutiny, so this pattern materially increases the chance of arbitrary command execution if the upstream script is malicious or tampered with.

Credential Access

High
Category
Privilege Escalation
Content
| Count hit rate lies | Judge by **cost-weighted** hit rate (`kache stats`, `kache report`). Cheap leaf crates hitting while the expensive spine misses every time still reads as a healthy-looking 60% by count. |
| `cache_executables` is on by default on Linux and macOS | Only Windows still defaults it to `false` (its `.pdb` path keeps debug info outside the binary). If you read older advice telling you to turn this on, it is already on. dylib/cdylib/proc-macro are always cached and unaffected by the flag either way. |
| `kache sync --push` filters to **workspace members** | Seeding an existing store to S3 from your project directory silently uploads almost nothing - the push filter is `cargo metadata --no-deps`. Run it from a directory with **no** `Cargo.toml` to push everything. Push also does a full unfiltered LIST of the prefix, which is a real cost on a large bucket. Since 0.15.0 `kache sync` exits non-zero if any transfer fails; pass `--allow-partial` when best-effort really is what you want. |
| An auto-started daemon does **not** inherit your shell environment | `KACHE_S3_*` credentials exported from your shell profile leave the launchd/systemd daemon with no credentials at all. Put the remote in the watched `[cache.remote]` config block, use an AWS profile (`cache.remote.profile`) or `~/.aws/credentials`, or start the daemon yourself from the intended environment with `kache daemon run`. |
| Set `endpoint` for any non-AWS object store | Ceph, MinIO, and R2 all need `cache.remote.endpoint` (or `KACHE_S3_ENDPOINT`) set explicitly; omit it only for AWS S3. kache always addresses path-style, so you do not need bucket-subdomain DNS to work. |
| Upgrading kache can orphan the service | The launchd plist or systemd unit keeps pointing at the deleted old binary. Re-run `kache init` (or `kache daemon install`) after an upgrade. |
| Keys encode **toolchain identity**, not machine identity | The key hashes the `rustc --version --verbose` banner (commit hash plus host t
...[truncated 25 chars]
Confidence
90% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
### Use a PAT for the release PR, not `GITHUB_TOKEN`

A pull request opened by the default `GITHUB_TOKEN` does not get CI the way a human-opened one does - GitHub gates that to prevent recursive workflow runs. The current behavior is that the resulting `pull_request` runs are created in an **approval-required** state rather than never existing at all, so the symptom is a release PR sitting with no green checks until somebody clicks through, which is the same practical failure: you merge a version bump nothing has tested. Give the `release-pr` step a personal access token (or a GitHub App token) instead.

### `[profile.dist]`: pay for optimization only at release
Confidence
70% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

External Transmission

Medium
Category
Data Exfiltration
Content
.build()?;

    let repo: Repo = client
        .get("https://api.github.com/repos/rust-lang/rust")
        .send()
        .await?
        .json()
Confidence
50% confidence
Finding
Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Session Persistence

Medium
Category
Rogue Agent
Content
.route("/health", get(health))
        .route("/users/{id}", get(|axum::extract::Path(id): axum::extract::Path<u64>| async move { format!("user {id}") }));

    let listener = tokio::net::TcpListener::bind("0.0.0.0:3000").await?;
    axum::serve(listener, app).await?;
    Ok(())
}
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
| `kache sync --push` filters to **workspace members** | Seeding an existing store to S3 from your project directory silently uploads almost nothing - the push filter is `cargo metadata --no-deps`. Run it from a directory with **no** `Cargo.toml` to push everything. Push also does a full unfiltered LIST of the prefix, which is a real cost on a large bucket. Since 0.15.0 `kache sync` exits non-zero if any transfer fails; pass `--allow-partial` when best-effort really is what you want. |
| An auto-started daemon does **not** inherit your shell environment | `KACHE_S3_*` credentials exported from your shell profile leave the launchd/systemd daemon with no credentials at all. Put the remote in the watched `[cache.remote]` config block, use an AWS profile (`cache.remote.profile`) or `~/.aws/credentials`, or start the daemon yourself from the intended environment with `kache daemon run`. |
| Set `endpoint` for any non-AWS object store | Ceph, MinIO, and R2 all need `cache.remote.endpoint` (or `KACHE_S3_ENDPOINT`) set explicitly; omit it only for AWS S3. kache always addresses path-style, so you do not need bucket-subdomain DNS to work. |
| Upgrading kache can orphan the service | The launchd plist or systemd unit keeps pointing at the deleted old binary. Re-run `kache init` (or `kache daemon install`) after an upgrade. |
| Keys encode **toolchain identity**, not machine identity | The key hashes the `rustc --version --verbose` banner (commit hash plus host triple), plus the linker's `--version` for outputs that actually link. Machines can share one bucket prefix safely - objects cannot collide - but only *matching toolchains* ever hit each other. Think **prefix per toolchain**, not per machine. Absolute build and checkout paths are deliberately normalized out, which is what makes a different clone path still hit. |
| `key_salt` covers what the key cannot see | A glibc, linker, or Nix-store change alters compiled output while leaving every version banner unchanged, so the 
...[truncated 25 chars]
Confidence
80% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Session Persistence

Medium
Category
Rogue Agent
Content
| `kache sync --push` filters to **workspace members** | Seeding an existing store to S3 from your project directory silently uploads almost nothing - the push filter is `cargo metadata --no-deps`. Run it from a directory with **no** `Cargo.toml` to push everything. Push also does a full unfiltered LIST of the prefix, which is a real cost on a large bucket. Since 0.15.0 `kache sync` exits non-zero if any transfer fails; pass `--allow-partial` when best-effort really is what you want. |
| An auto-started daemon does **not** inherit your shell environment | `KACHE_S3_*` credentials exported from your shell profile leave the launchd/systemd daemon with no credentials at all. Put the remote in the watched `[cache.remote]` config block, use an AWS profile (`cache.remote.profile`) or `~/.aws/credentials`, or start the daemon yourself from the intended environment with `kache daemon run`. |
| Set `endpoint` for any non-AWS object store | Ceph, MinIO, and R2 all need `cache.remote.endpoint` (or `KACHE_S3_ENDPOINT`) set explicitly; omit it only for AWS S3. kache always addresses path-style, so you do not need bucket-subdomain DNS to work. |
| Upgrading kache can orphan the service | The launchd plist or systemd unit keeps pointing at the deleted old binary. Re-run `kache init` (or `kache daemon install`) after an upgrade. |
| Keys encode **toolchain identity**, not machine identity | The key hashes the `rustc --version --verbose` banner (commit hash plus host triple), plus the linker's `--version` for outputs that actually link. Machines can share one bucket prefix safely - objects cannot collide - but only *matching toolchains* ever hit each other. Think **prefix per toolchain**, not per machine. Absolute build and checkout paths are deliberately normalized out, which is what makes a different clone path still hit. |
| `key_salt` covers what the key cannot see | A glibc, linker, or Nix-store change alters compiled output while leaving every version banner unchanged, so the 
...[truncated 25 chars]
Confidence
75% confidence
Finding
Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Vague Triggers

Low
Confidence
84% confidence
Finding
This markdown file explicitly says the skill dropped explicit trigger-keyword enumeration because 'semantic matching makes it redundant.' That suggests activation may rely on broader semantic matching without documenting concrete trigger boundaries here, which can increase the risk of unintended invocation.

Scope Creep

Low
Category
Excessive Agency
Content
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.

"Object" form shall mean any form resulting from mechanical transformation or
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Scope Creep

Low
Category
Excessive Agency
Content
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.

"Object" form shall mean any form resulting from mechanical transformation or
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Scope Creep

Low
Category
Excessive Agency
Content
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.

"Object" form shall mean any form resulting from mechanical transformation or
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

Scope Creep

Low
Category
Excessive Agency
Content
permissions granted by this License.

"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation source, and
configuration files.

"Object" form shall mean any form resulting from mechanical transformation or
Confidence
70% confidence
Finding
Skill's behavior or capabilities extend beyond its stated purpose. Scope creep allows an agent to perform actions unrelated to its documented functionality, increasing the attack surface.

External Script Fetching

Low
Category
Supply Chain
Content
```bash
# 1. Install the toolchain (rustup is the toolchain manager)
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh

# 2. Confirm components (rustfmt and clippy ship with stable, rust-src enables IDE features)
rustup component add rustfmt clippy rust-src
Confidence
96% confidence
Finding
The skill instructs users to fetch and immediately execute a remote script with `curl ... | sh`. Even from an official domain, this bypasses review, integrity verification, and change control: if the remote content is compromised, intercepted upstream, or unexpectedly changed, the user executes arbitrary shell code on their machine.

Missing User Warnings

Low
Confidence
82% confidence
Finding
This markdown instructs readers to run a release pipeline that publishes to crates.io, cuts git tags, creates GitHub releases, and uploads binaries. While the document explains ordering and failure modes, it does not give a direct user-facing warning near the commands that these actions modify external systems and may be irreversible once executed.

Static analysis

No suspicious patterns detected.