Skip to content

Commit e66b85d

Browse files
senamakelmedullabot
andcommitted
feat(security): bootstrap the native policy bus and contracts
Co-authored-by: Medulla <medulla@tinyhumans.ai>
1 parent a85fa87 commit e66b85d

67 files changed

Lines changed: 4041 additions & 1558 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎.github/ISSUE_TEMPLATE/config.yml‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
blank_issues_enabled: true
22
contact_links:
33
- name: Security reports
4-
url: https://github.com/tinyhumansai/rust-template/security/policy
4+
url: https://github.com/tinyhumansai/tinysecurity/security/policy
55
about: Please do not report vulnerabilities through public issues.

‎.github/workflows/ci.yml‎

Lines changed: 71 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,14 @@ env:
1919
jobs:
2020
rust:
2121
name: Rust
22-
runs-on: ubuntu-latest
22+
strategy:
23+
fail-fast: false
24+
matrix:
25+
os: [ubuntu-latest, macos-latest, windows-latest]
26+
runs-on: ${{ matrix.os }}
27+
defaults:
28+
run:
29+
shell: bash
2330
steps:
2431
- uses: actions/checkout@v7
2532
with:
@@ -54,42 +61,95 @@ jobs:
5461
run: cargo test
5562

5663
# `cargo build --all-targets` only *compiles* an example. `AGENTS.md`
57-
# promises `cargo run -p template --example basic` works, and a compiled
64+
# promises the native loader verifier works, and a compiled
5865
# example can still fail on its first line.
5966
- name: Run the bundled example
60-
run: cargo run -p template --example basic
61-
62-
# `crates/template-bus` exists so a host can name the payload types
67+
if: ${{ runner.os != 'Windows' }}
68+
shell: bash
69+
run: |
70+
case "$RUNNER_OS" in
71+
Linux) module=target/debug/libtinysecurity_module.so ;;
72+
macOS) module=target/debug/libtinysecurity_module.dylib ;;
73+
esac
74+
cargo run -p tinysecurity-module --example verify_module -- "$module"
75+
76+
- name: Verify Windows module through the restricted native copy
77+
if: ${{ runner.os == 'Windows' }}
78+
shell: pwsh
79+
run: |
80+
$ErrorActionPreference = 'Stop'
81+
$libraryName = 'tinysecurity_module'
82+
$module = "target/debug/$libraryName.dll"
83+
$verifyRoot = Join-Path $env:RUNNER_TEMP 'tinysecurity-module-ci-verify'
84+
New-Item -ItemType Directory -Force $verifyRoot | Out-Null
85+
86+
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
87+
$security = [System.Security.AccessControl.DirectorySecurity]::new()
88+
$security.SetOwner($identity.User)
89+
$security.SetAccessRuleProtection($true, $false)
90+
$rights = [System.Security.AccessControl.FileSystemRights]::FullControl
91+
$inheritance = [System.Security.AccessControl.InheritanceFlags]'ContainerInherit, ObjectInherit'
92+
$propagation = [System.Security.AccessControl.PropagationFlags]::None
93+
$access = [System.Security.AccessControl.AccessControlType]::Allow
94+
foreach ($sidValue in @(
95+
$identity.User.Value,
96+
'S-1-5-18',
97+
'S-1-5-32-544'
98+
)) {
99+
$sid = [System.Security.Principal.SecurityIdentifier]::new($sidValue)
100+
$rule = [System.Security.AccessControl.FileSystemAccessRule]::new(
101+
$sid,
102+
$rights,
103+
$inheritance,
104+
$propagation,
105+
$access
106+
)
107+
[void]$security.AddAccessRule($rule)
108+
}
109+
Set-Acl -LiteralPath $verifyRoot -AclObject $security
110+
111+
$verifiedModule = Join-Path $verifyRoot "$libraryName.dll"
112+
Copy-Item -LiteralPath $module -Destination $verifiedModule
113+
cargo run --locked --package tinysecurity-module --example verify_module -- $verifiedModule
114+
115+
# `crates/tinysecurity-bus` exists so a host can name the payload types
63116
# without compiling the module. That promise is invisible in a diff,
64117
# because a forbidden dependency arrives transitively through a feature
65118
# someone enabled one crate away — so it is asserted rather than
66119
# documented.
67120
#
68-
# The FORWARD form is required. `cargo tree -i <crate> -p template-bus`
121+
# The FORWARD form is required. `cargo tree -i <crate> -p tinysecurity-bus`
69122
# discards the `-p` scope, prints the whole-workspace inverse tree, and
70123
# exits 0 looking clean even when this crate is the one at fault.
71124
- name: Assert the contract crate stays transport-free
72125
run: |
73126
set -euo pipefail
74-
forbidden="$(cargo tree -p template-bus -e normal,build --prefix none \
127+
cargo metadata --format-version 1 --no-deps | jq -e '
128+
[.packages[] | select(.name == "tinysecurity-bus") | .dependencies[]
129+
| select(.kind == null) | .name] | sort == ["serde", "thiserror"]'
130+
forbidden="$(cargo tree -p tinysecurity-bus -e normal,build --prefix none \
75131
| grep -Ei 'tinybus|tokio|reqwest|ureq|hyper|rusqlite|git2' || true)"
76132
if [ -n "$forbidden" ]; then
77-
echo "template-bus pulled in a dependency its manifest forbids:" >&2
133+
echo "tinysecurity-bus pulled in a dependency its manifest forbids:" >&2
78134
echo "$forbidden" >&2
79135
echo >&2
80136
echo "The contract is what a host compiles against. It must stay free" >&2
81137
echo "of transports, async runtimes, HTTP clients and native libraries." >&2
82138
exit 1
83139
fi
84140
141+
# LLVM paths on Windows use native drive prefixes while Git Bash pwd
142+
# uses /d/... . The Linux lane owns the exact per-file coverage gate;
143+
# all three native lanes still build, test and load the real module.
85144
- name: Require 90% line coverage in every source file
145+
if: ${{ runner.os == 'Linux' }}
86146
run: .github/scripts/check-file-coverage.sh 90 coverage.json
87147

88148
- name: Upload coverage report
89-
if: ${{ always() }}
149+
if: ${{ always() && runner.os == 'Linux' }}
90150
uses: actions/upload-artifact@v7
91151
with:
92-
name: coverage-json
152+
name: coverage-json-${{ matrix.os }}
93153
path: coverage.json
94154
if-no-files-found: ignore
95155

@@ -128,7 +188,7 @@ jobs:
128188
run: |
129189
set -euo pipefail
130190
msrv="$(cargo metadata --format-version 1 --no-deps \
131-
| jq -r '.packages[] | select(.name == "template") | .rust_version')"
191+
| jq -r '.packages[] | select(.name == "tinysecurity-module") | .rust_version')"
132192
if [[ -z "$msrv" || "$msrv" == "null" ]]; then
133193
echo "workspace.package.rust-version is not set in Cargo.toml" >&2
134194
exit 1

‎.github/workflows/release.yml‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ permissions:
2222

2323
env:
2424
# The workspace member that ships as the loadable module. Its package name is
25-
# the artifact name and the library name; `crates/template-bus` rides along on
25+
# the artifact name and the library name; `crates/tinysecurity-bus` rides along on
2626
# the same inherited version and is not packaged separately.
27-
RELEASE_PACKAGE: template
27+
RELEASE_PACKAGE: tinysecurity-module
2828

2929
jobs:
3030
prepare:
@@ -259,7 +259,7 @@ jobs:
259259
macOS) module="target/release/lib${library_name}.dylib" ;;
260260
*) echo "unsupported Unix runner: ${RUNNER_OS}" >&2; exit 1 ;;
261261
esac
262-
cargo run --locked --package template --example verify_module -- "$module"
262+
cargo run --locked --package tinysecurity-module --example verify_module -- "$module"
263263
264264
- name: Verify Windows module through TinyBus loader
265265
if: ${{ runner.os == 'Windows' }}
@@ -270,7 +270,7 @@ jobs:
270270
$ErrorActionPreference = 'Stop'
271271
$libraryName = $env:CRATE_NAME.Replace('-', '_')
272272
$module = "target/release/$libraryName.dll"
273-
$verifyRoot = Join-Path $env:RUNNER_TEMP 'template-module-verify'
273+
$verifyRoot = Join-Path $env:RUNNER_TEMP 'tinysecurity-module-verify'
274274
New-Item -ItemType Directory -Force $verifyRoot | Out-Null
275275
276276
$identity = [System.Security.Principal.WindowsIdentity]::GetCurrent()
@@ -300,7 +300,7 @@ jobs:
300300
301301
$verifiedModule = Join-Path $verifyRoot "$libraryName.dll"
302302
Copy-Item -LiteralPath $module -Destination $verifiedModule
303-
cargo run --locked --package template --example verify_module -- $verifiedModule
303+
cargo run --locked --package tinysecurity-module --example verify_module -- $verifiedModule
304304
305305
- name: Assemble Unix module package
306306
if: ${{ runner.os != 'Windows' }}
@@ -463,7 +463,7 @@ jobs:
463463
verify_root="/opt/${CRATE_NAME}-module-verify"
464464
install -d -m 700 "$verify_root"
465465
install -m 755 "target/release/lib${library_name}.so" "$verify_root/"
466-
cargo run --locked --package template --example verify_module -- \
466+
cargo run --locked --package tinysecurity-module --example verify_module -- \
467467
"$verify_root/lib${library_name}.so"
468468
469469
- name: Assemble distribution module package
@@ -587,5 +587,5 @@ jobs:
587587
cargo run --manifest-path vendor/tinybus/Cargo.toml --locked \
588588
--package tinybus --all-features --example github_module_host -- \
589589
"$release_url" "$archive" "$sha256"
590-
cargo run --locked --package template --example verify_github_release -- \
590+
cargo run --locked --package tinysecurity-module --example verify_module -- \
591591
"$release_url" "$archive" "$sha256"

‎AGENTS.md‎

Lines changed: 27 additions & 117 deletions
Original file line numberDiff line numberDiff line change
@@ -4,118 +4,23 @@ This file is the single source of truth for how humans and coding agents work
44
in this repository. `CLAUDE.md` is a symlink to this file, so every agent reads
55
the same instructions.
66

7-
When you generate a new project from this template, keep this file and adapt
8-
the project-specific parts (crate name, module map, feature flags, commands).
9-
Delete guidance that no longer applies rather than leaving it to rot.
10-
11-
## Template Checklist
12-
13-
Do this once, in a single commit, before writing feature code:
14-
15-
- [ ] Rename `crates/template` and `crates/template-bus` to the project's crate
16-
names, and update `name` in each manifest plus the `template-bus` entry in
17-
the root `[workspace.dependencies]`.
18-
- [ ] Set `description`, `keywords`, and `categories` in each manifest, and
19-
`repository` in the root `[workspace.package]`.
20-
- [ ] Rename the crate references in `README.md`, both `src/lib.rs` files,
21-
`crates/template/examples/`, and `crates/template/tests/` (search for
22-
`template` and `template_bus`).
23-
- [ ] Replace the placeholder `greeting` module in both crates with the first
24-
real feature area — payload types in the contract crate, behavior in the
25-
module crate — keeping the `mod.rs` / `types.rs` / `test.rs` layout.
26-
- [ ] Confirm `license` and `LICENSE` match the project's intended license.
27-
- [ ] Update the security contact in `SECURITY.md`.
28-
- [ ] Rename the TinyBus interface, object path, and member constants in
29-
`crates/template-bus/src/names/`, and the matching `provides` / `methods`
30-
declarations in `crates/template/src/tinybus_module/`, while keeping
31-
`vendor/tinybus` pinned.
32-
- [ ] Reset `CONTRACT_VERSION` in `crates/template-bus/src/version/` for the new
33-
contract.
34-
- [ ] Replace `ROADMAP.md` with the real plan, or delete it.
35-
- [ ] Rewrite the "Project Structure" section below to describe this workspace.
36-
377
## Project Structure
388

39-
This is a Rust 2024 cargo workspace rooted at a virtual `Cargo.toml`. Every
40-
crate lives under `crates/`, one directory per package, each directory named for
41-
the package it holds. There is no root package: the crate that ships as the
42-
loadable module is `crates/template`, the same as any other member.
43-
44-
```text
45-
Cargo.toml # virtual workspace: members, [workspace.package],
46-
# [workspace.dependencies], [workspace.lints]
47-
crates/
48-
├── template-bus/ # the wire contract: what crosses the bus, nothing else
49-
│ ├── README.md # why the contract is its own crate
50-
│ └── src/
51-
│ ├── lib.rs # crate docs + the entire public re-export surface
52-
│ ├── names/ # interface, object path, one constant per member
53-
│ ├── version/ # contract version and the host bind rule
54-
│ └── <family>/ # one directory per payload family
55-
└── template/ # the module: behavior, adapter, and the cdylib
56-
├── src/
57-
│ ├── lib.rs # crate docs + public surface, re-exporting the contract
58-
│ ├── error/mod.rs # crate-wide `Error` and `Result<T>`
59-
│ ├── tinybus_module/ # TinyBus interface, ABI exports, integration tests
60-
│ └── <feature>/ # one directory per feature area
61-
│ ├── mod.rs # module docs, wiring, smallest useful public API
62-
│ ├── types.rs # substantial type definitions
63-
│ └── test.rs # module-local unit tests
64-
├── tests/ # integration tests against the public API only
65-
└── examples/ # runnable, compiled-in-CI usage examples
66-
vendor/tinybus/ # pinned TinyBus host types and module SDK
67-
docs/
68-
├── specs/ # behavior and architecture specifications
69-
├── plans/ # test-first implementation plans
70-
└── adr/ # immutable architecture decision records
71-
```
72-
73-
### The two-crate split
74-
75-
`crates/template-bus` holds every type that crosses the bus and the names of the
76-
members that carry them. It has no transport, no runtime, and no behavior, and
77-
CI asserts it stays that way. A host that only makes calls depends on it alone.
78-
79-
`crates/template` depends on it and re-exports all of it, so
80-
`template::GreetRequest` and `template_bus::GreetRequest` are the *same* type
81-
rather than structural twins. That direction is load-bearing: a parallel set of
82-
payload types for hosts would mean a conversion at every call site that nothing
83-
checks.
84-
85-
The rule for deciding where something goes: a payload type describes what a
86-
frame carries and belongs in the contract; anything that answers a frame, holds
87-
a connection, or touches an engine belongs in the module crate.
88-
89-
Add a crate by creating `crates/<name>/` — `members = ["crates/*"]` picks it up
90-
by existing. Inherit `version`, `edition`, `rust-version`, `license`, and
91-
`repository` from `[workspace.package]`, take shared dependencies from
92-
`[workspace.dependencies]`, and opt into the shared lint set with:
93-
94-
```toml
95-
[lints]
96-
workspace = true
97-
```
98-
99-
Each feature area belongs in a focused module directory under a crate's `src/`.
100-
A module root explains the module, wires its pieces together, and exposes the
101-
smallest useful API. Move substantial type definitions into `types.rs` and put
102-
module-local unit tests in a dedicated `test.rs`, wired from the bottom of the
103-
module root with:
104-
105-
```rust
106-
#[cfg(test)]
107-
mod test;
108-
```
9+
This Rust 2024 workspace ships a native TinyBus security module.
10910

110-
Do not accumulate inline `mod tests` blocks in implementation files, and do not
111-
let a general-purpose `utils.rs` or `helpers.rs` grow — those are a symptom of a
112-
missing module. Prefer many small modules that each do one thing well over few
113-
broad ones.
11+
- `crates/tinysecurity-bus`: versioned transport-free contract, normal dependencies serde and thiserror only.
12+
- `crates/tinysecurity-policy`: conservative native policy engine.
13+
- `crates/tinysecurity`: internal engine re-export, never host-linked.
14+
- `crates/tinysecurity-module`: configured TinyBus SDK adapter and cdylib.
15+
- `vendor/tinybus`: pinned module SDK and loader, maintained upstream.
16+
- `docs/specs`, `docs/plans`, `docs/adr`: accepted design, ordered implementation and decisions.
11417

115-
Keep public exports centralized in each crate's `src/lib.rs` so downstream users
116-
have one predictable surface. Put shared error variants in
117-
`crates/template/src/error/mod.rs` and return the crate-wide `Result<T>` from
118-
fallible public APIs.
18+
Only implemented engines may advertise methods. Unknown effects deny. Module
19+
configuration uses SDK init/reinit, not lifecycle RPCs. Hosts link only the
20+
contract, and production migration requires released artifacts/checksums.
21+
Tests live in sibling `*_tests.rs` files or crate `tests/` integration suites;
22+
never use inline modules or `test.rs` filenames. Public types have rustdoc.
23+
Do not create empty engine crates or claim full tracker completion at bootstrap.
11924

12025
## Build And Test
12126

@@ -133,8 +38,8 @@ Supporting commands:
13338

13439
- `cargo fmt --all` — format before committing.
13540
- `cargo test <filter>` — run a focused subset while iterating.
136-
- `cargo test -p template-bus` — run one crate's suite.
137-
- `cargo run -p template --example basic` — run the bundled example.
41+
- `cargo test -p tinysecurity-bus` — run one crate's suite.
42+
- `cargo run -p tinysecurity-module --example verify_module -- <native-library>` — run the bundled example.
13843
- `cargo doc --no-deps --all-features` — build the rustdoc CI also builds with
13944
`RUSTDOCFLAGS="-D warnings"`.
14045
- `cargo test --doc` — run doctests alone when editing documentation examples.
@@ -185,7 +90,7 @@ add one:
18590
- gate anything optional behind a Cargo feature, documented in `Cargo.toml`;
18691
- declare it once in the root `[workspace.dependencies]` when more than one
18792
crate needs it, and take it with `{ workspace = true }`;
188-
- never add one to `crates/template-bus` that pulls in a transport, an async
93+
- never add one to `crates/tinysecurity-bus` that pulls in a transport, an async
18994
runtime, an HTTP client, or a native library — CI fails the build if you do;
19095
- leave a comment above the entry explaining *why* the crate is needed and what
19196
uses it — see the existing entries for the expected tone;
@@ -211,15 +116,20 @@ new module capability requires more.
211116

212117
## Testing
213118

214-
- Module-local unit tests live in `crates/<crate>/src/<feature>/test.rs` and may
215-
touch private items.
119+
- Module-local unit tests live in sibling `*_tests.rs` files and may touch
120+
private items: use `src/<feature>/mod_tests.rs` beside `mod.rs`, or
121+
`src/<module>_tests.rs` beside `<module>.rs`. Declare them at the bottom of
122+
the source module with `#[cfg(test)]`, `#[path = "<module>_tests.rs"]`
123+
(or `"mod_tests.rs"`), and `mod tests;`. The test file starts with
124+
`use super::*;` and carries no `#[cfg(test)]` of its own. Never use inline
125+
test modules or files named `test.rs`, `tests.rs`, or `<module>_test.rs`.
216126
- Integration tests live in `crates/<crate>/tests/` and exercise only the public
217127
API — they are the regression suite for the crate's contract.
218128
- Payload types pin their serde representation in a unit test. That
219129
representation is the wire form: a host and a module that disagree about a
220130
field name fail at runtime with a decode error.
221131
- Use descriptive, behavioral test names: `rejects_an_empty_name`, not
222-
`test_greet_2`.
132+
`test_policy_2`.
223133
- Cover the failure paths, not just the happy path. Every new error variant
224134
needs a test that produces it.
225135
- For async behavior, standardize on one runtime (`tokio` as a dev-dependency
@@ -240,8 +150,8 @@ Write documentation for the reader who has never seen the code.
240150

241151
- Every public item gets a rustdoc comment. `missing_docs` is a warning that CI
242152
treats as an error.
243-
- Start every `mod.rs` and `test.rs` with a concise module-level `//!`
244-
description.
153+
- Start every `mod.rs` with a concise module-level `//!` description.
154+
Sibling `*_tests.rs` files start with `use super::*;` as specified above.
245155
- Each crate's `src/lib.rs` carries its crate-level overview: what the crate
246156
does, the primary entry points, and a short runnable example. It should also
247157
say what the crate deliberately does *not* hold, and why.
@@ -295,7 +205,7 @@ Releases run from `.github/workflows/release.yml` via a manual
295205
an interrupted release after its version commit and tag exist. The workflow
296206
re-runs the full validation suite, computes the next version, updates
297207
the root `[workspace.package]` version and `Cargo.lock`, commits and tags
298-
`vX.Y.Z`, builds `crates/template` as a TinyBus module for every supported
208+
`vX.Y.Z`, builds `crates/tinysecurity-module` as a TinyBus module for every supported
299209
platform, pushes, and creates an immutable GitHub release with installable
300210
native packages.
301211

0 commit comments

Comments
 (0)