Skip to content

Complete native security engines and scoped policy enforcement #114

Complete native security engines and scoped policy enforcement

Complete native security engines and scoped policy enforcement #114

Workflow file for this run

name: CI
on:
push:
pull_request:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
env:
# Lint levels live in `[workspace.lints]` in the root Cargo.toml so local and
# CI runs agree; don't add a blanket RUSTFLAGS here.
CARGO_TERM_COLOR: always
jobs:
rust:
name: Rust
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}
defaults:
run:
shell: bash
steps:
- uses: actions/checkout@v7
with:
# This job executes repository code (cargo build/test); don't persist
# the token in git config.
persist-credentials: false
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy
- uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- uses: Swatinem/rust-cache@v2
- name: Check formatting
run: cargo fmt --all -- --check
- name: Clippy
run: cargo clippy --all-targets --all-features -- -D warnings
- name: Build
run: cargo build --all-targets --all-features
- name: Provision Windows audit test namespace
if: ${{ runner.os == 'Windows' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
# The hosted D: runner-temp ancestors fail the engine's actual owner/
# mutation ACL checks. Provision below the current user's normal profile;
# native tests still inspect and pin every ancestor, including C:\.
$auditRoot = Join-Path $env:USERPROFILE 'tinysecurity-audit-ci'
New-Item -ItemType Directory -Force $auditRoot | Out-Null
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$security = [System.Security.AccessControl.DirectorySecurity]::new()
$security.SetOwner($identity.User)
$security.SetAccessRuleProtection($true, $false)
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
$identity.User,
[System.Security.AccessControl.FileSystemRights]::FullControl,
[System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit',
[System.Security.AccessControl.PropagationFlags]::None,
[System.Security.AccessControl.AccessControlType]::Allow
)
[void]$security.AddAccessRule($rule)
Set-Acl -LiteralPath $auditRoot -AclObject $security
"TINYSECURITY_AUDIT_TEST_ROOT=$auditRoot" >> $env:GITHUB_ENV
- name: Windows protected audit namespace and rotation behavior
if: ${{ runner.os == 'Windows' }}
run: cargo test --locked -p tinysecurity-audit windows_ -- --nocapture
- name: Test
run: cargo test --all-features
- name: Test default features
run: cargo test
# `cargo build --all-targets` only *compiles* an example. `AGENTS.md`
# promises the native loader verifier works, and a compiled
# example can still fail on its first line.
- name: Run the bundled example
if: ${{ runner.os != 'Windows' }}
shell: bash
run: |
case "$RUNNER_OS" in
Linux) module=target/debug/libtinysecurity_module.so ;;
macOS) module=target/debug/libtinysecurity_module.dylib ;;
esac
cargo run -p tinysecurity-module --example verify_module -- "$module"
cargo run -p tinysecurity-module --example audit_native_contract -- "$module"
# Installed examples must also work outside the workspace root.
module="$(pwd)/$module"
(cd target && ./debug/examples/verify_module "$module")
(cd target && ./debug/examples/audit_native_contract "$module")
- name: Verify Windows module through the restricted native copy
if: ${{ runner.os == 'Windows' }}
shell: pwsh
run: |
$ErrorActionPreference = 'Stop'
$libraryName = 'tinysecurity_module'
$module = "target/debug/$libraryName.dll"
$verifyRoot = Join-Path $env:RUNNER_TEMP 'tinysecurity-module-ci-verify'
New-Item -ItemType Directory -Force $verifyRoot | Out-Null
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
$security = [System.Security.AccessControl.DirectorySecurity]::new()
$security.SetOwner($identity.User)
$security.SetAccessRuleProtection($true, $false)
$rights = [System.Security.AccessControl.FileSystemRights]::FullControl
$inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
$propagation = [System.Security.AccessControl.PropagationFlags]::None
$access = [System.Security.AccessControl.AccessControlType]::Allow
foreach ($sidValue in @(
$identity.User.Value,
'S-1-5-18',
'S-1-5-32-544'
)) {
$sid = [System.Security.Principal.SecurityIdentifier]::new($sidValue)
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
$sid,
$rights,
$inheritance,
$propagation,
$access
)
[void]$security.AddAccessRule($rule)
}
Set-Acl -LiteralPath $verifyRoot -AclObject $security
$verifiedModule = Join-Path $verifyRoot "$libraryName.dll"
Copy-Item -LiteralPath $module -Destination $verifiedModule
cargo run --locked --package tinysecurity-module --example verify_module -- $verifiedModule
cargo run --locked --package tinysecurity-module --example audit_native_contract -- $verifiedModule
$verifier = (Resolve-Path 'target/debug/examples/verify_module.exe').Path
$auditVerifier = (Resolve-Path 'target/debug/examples/audit_native_contract.exe').Path
Push-Location $env:RUNNER_TEMP
try {
& $verifier $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native verifier failed outside workspace' }
& $auditVerifier $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native audit verifier failed outside workspace' }
} finally {
Pop-Location
}
# `crates/tinysecurity-bus` exists so a host can name the payload types
# without compiling the module. That promise is invisible in a diff,
# because a forbidden dependency arrives transitively through a feature
# someone enabled one crate away — so it is asserted rather than
# documented.
#
# The FORWARD form is required. `cargo tree -i <crate> -p tinysecurity-bus`
# discards the `-p` scope, prints the whole-workspace inverse tree, and
# exits 0 looking clean even when this crate is the one at fault.
- name: Assert the contract crate stays transport-free
run: |
set -euo pipefail
cargo metadata --format-version 1 --no-deps | jq -e '
[.packages[] | select(.name == "tinysecurity-bus") | .dependencies[]
| select(.kind == null) | .name] | sort == ["serde", "thiserror"]'
forbidden="$(cargo tree -p tinysecurity-bus -e normal,build --prefix none \
| grep -Ei 'tinybus|tokio|reqwest|ureq|hyper|rusqlite|git2' || true)"
if [ -n "$forbidden" ]; then
echo "tinysecurity-bus pulled in a dependency its manifest forbids:" >&2
echo "$forbidden" >&2
echo >&2
echo "The contract is what a host compiles against. It must stay free" >&2
echo "of transports, async runtimes, HTTP clients and native libraries." >&2
exit 1
fi
# LLVM paths on Windows use native drive prefixes while Git Bash pwd
# uses /d/... . The Linux lane owns the exact per-file coverage gate;
# all three native lanes still build, test and load the real module.
- name: Require 90% line coverage in every source file
if: ${{ runner.os == 'Linux' }}
run: .github/scripts/check-file-coverage.sh 90 coverage.json
- name: Upload coverage report
if: ${{ always() && runner.os == 'Linux' }}
uses: actions/upload-artifact@v7
with:
name: coverage-json-${{ matrix.os }}
path: coverage.json
if-no-files-found: ignore
docs:
name: Docs
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- name: Build documentation
env:
RUSTDOCFLAGS: -D warnings
run: cargo doc --no-deps --all-features
msrv:
name: Minimum supported Rust version
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
# `rust-version` is inherited from `[workspace.package]`, so every member
# reports the same value. Read it off the package the module ships as
# rather than off `packages[0]`, whose order cargo does not promise.
- name: Read rust-version from Cargo.toml
id: msrv
run: |
set -euo pipefail
msrv="$(cargo metadata --format-version 1 --no-deps \
| jq -r '.packages[] | select(.name == "tinysecurity-module") | .rust_version')"
if [[ -z "$msrv" || "$msrv" == "null" ]]; then
echo "workspace.package.rust-version is not set in Cargo.toml" >&2
exit 1
fi
echo "version=$msrv" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ steps.msrv.outputs.version }}
- uses: Swatinem/rust-cache@v2
- name: Build with the declared MSRV
run: cargo build --all-targets --all-features
supply-chain:
name: Supply chain
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Check advisories, licenses, bans, and sources
uses: EmbarkStudios/cargo-deny-action@v2
with:
command: check all