Skip to content

v0.9.7: OS keychain integration, password resolution refactor, test parallelism #60

Merged
Hendler merged 22 commits intomainfrom
v0.9.7
Mar 19, 2026
Merged

v0.9.7: OS keychain integration, password resolution refactor, test parallelism #60
Hendler merged 22 commits intomainfrom
v0.9.7

Conversation

@Hendler
Copy link
Copy Markdown
Contributor

@Hendler Hendler commented Mar 17, 2026

  • OS keychain support: New keychain feature stores private key passwords in macOS Keychain or Linux Secret Service (D-Bus). CLI jacs keychain {set,get,delete,status} commands. Config field jacs_keychain_backend with
    auto/disabled modes.
  • Centralized password resolution: Single resolve_private_key_password() function with priority chain: env var → explicit password → OS keychain → error. Removes scattered get_required_env_var calls. Agents gain
    set_password() and load_by_config_file_only() to avoid env var mutation.
  • Agent loader consolidation: DRY refactor of config loading. Password file conflict downgraded from hard error to warning (fixes OpenClaw integration).
  • Storage fixes: Doubled-path bug when data/key directory is absolute inside storage root. list() no longer returns root-prefixed paths that break get_file(). Schema resolution preserves leading / for absolute filesystem
    paths.
  • Test parallelism: All 148 bare #[serial] annotations replaced with keyed groups (jacs_env, home_env, cwd_env, etc.). CI splits Rust tests into 3 parallel shards. Guardrail script prevents regression. Two pre-existing
    race conditions fixed in simple/mod.rs and dns/bootstrap.rs.
  • Security review: Hardened path traversal checks, env var isolation, trust store handling.

Test plan

  • cargo test -p jacs --features attestation — core tests with keyed serial groups
  • cargo test -p jacs-binding-core --features a2a,attestation — binding contract tests
  • cd jacspy && pytest — Python bindings including PQ2025 agreement flow
  • cd jacsnpm && npm test — Node.js bindings
  • ./scripts/check-no-bare-serial.sh — no bare #[serial] annotations
  • Keychain tests require real OS keychain (manual/local only, not CI)

Hendler added 22 commits March 17, 2026 08:46
… loaded-agent metadata now comes from one canonical Rust path instead of being rebuilt by Python, Node, or MCP wrappers. That lives in jacs/src/simple/core.rs and is surfaced through binding-core/src/lib.rs and binding-core/src/simple_wrapper.rs.

On top of that, jacspy/python/jacs/client.py now uses native load_with_info, jacspy/python/jacs/simple.py delegates load/quickstart through JacsClient, jacsnpm/client.ts uses native loadWithInfo, jacsnpm/simple.ts adopts client state instead of reparsing config, and jacs-mcp/src/config.rs now loads through the same shared contract instead of manually rewriting config/env state. I also added parity/characterization tests in the Rust, Python, Node, and MCP suites.

Verified:

cargo test -p jacs-binding-core --test contract --test simple_wrapper
cargo test -p jacs-mcp --test config_loading
PYTHONPATH=/Users/jonathan.hendler/personal/JACS/jacspy/python uv run pytest jacspy/tests/test_client.py
targeted Node loader tests via npx mocha for the touched client/simple quickstart/load cases
One broader Node run of test/client.test.js test/simple.test.js still hit an unrelated existing permission failure in a trust-store agreement test writing under ~/Library/Application Support/jacs/...; the load-refactor-specific tests passed.
…nfig_after_cwd_change -- --nocapture

cargo test -p jacs-binding-core --test contract --test simple_wrapper
cargo build -p jacs-cli
cargo test -p jacs-mcp --test config_loading
cargo test -p jacs-mcp --test integration mcp_nested_config_allows_state_files_under_loaded_data_root -- --nocapture
cargo test -p jacs-mcp --test integration mcp_sign_state_rejects_file_outside_allowed_roots -- --nocapture
env PYTHONPATH=/Users/jonathan.hendler/personal/JACS/jacspy/python uv run pytest jacspy/tests/test_client.py
npm run build in jacsnpm/
direct Node runtime assertions for quickstart canonical paths and “no config reopen on load”; the targeted mocha invocation was hanging on open handles in this environment, so I used a runtime assertion script instead.
     - /Users/jonathan.hendler/personal/JACS/jacs/src/keystore/mod.rs - KeyPaths struct, FsEncryptedStore refactored from unit struct to stateful struct, 8 new tests
     - /Users/jonathan.hendler/personal/JACS/jacs/src/crypt/aes_encrypt.rs - resolve_private_key_password() gains explicit_password: Option<&str> parameter, 5 new tests
     - /Users/jonathan.hendler/personal/JACS/jacs/src/schema/utils.rs - check_filesystem_schema_access() accepts config: Option<&Config>
     - /Users/jonathan.hendler/personal/JACS/jacs/src/agent/mod.rs - Agent struct gains key_paths, password fields plus getters/setters/helpers
     - /Users/jonathan.hendler/personal/JACS/jacs/src/crypt/mod.rs - All FsEncryptedStore instantiations use self.build_fs_store()
     - /Users/jonathan.hendler/personal/JACS/jacs/src/simple/core.rs - create_with_params removes EnvRestoreGuard, CREATE_MUTEX, all unsafe set_var
     - /Users/jonathan.hendler/personal/JACS/jacs/src/agent/loaders.rs - save_private_key uses canonical resolver
     - /Users/jonathan.hendler/personal/JACS/jacs/src/config/mod.rs - publish_to_env() deleted
     - /Users/jonathan.hendler/personal/JACS/jacs/src/cli_utils/create.rs - publish_to_env() call removed

     Binding core:
     - /Users/jonathan.hendler/personal/JACS/binding-core/src/lib.rs - ScopedPrivateKeyEnv deleted, private_key_env_lock deleted, with_private_key_password uses Agent.set_password(), standalone verify uses JenvGuard instead of unsafe
     EnvGuard

     MCP:
     - /Users/jonathan.hendler/personal/JACS/jacs-mcp/src/jacs_tools.rs - configured_state_roots() accepts explicit data_directory

     Result: Zero unsafe std::env::set_var in library code. Multi-agent-in-one-process is now safe.
… for nonexistent docs

FS backend returns StorageError, SQLite returns DocumentError for get/remove
on nonexistent documents. The tests asserted discriminant equality which fails.
Updated to verify both backends error (the actual contract) and log the
variant mismatch for a future error-parity alignment pass.
make_data_directory_path and make_key_directory_path now strip the storage
root prefix from absolute directory paths before passing to the storage
backend, preventing the root from being prepended twice.

Added MultiStorage::root() getter for filesystem_base_dir.
…get_file()

LocalFileSystem ObjectPath strips the leading '/' from absolute paths, so
list() returns locations like "Users/foo/bar" instead of relative paths.
get_file() then treats these as relative and prepends the storage root again.

Fix: strip the filesystem_base_dir prefix from list() results so callers
get paths relative to the storage root.

Also switch test fixture env vars to relative paths since Agent::new()
roots storage at CWD.
The schema resolver strips the leading '/' from absolute paths then
passes the result to storage as a relative path, causing path doubling.
Use a relative path from the crate root instead.
@Hendler Hendler changed the title add more security to password storage v0.9.7: OS keychain integration, password resolution refactor, test parallelism Mar 19, 2026
@Hendler Hendler merged commit 2048367 into main Mar 19, 2026
20 checks passed
@Hendler Hendler deleted the v0.9.7 branch March 19, 2026 15:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant