Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
9117f41
test(security): characterize RPC contracts and enforce bus-only depen…
senamakel Oct 10, 2026
e2ceeac
Merge remote-tracking branch 'upstream/main' into security-bus-7328
senamakel Oct 10, 2026
1f81c4c
feat(security): vendor native policy and migrate filesystem validators
senamakel Oct 10, 2026
a02e56a
fix(ci): keep release URLs within their module records
senamakel Oct 10, 2026
27fdc80
chore(vendor): include empty-path rejection in TinySecurity
senamakel Oct 10, 2026
6089687
test(embed): initialize keyring at the fixture's runtime workspace
senamakel Oct 10, 2026
76184f9
chore(vendor): pin validated TinySecurity scope construction
senamakel Oct 10, 2026
ca9f85b
chore(vendor): pin fail-closed native path checks
senamakel Oct 10, 2026
1311113
chore(vendor): pin Windows path alias protections
senamakel Oct 10, 2026
16b44cf
chore(vendor): reject native filesystem metadata failures
senamakel Oct 10, 2026
38a2bfa
chore(vendor): pin merged TinySecurity filesystem policy
senamakel Oct 10, 2026
87fb462
Merge remote-tracking branch 'upstream/main' into security-bus-7328
senamakel Oct 10, 2026
66dfdd9
feat(security): pin published modules and exercise release admission
senamakel Oct 10, 2026
23e4baa
docs: repair moved embedding guide links
senamakel Oct 10, 2026
609f023
ci(docs): bound link checker concurrency for upstream reliability
senamakel Oct 10, 2026
c39f926
docs(embed): regenerate current capability reference
senamakel Oct 10, 2026
541a3bf
ci(links): authenticate GitHub link validation
senamakel Oct 10, 2026
e1a275f
Merge remote-tracking branch 'upstream/main' into security-bus-7328
senamakel Oct 10, 2026
70ad98c
docs(security): record remaining consolidated migration tasks
senamakel Oct 10, 2026
f4f0804
fix(sandbox): create grant test symlinks on Windows
senamakel Oct 10, 2026
edc2d93
test(sandbox): detect canonical credential grants
senamakel Oct 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/ci-full.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,9 @@ jobs:
touch .env
touch app/.env

- name: Exercise attested native security adapter and latency budget
shell: bash
run: bash scripts/ci/security-native-fixture.sh
- name: Install checksum-pinned native test modules
run: |
# tinybus validates every directory ancestor. GitHub mounts the
Expand Down
4 changes: 4 additions & 0 deletions .github/workflows/pr-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ jobs:
fetch-depth: 1
- name: Lychee link check
uses: lycheeverse/lychee-action@v2
env:
# Use authenticated API checks instead of anonymous GitHub page requests.
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
# Product Hunt denies GitHub-hosted runners (403) for the translated
# README badge destination, so it cannot be a stable external-link
Expand All @@ -72,6 +75,7 @@ jobs:
# its provider-side redirect is restored.
args: >-
--no-progress
--max-concurrency 8
--include-fragments
--exclude '^http://localhost'
--exclude '^https?://127\.0\.0\.1'
Expand Down
54 changes: 54 additions & 0 deletions .github/workflows/security-native.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
---
# Candidate-source integration checks. Released-artifact admission remains a
# separate requirement before the dependent migration is ready to ship.
name: Security Native

on:
pull_request:
branches: [main, release]
paths:
- .github/workflows/security-native.yml
- .gitmodules
- vendor/tinysecurity
- crates/openhuman-core/src/modules/security*.rs
- crates/openhuman-core/src/modules/registry/records_security.rs
- crates/openhuman-core/src/security/policy/**
- scripts/ci/security-native-fixture.sh
- Cargo.lock
workflow_dispatch: {}

permissions:
actions: read
contents: read

concurrency:
group: security-native-${{ github.event.pull_request.number || github.ref }}
cancel-in-progress: true

jobs:
native:
name: Native policy (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15, windows-2025]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
env:
CARGO_BUILD_JOBS: "2"
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
submodules: recursive
- name: Install Linux inference prerequisites
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
workspaces: . -> target
key: security-native-${{ runner.os }}
- name: Exercise attested adapter and latency budget
shell: bash
run: bash scripts/ci/security-native-fixture.sh
38 changes: 38 additions & 0 deletions .github/workflows/test-reusable.yml
Original file line number Diff line number Diff line change
Expand Up @@ -283,6 +283,44 @@ jobs:
fi
done < <(integration_test_targets)

security-characterization:
if: inputs.run_rust_core
name: Security characterization (${{ matrix.os }})
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04, macos-15, windows-2025]
runs-on: ${{ matrix.os }}
timeout-minutes: 60
env:
CARGO_BUILD_JOBS: "2"
CARGO_PROFILE_DEV_DEBUG: line-tables-only
steps:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
submodules: recursive
- name: Install Linux inference test prerequisites
if: runner.os == 'Linux'
run: sudo apt-get update && sudo apt-get install -y libasound2-dev pkg-config
- uses: Swatinem/rust-cache@v2
with:
workspaces: . -> target
key: security-characterization-${{ runner.os }}
- name: Pin current security wire and redaction behavior
shell: bash
run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman-cli --no-default-features --test security_characterization_e2e
- name: Exercise private redactors with the shared corpus
shell: bash
run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman --no-default-features --features inference,web3,modules shared_security -- --nocapture
- name: Exercise attested native security adapter and latency budget
shell: bash
run: bash scripts/ci/security-native-fixture.sh
- name: Record existing in-process latency baseline
shell: bash
run: bash scripts/ci-cancel-aware.sh cargo test -p openhuman-cli --no-default-features --test security_characterization_e2e in_process_policy_latency_baseline -- --nocapture

rust-core-tests-windows:
if: inputs.run_rust_core
name: Rust Core Tests (Windows — secrets ACL)
Expand Down
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -63,3 +63,6 @@
[submodule "vendor/tinymemes"]
path = vendor/tinymemes
url = https://github.com/tinyhumansai/tinymemes.git
[submodule "vendor/tinysecurity"]
path = vendor/tinysecurity
url = https://github.com/tinyhumansai/tinysecurity
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -545,6 +545,7 @@ Direct rendered submodules under `vendor/`:
| --- | --- |
| `tinyagents` | Provider-neutral agent harness and durable typed state graph: model/tool loop, tool-call dialects and parsing, middleware, retries, caching, sessions/transcripts, and graph execution. |
| `tinybox` | Isolated execution environments for code the host does not trust; box lifecycle and isolation backends. |
| `tinysecurity` | Native security policy engines and their transport-free bus contract. OpenHuman owns trusted configuration translation, product RPCs, and host execution adapters. Hosts link `tinysecurity-bus` only. |
| `tinybus` | TinyBus runtime and module contracts: discovery/loading, ABI and manifest admission, transport, proxies, lifecycle, and module bus behavior. |
| `tinychannels` | Portable channel/message contracts, configuration/schema, routing metadata, and channel backend abstractions. OpenHuman owns its concrete product/backend adapters. |
| `tinyconnectors` | OAuth connector module behavior: account linking, available actions, action execution, and connector webhooks. |
Expand Down
16 changes: 13 additions & 3 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ Most agent harnesses run one heavy process per agent and resend a big prompt on

<h3>Built for developers</h3>

<p><a href="https://tinyhumans.gitbook.io/openhuman/developing/quickstart">Rust quickstart</a> · <a href="https://tinyhumans.gitbook.io/openhuman/developing/embedding">Embedding guide</a> · <a href="./crates/openhuman-embed/examples">Examples</a></p>
<p><a href="https://tinyhumans.gitbook.io/openhuman/developing/quickstart">Rust quickstart</a> · <a href="https://tinyhumans.gitbook.io/openhuman/developing/embed">Embedding guide</a> · <a href="./crates/openhuman-embed/examples">Examples</a></p>

<p>Use it as a Rust library: call an agent like any other function, or run a whole fleet from one small server.</p>

Expand Down Expand Up @@ -288,7 +288,7 @@ let reply = agent.run("Summarize what you can see in this directory.").await?;
println!("{}", reply.reply);
```

Next: the [Rust quickstart](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), the [embedding guide](https://tinyhumans.gitbook.io/openhuman/developing/embedding) and the [developer docs](https://tinyhumans.gitbook.io/openhuman/developing).
Next: the [Rust quickstart](https://tinyhumans.gitbook.io/openhuman/developing/quickstart), the [embedding guide](https://tinyhumans.gitbook.io/openhuman/developing/embed) and the [developer docs](https://tinyhumans.gitbook.io/openhuman/developing).

---

Expand Down
1 change: 1 addition & 0 deletions crates/openhuman-app/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ openhuman-rpc = { path = "../openhuman-rpc", default-features = false, features
"web3",
"documents",
"modules",
"security-module",
"flows",
"skills",
"mcp",
Expand Down
5 changes: 5 additions & 0 deletions crates/openhuman-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,10 @@ path = "../../tests/memory_cortexdb_live.rs"
# Skips unless OPENHUMAN_LIVE_CORTEXDB_URL is set; run it with
# scripts/test-memory-cortexdb-live.sh, which boots the server in Docker.

[[test]]
name = "security_characterization_e2e"
path = "../../tests/security_characterization_e2e.rs"

[[test]]
name = "json_rpc_e2e"
path = "../../tests/json_rpc_e2e.rs"
Expand Down Expand Up @@ -374,6 +378,7 @@ documents = ["openhuman-rpc/documents"]
hosting = ["openhuman-rpc/hosting"]
tinymemes = ["openhuman-rpc/tinymemes"]
modules = ["openhuman-rpc/modules"]
security-module = ["openhuman-rpc/security-module"]
voice = ["openhuman-rpc/voice"]
web3 = ["openhuman-rpc/web3"]
# Storage drivers for `[storage] url` (see the core `storage` domain).
Expand Down
2 changes: 2 additions & 0 deletions crates/openhuman-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -320,6 +320,7 @@ tinychannels = { version = "0.1", features = ["relay-websocket"], optional = tru
# never loads a module. The kernel-floor ratchet catches exactly that, and did.
tinybus = { path = "../../vendor/tinybus/crates/tinybus", default-features = false, features = ["macros", "uds"] }
tinysearch-bus = { path = "../../vendor/tinysearch/crates/tinysearch-bus" }
tinysecurity-bus = { path = "../../vendor/tinysecurity/crates/tinysecurity-bus", optional = true }
tinycomputer-bus = { path = "../../vendor/tinycomputer/crates/tinycomputer-bus", optional = true }
# Desktop accessibility middleware, linked as a plain library (not through the
# tinycomputer module): the voice pipeline needs focus lookup, paste validation,
Expand Down Expand Up @@ -832,6 +833,7 @@ tinymemes = ["dep:tinymemes"]
# tinybus never unloads one. See `crates/openhuman-core/src/modules/` before adding an entry
# to the registry.
modules = ["tinybus/modules", "dep:tinycomputer-bus"]
security-module = ["modules", "dep:tinysecurity-bus"]
# Voice + audio_toolkit domains: STT/TTS providers, the standalone dictation
# server, always-on listening, and podcast audio generation/email delivery.
# Default-ON — the desktop app always ships with voice. Slim / headless builds
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
use super::*;

pub(super) fn assert_session_cache_eviction() {
// Other modules register completions without TEST_ENV_LOCK. Their inserts
// legitimately evict older sessions from this process-wide bounded cache,
// so exercise the exact eviction boundary in a process with no other tests.
const CHILD: &str = "OPENHUMAN_SESSION_CACHE_EVICTION_TEST_CHILD";
if std::env::var_os(CHILD).is_none() {
let output = std::process::Command::new(std::env::current_exe().unwrap())
.args([
"--exact",
"agent::orchestration::background_completions::tests::the_session_cache_evicts_its_oldest_mapping_only",
"--nocapture",
])
.env(CHILD, "1")
.output()
.unwrap();
assert!(
output.status.success(),
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
return;
}
let _guard = test_guard();
for i in 0..(SESSION_THREADS_CAP + 5) {
note_session_thread(&format!("evict-sess-{i}"), &format!("evict-thread-{i}"));
}
assert_eq!(thread_for_session("evict-sess-0"), None, "oldest evicted");
for survivor in [5, SESSION_THREADS_CAP / 2, SESSION_THREADS_CAP + 4] {
assert_eq!(
thread_for_session(&format!("evict-sess-{survivor}")).as_deref(),
Some(format!("evict-thread-{survivor}").as_str()),
"a surviving session still resolves to its own thread"
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -534,20 +534,12 @@ fn clear_all_also_withdraws_a_completion_a_delivery_has_leased() {

#[test]
fn the_session_cache_evicts_its_oldest_mapping_only() {
let _guard = test_guard();
for i in 0..(SESSION_THREADS_CAP + 5) {
note_session_thread(&format!("evict-sess-{i}"), &format!("evict-thread-{i}"));
}
assert_eq!(thread_for_session("evict-sess-0"), None, "oldest evicted");
for survivor in [5, SESSION_THREADS_CAP / 2, SESSION_THREADS_CAP + 4] {
assert_eq!(
thread_for_session(&format!("evict-sess-{survivor}")).as_deref(),
Some(format!("evict-thread-{survivor}").as_str()),
"a surviving session still resolves to its own thread"
);
}
isolation::assert_session_cache_eviction();
}

#[path = "background_completions_isolation_tests.rs"]
mod isolation;

#[test]
fn a_delete_marker_outlives_compaction() {
let _guard = test_guard();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,3 +62,29 @@ fn source_code_about_tokens_passes_through_the_host_scrubber() {
assert_eq!(count, 1);
assert!(!scrubbed.contains("hunter2secret"));
}

#[test]
fn shared_security_corpus_pins_credential_middleware_catches_and_gaps() {
let corpus: serde_json::Value = serde_json::from_str(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../tests/fixtures/security-redaction-corpus.json"
)))
.unwrap();
for case in corpus["text"].as_array().unwrap() {
let input = case["input"].as_str().unwrap();
let output = scrub_with_notice_for_tool("read_file", input)
.map(|(text, _)| text)
.unwrap_or_else(|| input.to_owned());
let removed = case["removed_by"]
.as_array()
.unwrap()
.iter()
.any(|redactor| redactor == "credential_middleware");
assert_eq!(
!output.contains(case["needle"].as_str().unwrap()),
removed,
"{}: {output}",
case["case"]
);
}
}
15 changes: 15 additions & 0 deletions crates/openhuman-core/src/config/schema/load_migration_tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -664,3 +664,18 @@ fn resolve_action_dir_rejects_empty_override() {
"empty override must be ignored, falling back to default"
);
}

#[test]
fn shared_security_url_corpus_pins_migration_redactor() {
let corpus: serde_json::Value = serde_json::from_str(include_str!(concat!(
env!("CARGO_MANIFEST_DIR"),
"/../../tests/fixtures/security-redaction-corpus.json"
)))
.unwrap();
for case in corpus["urls"].as_array().unwrap() {
assert_eq!(
redact_url_for_log(case["input"].as_str().unwrap()),
case["migration"]
);
}
}
Loading
Loading