Skip to content

fix(approval): recheck reusable grants and shrink judge caches #165

fix(approval): recheck reusable grants and shrink judge caches

fix(approval): recheck reusable grants and shrink judge caches #165

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
fetch-depth: 0
- uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy, llvm-tools-preview
- uses: taiki-e/install-action@v2
with:
tool: cargo-llvm-cov
- uses: Swatinem/rust-cache@v2
- name: Test native coverage gate
run: python .github/scripts/check-native-coverage_tests.py
- 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 approval namespace preserves restricted native drive paths
if: ${{ runner.os == 'Windows' }}
shell: pwsh
run: |
cargo test --locked --package tinysecurity-module --example approval_native_contract windows_approval_namespace_preserves_drive_path_and_opens_restricted_sink -- --nocapture
if ($LASTEXITCODE -ne 0) { throw 'native approval namespace regression failed' }
- 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
# Collect independent ownership checks before aggregating failure;
# one failed check must not hide the exact installed namespace result.
$diagnosticFailure = $false
cargo test --locked --package tinysecurity-module --example audit_native_contract -- --nocapture
if ($LASTEXITCODE -ne 0) { $diagnosticFailure = $true }
cargo test --locked -p tinysecurity-audit windows_token_default_owner_explains_ordinary_created_file_owner -- --nocapture
if ($LASTEXITCODE -ne 0) { $diagnosticFailure = $true }
cargo test --locked -p tinysecurity-audit windows_new_engine_file_is_owned_by_current_user_before_any_bytes -- --nocapture
if ($LASTEXITCODE -ne 0) { $diagnosticFailure = $true }
cargo test --locked -p tinysecurity-audit windows_existing_foreign_owner_is_denied_without_owner_repair -- --nocapture
if ($LASTEXITCODE -ne 0) { $diagnosticFailure = $true }
cargo test --locked -p tinysecurity-audit windows_protected_preprovisioned_namespace_commits_exact_retry -- --nocapture
if ($LASTEXITCODE -ne 0) { $diagnosticFailure = $true }
if ($diagnosticFailure) { throw 'native audit ownership regressions failed' }
cargo run --locked --package tinysecurity-module --example verify_module -- $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native verifier failed in workspace' }
cargo run --locked --package tinysecurity-module --example audit_native_contract -- $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native audit verifier failed in workspace' }
$verifier = (Resolve-Path 'target/debug/examples/verify_module.exe').Path
cargo run --locked --package tinysecurity-module --example approval_native_contract -- $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native approval verifier failed in workspace' }
cargo run --locked --package tinysecurity-module --example judge_native_contract -- $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native judge verifier failed in workspace' }
$judgeVerifier = (Resolve-Path 'target/debug/examples/judge_native_contract.exe').Path
$approvalVerifier = (Resolve-Path 'target/debug/examples/approval_native_contract.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' }
& $judgeVerifier $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native judge verifier failed outside workspace' }
& $approvalVerifier $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native approval verifier failed outside workspace' }
& $auditVerifier $verifiedModule
if ($LASTEXITCODE -ne 0) { throw 'native audit verifier failed outside workspace' }
} finally {
Pop-Location
}
- 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"
cargo run -p tinysecurity-module --example approval_native_contract -- "$module"
cargo run -p tinysecurity-module --example judge_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")
(cd target && ./debug/examples/approval_native_contract "$module")
(cd target && ./debug/examples/judge_native_contract "$module")
# `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
# Preserve Linux's established per-file gate and add the same strict
# native JSON/LCOV inventory and changed-line gate to every platform.
- name: Require 90% line coverage in every source file
if: ${{ runner.os == 'Linux' }}
run: .github/scripts/check-file-coverage.sh 90 coverage.json
- name: Collect native coverage
env:
PR_BASE: ${{ github.event.pull_request.base.sha }}
BASE_BRANCH: ${{ github.event.repository.default_branch }}
run: |
set -euo pipefail
base="$PR_BASE"
if [ -z "$base" ]; then
base="$(git merge-base HEAD "refs/remotes/origin/$BASE_BRANCH")"
fi
case "$RUNNER_OS" in
Linux) platform=linux ;;
macOS) platform=macos ;;
Windows) platform=windows ;;
*) echo 'unsupported coverage platform' >&2; exit 1 ;;
esac
# Python supplies a native drive path on Windows; Bash PWD is an
# MSYS path and cannot match cargo-llvm-cov's native filenames.
checkout_root="$(python -c 'import pathlib; print(pathlib.Path.cwd())')"
export COVERAGE_BASE="$base" COVERAGE_PLATFORM="$platform"
python - <<'PYTHON'
import json, os, pathlib, subprocess
metadata = {
'checkout_root': str(pathlib.Path.cwd()),
'head': subprocess.check_output(['git', 'rev-parse', 'HEAD']).decode('utf-8').strip(),
'base': os.environ['COVERAGE_BASE'],
'rustc': subprocess.check_output(['rustc', '--version', '--verbose']).decode('utf-8'),
'runner_os': os.environ['RUNNER_OS'],
'platform': os.environ['COVERAGE_PLATFORM'],
}
pathlib.Path('coverage-native-metadata.json').write_text(json.dumps(metadata, indent=2), encoding='utf-8')
PYTHON
cargo llvm-cov --locked --workspace --all-targets --all-features --json --output-path coverage-native.json
# Report exports the instrumented all-feature/all-target run. Selection
# flags are invalid for cargo-llvm-cov's export-only report subcommand.
cargo llvm-cov report --lcov --output-path coverage-native.info
python .github/scripts/check-native-coverage.py --json coverage-native.json --lcov coverage-native.info --base "$base" --root "$checkout_root" --platform "$platform"
- name: Upload native coverage reports
if: ${{ always() }}
uses: actions/upload-artifact@v7
with:
name: coverage-native-${{ runner.os }}-${{ github.sha }}
path: |
coverage-native.json
coverage-native.info
coverage-native-metadata.json
if-no-files-found: warn
- 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