Pin CI actions to commit SHAs, add Npcap checksum verification (#210)

- Pin all GitHub Actions to full commit SHAs across workflows
- Add SHA256 verification for Windows Npcap SDK download in build.rs
- Add supply chain security and sandboxing documentation to SECURITY.md
- Add sha2 build-dependency for Windows
This commit is contained in:
Marco Cadetg
2026-03-27 08:49:53 +01:00
committed by GitHub
parent 7739231f06
commit 85d7b5c12c
14 changed files with 92 additions and 38 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Extract version from tag
id: version
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
- name: Checkout repository
if: matrix.arch != 'aarch64'
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ inputs.version }}
+7 -7
View File
@@ -68,14 +68,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Setup Linux dependencies
if: matrix.os == 'ubuntu-22.04'
uses: ./.github/actions/setup-linux-deps
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
with:
targets: ${{ matrix.target }}
components: rustfmt
@@ -99,7 +99,7 @@ jobs:
# For test builds: upload raw binary
- name: Upload binary artifact
if: ${{ !inputs.create-archives }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: rustnet-${{ matrix.build }}
path: target/${{ matrix.target }}/release/rustnet${{ contains(matrix.os, 'windows') && '.exe' || '' }}
@@ -135,7 +135,7 @@ jobs:
- name: Upload release archive
if: ${{ inputs.create-archives }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: build-${{ matrix.target }}
path: ${{ env.ASSET }}
@@ -179,7 +179,7 @@ jobs:
# x86_64: use standard checkout action
- name: Checkout repository
if: matrix.arch == 'x86_64' || matrix.arch == 'x86_64-android' || matrix.arch == 'armv7-android' || matrix.arch == 'x86-android'
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
# aarch64: JS actions don't work in Alpine containers on ARM runners
- name: Checkout repository (ARM workaround)
@@ -312,7 +312,7 @@ jobs:
- name: Upload binary artifact
if: ${{ !inputs.create-archives }}
continue-on-error: ${{ matrix.arch != 'x86_64' }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: rustnet-linux-static-${{ matrix.arch }}
path: ${{ (matrix.arch == 'armv7-android' && 'target/armv7-unknown-linux-musleabihf/release/rustnet') || (matrix.arch == 'x86-android' && 'target/i686-unknown-linux-musl/release/rustnet') || 'target/release/rustnet' }}
@@ -339,7 +339,7 @@ jobs:
# JS actions don't work in Alpine containers on ARM runners;
# release.yml has dedicated upload-arm-static/upload-android-static jobs as fallback
continue-on-error: ${{ contains(matrix.arch, 'aarch64') }}
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: build-${{ matrix.target }}
path: rustnet-${{ inputs.version }}-${{ matrix.target }}.tar.gz
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
token: ${{ secrets.GITHUB_TOKEN }}
+6 -6
View File
@@ -18,14 +18,14 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Log in to Container Registry
if: github.event_name != 'pull_request'
uses: docker/login-action@v4
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
@@ -33,7 +33,7 @@ jobs:
- name: Extract metadata
id: meta
uses: docker/metadata-action@v6
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
@@ -46,7 +46,7 @@ jobs:
- name: Build and push Docker image
id: build-and-push
uses: docker/build-push-action@v7
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
platforms: linux/amd64,linux/arm64
@@ -58,7 +58,7 @@ jobs:
- name: Generate artifact attestation
if: github.event_name != 'pull_request'
uses: actions/attest-build-provenance@v4
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4
with:
subject-name: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME}}
subject-digest: ${{ steps.build-and-push.outputs.digest }}
+3 -3
View File
@@ -43,7 +43,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
fetch-depth: 0
@@ -64,7 +64,7 @@ jobs:
pkg-config
- name: Install Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
with:
toolchain: stable
@@ -185,7 +185,7 @@ jobs:
dput ${{ env.PPA }} ${CHANGES_FILE}
- name: Upload artifacts
uses: actions/upload-artifact@v7
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
with:
name: ppa-source-${{ matrix.ubuntu_release }}
path: |
+2 -2
View File
@@ -13,7 +13,7 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install dependencies
run: |
@@ -21,7 +21,7 @@ jobs:
sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Set up Rust
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
- name: Publish to crates.io
env:
+11 -11
View File
@@ -50,10 +50,10 @@ jobs:
needs: build
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download all artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
@@ -210,7 +210,7 @@ jobs:
- name: Checkout repository
if: matrix.arch != 'aarch64'
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install common dependencies
run: |
@@ -439,10 +439,10 @@ jobs:
needs: create-release
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download build artifacts
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
path: artifacts
@@ -450,7 +450,7 @@ jobs:
uses: ./.github/actions/setup-linux-deps
- name: Install Rust and tools
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
with:
targets: x86_64-unknown-linux-gnu,aarch64-unknown-linux-gnu,armv7-unknown-linux-gnueabihf
components: rustfmt
@@ -537,7 +537,7 @@ jobs:
target: aarch64-apple-darwin
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install packaging tools
run: |
@@ -546,7 +546,7 @@ jobs:
brew install create-dmg
- name: Download build artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: build-${{ matrix.target }}
path: artifacts
@@ -652,7 +652,7 @@ jobs:
target: x86_64-pc-windows-msvc
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install dependencies
shell: powershell
@@ -672,7 +672,7 @@ jobs:
Write-Host "::endgroup::"
- name: Install Rust and tools
uses: dtolnay/rust-toolchain@stable
uses: dtolnay/rust-toolchain@stable # unpinned: maintained branch by Rust team member
with:
targets: ${{ matrix.target }}
@@ -683,7 +683,7 @@ jobs:
cargo wix --version
- name: Download build artifact
uses: actions/download-artifact@v8
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
with:
name: build-${{ matrix.target }}
path: artifacts
+4 -4
View File
@@ -37,7 +37,7 @@ jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Install dependencies
run: sudo apt-get update && sudo apt-get install -y libpcap-dev libelf-dev zlib1g-dev clang llvm pkg-config
- name: Check formatting
@@ -56,11 +56,11 @@ jobs:
docker:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
- name: Build Docker image
uses: docker/build-push-action@v7
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
with:
context: .
push: false
+1 -1
View File
@@ -13,7 +13,7 @@ jobs:
update-oui:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
- name: Download latest IEEE OUI database
run: |
Generated
+1
View File
@@ -2365,6 +2365,7 @@ dependencies = [
"ring",
"serde",
"serde_json",
"sha2",
"simplelog",
"windows",
"zip",
+1
View File
@@ -75,6 +75,7 @@ clap_mangen = "0.2"
[target.'cfg(windows)'.build-dependencies]
http_req = "0.14"
zip = "8.3"
sha2 = "0.10"
windows = { version = "0.62", features = [
"Win32_Foundation",
"Win32_NetworkManagement_IpHelper",
+30
View File
@@ -12,6 +12,7 @@ RustNet processes untrusted network data, making defense-in-depth security criti
- [Log File Privacy](#log-file-privacy)
- [eBPF Security](#ebpf-security)
- [Threat Model](#threat-model)
- [Supply Chain Security](#supply-chain-security)
- [Audit and Compliance](#audit-and-compliance)
- [Reporting Security Issues](#reporting-security-issues)
@@ -201,6 +202,35 @@ When using eBPF for enhanced process detection (default on Linux):
- Physical access to the machine enables packet capture
- Network-level attacks (RustNet is a monitoring tool, not a security appliance)
### Sandboxing as Root
Both Landlock (Linux) and Seatbelt (macOS) enforce restrictions even when RustNet runs as root (UID 0). Once applied, the sandbox cannot be reversed from within the process — Landlock sets `PR_SET_NO_NEW_PRIVS` which is irreversible per-process.
However, sandboxing does **not** protect against supply chain attacks. A compromised binary would simply not apply the sandbox. Root can also:
- Pass `--no-sandbox` to skip sandboxing entirely
- Unload the Landlock LSM kernel module
- Disable SIP on macOS (which controls sandbox enforcement)
- Use `ptrace` to modify a running process
For this reason, running with fine-grained capabilities (`setcap cap_net_raw=eip`) is strongly preferred over running as root.
## Supply Chain Security
RustNet takes the following measures to protect against supply chain attacks:
- **Dependency lockfile**: `Cargo.lock` is committed to the repository, pinning all transitive dependency versions and recording source checksums. This prevents silent version upgrades.
- **Security audit**: `cargo audit` runs in CI on every push and pull request, checking dependencies against the RustSec Advisory Database.
- **CI action pinning**: All GitHub Actions are pinned by commit SHA (not tags), preventing tag-rewriting attacks on upstream actions.
- **Conservative dependency policy**: New dependencies require justification and are reviewed for maintenance status and security track record (see `CONTRIBUTING.md`).
- **Build-time integrity**: The Windows Npcap SDK download in `build.rs` is verified against a hardcoded SHA256 checksum.
- **Code signing**: macOS releases are signed with an Apple Developer certificate and notarized.
- **Checksum verification**: All packaging workflows (Homebrew, Chocolatey, AUR) calculate and double-verify SHA256 checksums before publishing.
### Limitations
- `cargo install rustnet` fetches the latest compatible versions from crates.io and does **not** use `Cargo.lock`. Users building from source should verify the source tarball checksum.
- Build scripts (`build.rs`) and proc-macros execute arbitrary code at compile time. While all current dependencies are well-established crates, this is an inherent risk of the Rust build model.
## Audit and Compliance
For production environments:
+23 -1
View File
@@ -80,6 +80,7 @@ fn generate_assets() -> Result<()> {
#[cfg(target_os = "windows")]
fn download_windows_npcap_sdk() -> Result<()> {
use sha2::{Digest, Sha256};
use std::{
fs,
io::{self, Write},
@@ -89,15 +90,18 @@ fn download_windows_npcap_sdk() -> Result<()> {
// get npcap SDK
const NPCAP_SDK: &str = "npcap-sdk-1.15.zip";
const NPCAP_SDK_SHA256: &str =
"52c7b9fb4abee3ad9fe739bb545c3efe77b731c8e127122bdf328eafdae3ed4f";
let npcap_sdk_download_url = format!("https://npcap.com/dist/{NPCAP_SDK}");
let cache_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR")?).join("target");
let npcap_sdk_cache_path = cache_dir.join(NPCAP_SDK);
let npcap_zip = match fs::read(&npcap_sdk_cache_path) {
// use cached
// use cached (verify checksum)
Ok(zip_data) => {
eprintln!("Found cached npcap SDK");
verify_npcap_checksum(&zip_data)?;
zip_data
}
// download SDK
@@ -108,6 +112,9 @@ fn download_windows_npcap_sdk() -> Result<()> {
let mut zip_data = vec![];
let _res = http_req::request::get(npcap_sdk_download_url, &mut zip_data)?;
// verify checksum before caching
verify_npcap_checksum(&zip_data)?;
// write cache
fs::create_dir_all(cache_dir)?;
let mut cache = fs::File::create(npcap_sdk_cache_path)?;
@@ -117,6 +124,21 @@ fn download_windows_npcap_sdk() -> Result<()> {
}
};
fn verify_npcap_checksum(data: &[u8]) -> Result<()> {
let hash = Sha256::digest(data);
let actual = hash.iter().map(|b| format!("{b:02x}")).collect::<String>();
if actual != NPCAP_SDK_SHA256 {
anyhow::bail!(
"Npcap SDK checksum mismatch!\n Expected: {}\n Actual: {}\n\
The downloaded file may be corrupted or tampered with.",
NPCAP_SDK_SHA256,
actual
);
}
eprintln!("Npcap SDK checksum verified: {actual}");
Ok(())
}
// extract libraries based on target architecture
let target = env::var("TARGET").unwrap_or_else(|_| "unknown".to_string());
let (packet_lib_path, wpcap_lib_path) = if target.contains("aarch64") {