Repository navigation
Build tinysecurity: one bus module for policy, approvals, auto-approve, audit, redaction and sandbox planning #1
Description
Tracker in OpenHuman: tinyhumansai/openhuman#7328 · Sandbox execution side: tinyhumansai/tinybox#27
Goal
Turn this repo (currently the unrenamed Rust template) into tinysecurity: one TinyBus module that owns every policy, approval, auto-approve, approval-log, redaction/privacy, audit and sandbox-planning decision OpenHuman makes today. OpenHuman currently spreads this over crates/openhuman-core/src/security/ (~21.5k LOC non-test), sandbox/, three or four tool-policy layers, about eight redactors and four SSRF checks, with several config fields that nothing enforces. Reference designs: tinywallet (contract crate + module cdylib), tinysearch (MODULE.md, credentials via init/reinit config), and NVIDIA OpenShell (declarative versioned policy, policy-change review, credentials injected only at approved endpoints).
Non-negotiable delivery rule: bus-only
tinysecurity is compiled and loaded as a TinyBus native module. Its dependencies are not shared with the host.
- The host (OpenHuman) depends on exactly one crate from this repo:
tinysecurity-bus(names, payload types, errors, contract version; sync, no I/O, no runtime, no transport). CI asserts the bus crate stays transport-free (template check) and OpenHuman adds a manifest check that no othertinysecurity-*crate is a dependency. - Every other crate here is
publish = falseand internal to the cdylib:regex, SQLite, HTTP client,tinyinference-decisions, etc. are compiled into the module and never unified with the host graph. - Rust traits cannot cross the cdylib boundary, so "seams" are bus contracts: host-served callbacks (approval persistence on the host storage backend, approval prompt/await, audit forwarding, keyring consent; the
memory_host.rspattern) and init/reinit config (policy config; the Jev judge's endpoint and credential, like tinysearch credentials; never in call arguments). - Futures and task-locals do not cross the bus: requests carry origin/thread/flow/agent context.
RequireApproval(request_id)returns to the host, which parks the call and prompts;Decideresumes it. The module owns the state machine, TTLs and log. - Fail closed everywhere. Module absent, faulted or timed out ⇒ external-effect calls are denied; the module is never retried after a fault in-process.
Workspace layout
crates/
tinysecurity-bus/ contract (the only crate hosts see)
tinysecurity-policy/ rules, command/path classification, floor, rate limit, Decision
tinysecurity-approval/ request state machine, TTL, decisions, flow trust, approval log
tinysecurity-auto/ auto-approve engine + Jev judge
tinysecurity-redact/ one redactor, PII, URL/args, prompt-injection
tinysecurity-egress/ privacy modes, descriptors, SSRF/private-host guard
tinysecurity-sandbox/ SandboxPlan resolution (no execution)
tinysecurity-audit/ audit event model, sinks, rotation
tinysecurity-crypto/ (phase 3) password crypto, keyring backends, device crypto, pairing
tinysecurity/ umbrella lib (internal)
tinysecurity-module/ cdylib, ABI v1 exports, bus adapter
Bus: ai.tinyhumans.tinysecurity.Policy, object /ai/tinyhumans/tinysecurity/Policy. Follow tinywallet's layout, lints (unsafe_code = "forbid", clippy pedantic, no unwrap/panic in library code), mod.rs/types.rs/*_tests.rs structure, and copy ci.yml/release.yml from tinywallet.
Step-by-step plan
Phase 0: Scaffold (T0)
- Rename
crates/template*→ names above; setdescription,repository, keywords; updateMODULE.md(bus name, members, contract version, credentials-in-init-config statement, fail-closed statement),AGENTS.mdproject-structure section,SECURITY.mdcontact, license check. - Replace
ROADMAP.mdwith the real phases; adddocs/specs/,docs/plans/, ADRs: 0002 bus-only delivery, 0003 fail-closed semantics, 0004 policy-vs-sandbox split (plan here, execute in tinybox), 0005 Jev judge safety rules. - Keep
vendor/tinybuspinned. Remove thegreetingplaceholder from both crates.
Phase 1: Contract tinysecurity-bus (T1)
Members (all request/response types versioned, #[non_exhaustive] where evolution is expected):
Init/Reinitconfig:PolicyConfig(autonomy level,enabled, workspace/trusted roots, forbidden paths, allowed commands, rate limit, cost cap, privacy mode, approval TTLs, sandbox defaults, auto-approve config incl. judge endpoint/credential/thresholds, audit config).Evaluate(ToolCall{tool, args, class, origin, thread, flow, agent, call_id, tier})→Decision {Allow, RequireApproval{request_id, reason}, Deny{reason}};Check(batched path/command/url checks for in-tool use);PolicyChangedgeneration counter so hosts cache by (generation, fingerprint).- Approvals:
Decide,ListPending,ListDecisions,RecordExecution,PreauthorizeFlow,ParseReply. Redact,ScanPrompt,ScanToolDefinition,EgressCheck,UrlGuard.Plan(SandboxRequest)→SandboxPlan.Auditappend/query.- Host-served callback interfaces:
ApprovalStore,ApprovalPrompt,AuditSink,KeyringConsent. - Contract version and the host bind rule. Latency budget documented per member (hot path:
Evaluate,Check).
Acceptance: contract crate compiles with only serde + thiserror; round-trip serde tests for every type; golden wire fixtures committed.
Phase 2: Pure engines (each its own PR, with tests ported from OpenHuman and then deleted there)
- T2 policy. Port
security/policy/{types,command_checks,path_checks,enforcement}.rsminus config binding. Build the rule model ontinytoolsToolRules/ApprovalDirective(singletinytoolscopy via the vendored tinyagents; do not add a second). Absorbtinybox_core::shell::{classify,scan,env_guard}here as command classification (policy, not sandboxing). Keepis_always_forbidden(credential stores, system roots,.., NUL) as an unconditional floor even when autonomy is disabled. Implementmax_cost_per_day_centsfor real or drop the field (decide in the ADR). Add the action-rate limiter. - T3 redact. One pattern registry replacing:
security/core.rs,util/redact.rs,approval/redact.rs,security/scrub.rs(tinymemory safety policy; decide to wrap or absorb),pii,core/log_redaction.rs,tools/registry/denials.rs,integrations/composio/tools/redact.rs, the vendorcredential_scrubpattern list. Modes: secret-token, PII/identifiers, URL/endpoint, home-path, key-based args, free text. Absorb the prompt-injection detector andscan_tool_definition. Property tests: idempotence, no-secret-survives corpus, no catastrophic regex backtracking (fuzz). - T4 egress. Privacy modes (
LocalOnly,Standard,Sensitive; defineSensitivefor real or delete it),EgressDescriptor/DataKind/IdentificationRisk, local-only enforcement, SSRF guard: link-local 169.254.0.0/16 (cloud metadata), CGNAT, IPv6 ULA/link-local, IPv4-mapped IPv6, decimal/octal/hex host forms, and a resolved-IP check (resolve, validate every address, pin the connection) to defeat DNS rebinding. Domain allowlists (http_request, browser). - T5 sandbox planning.
SandboxRequest{command class, source trust tier (user/agent/mcp/skill/download), saas, env override, agent mode, config, needs_network, grants}→SandboxPlan{backend (None/Jail/Namespace/MicroVm/Docker), grants, network policy, resources, reason}. Rules: SaaS always sandboxed andOPENHUMAN_SANDBOX=offnever applies under SaaS; untrusted tiers fail closed (no silent Noop); credential-store floor excluded from every grant; host-network requires approval. The module does not execute: the host maps the plan onto tinybox. See Make tinybox sandbox-only: capability honesty, resource limits, network policy, events, bus contract (Linux/macOS/Windows) tinybox#27. - T6 audit. Event model for approvals, shell, sandbox decisions, policy changes, judge verdicts; JSONL sink with rotation; callback forwarding to the host
AuditSink; one log, configurable (enabled, path, max size), covering more than shell.auto_approve_allmust still write audit rows.
Phase 3: Approval and auto-approve
- T7 approval. Port the
ApprovalGatestate machine (parked → decided/expired/executed), TTLs (default 10 min; copilot and sub-agent 3 min), decisionsapprove_once/approve_always_for_tool/approve_always_for_flow/deny, flow trust,parse_approval_reply, restart-survival through the host-servedApprovalStore(the module does not own SQLite on the host's behalf), redaction of everything persisted/broadcast, terminal execution-outcome rows. Origin rules (cron: read-only tools only, external effects denied). - T8 auto (Jev). Rule layer: allowlist, flow trust, origin policy. Judge layer on
tinyinference-decisions(Client,EvaluationRequest::jev,Question::{Choice, Score, Noul}): send redacted call state with 2–3 independent questions (does this match the user's request, is it reversible, does it exfiltrate data). That crate deliberately does not infer permission from a probability, so thresholds live here, per origin and class. Hard rules: Jev can only convertRequireApproval→Allow; never overridesDenyor the floor; timeout/error/low confidence/unreachable ⇒ human approval; every verdict logged with question, scores, threshold (auto:jev); judge input goes through the redactor first; off by default, per-origin opt-in; judge endpoint/credential only via init config; cache + budget cap to keep it cheap. Tests with a local stub judge on all three OSes (no real service).
Phase 4: Module and release
- T9 module.
tinysecurity-modulecdylib with ABI v1 exports,provides/methods, host-callback client, init/reinit handling, single-fault policy (no retries after a fault), attestation. Release workflow per tinywallet (11 host keys,checksum.toml), release smoke test that loads the published archive through TinyBus and callsEvaluatefor both an allow and a deny. - T10 policy-change review ("prover" light). Versioned declarative policy file + schema; diff two policies; flag changes that grant new reach (new path, host, tool, class, auto-approve origin, judge enablement) and require human approval before activation.
PolicyChangedbumps the generation. - T11 crypto (last). Move
encryption/core(Argon2id + AES-256-GCM),keyringbackends and crypto,devices/crypto(X25519/HKDF tunnel),pairingguard. Unify three crypto stacks into one; keep keychain consent callbacks host-side.
T12 Quality gates
Conformance suite over the bus contract; fuzz targets (command parser, path normalizer, redactor, URL parser); property tests; 90% per-file coverage gate; cargo deny; MSRV build; rustdoc -D warnings.
Cross-OS end-to-end tests (required)
Every phase ships Linux, macOS and Windows e2e tests on real runners (no mocking of OS behavior). Matrix: ubuntu-24.04, ubuntu-22.04, macos-15/macos-26 (Intel + Apple Silicon where the release matrix has them), windows-2025/windows-11-arm. Skips are listed explicitly per OS in-repo with a reason, never silent.
| Area | Linux | macOS | Windows |
|---|---|---|---|
| Path policy | ~/.ssh/~/.aws/~/.gnupg floor; symlink, .. and NUL escapes; case-sensitive FS |
case-insensitive APFS; /private/var, /var, /tmp symlinks; firmlinks; /Users/x |
drive letters, UNC, \\?\, 8.3 short names, reserved names (CON, NUL), alternate data streams, case-insensitivity, C:\Users\x |
| Command classification | sh/bash/dash, $(…), heredocs (quoted = data, unquoted = scanned) |
zsh default, sh as bash |
PowerShell and cmd.exe quoting, ^ escapes, &/&&, .exe/.bat resolution |
| Redaction | /home/x strip, env var forms |
/Users/x |
C:\Users\x, %USERPROFILE%, backslash JSON escaping |
| SSRF guard | resolve + pin; IPv6 | same (system resolver differences) | same (Windows resolver, IPv6 zone IDs) |
| Sandbox planning | Landlock/namespace/microvm/docker selection by capability | Seatbelt/docker; namespace/microvm ⇒ clean Unsupported |
AppContainer/docker; namespace/microvm ⇒ clean Unsupported; untrusted tier fails closed without a real jail |
| Audit log | rotation, permissions 0600 | same | ACL-restricted file, file locking during rotation, long paths |
| Approval lifecycle | park → decide → TTL → restart survival → execution row | same | same (file-lock and path differences) |
| Module load | .so + digest admission |
.dylib + digest admission (+ codesign/quarantine notes) |
.dll + digest admission; locked-file replacement |
| Crypto/keyring | Secret Service / encrypted file | Keychain | Credential Manager |
| Jev judge (stub) | timeout/error ⇒ human | same | same |
Definition of done
- Contract published; golden fixtures; latency budget documented and benchmarked (p50/p99
Evaluate/Check). - Every crate has tests ported from OpenHuman plus new ones; three-OS e2e green in CI.
- Released archive loads through TinyBus on all release-matrix hosts;
checksum.tomlpublished. - OpenHuman has migrated (see tracker) and the old code and tests there are deleted.
- Documentation:
MODULE.md, specs, ADRs, an OpenHuman gitbook page.
Activity
tinysweeper commentedon Oct 10, 2026
Large roadmap-style proposal to build a new tinysecurity bus module consolidating policy, approvals, redaction, egress, sandbox planning, audit and crypto from OpenHuman into this repo; this is new functionality/architecture work, not a defect.
Labelled
priority: p3.senamakel commentedon Oct 15, 2026
Host tracking: merged PRs #7, #8, #9, #10, #11, #12, #13, #14, #15, #16, #17, #18 are released through v0.7.0, which openhuman PR tinyhumansai/openhuman#7331 pins. Host migration is still in progress (tracker tinyhumansai/openhuman#7328).