Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
## 0.9.9

(unreleased)

## 0.9.7 (unreleased)

### Features
Expand Down
4 changes: 2 additions & 2 deletions binding-core/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jacs-binding-core"
version = "0.9.8"
version = "0.9.9"
edition = "2024"
rust-version = "1.93"
resolver = "3"
Expand All @@ -19,7 +19,7 @@ attestation = ["jacs/attestation"]
pq-tests = []

[dependencies]
jacs = { version = "0.9.8", path = "../jacs" }
jacs = { version = "0.9.9", path = "../jacs" }
serde_json = "1.0"
base64 = "0.22.1"
serde = { version = "1.0", features = ["derive"] }
Expand Down
6 changes: 3 additions & 3 deletions jacs-cli/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jacs-cli"
version = "0.9.8"
version = "0.9.9"
edition = "2024"
rust-version = "1.93"
description = "JACS CLI: command-line interface for JSON AI Communication Standard"
Expand All @@ -23,8 +23,8 @@ attestation = ["jacs/attestation"]
keychain = ["jacs/keychain"]

[dependencies]
jacs = { version = "0.9.8", path = "../jacs" }
jacs-mcp = { version = "0.9.8", path = "../jacs-mcp", features = ["mcp", "full-tools"], optional = true }
jacs = { version = "0.9.9", path = "../jacs" }
jacs-mcp = { version = "0.9.9", path = "../jacs-mcp", features = ["mcp", "full-tools"], optional = true }
clap = { version = "4.5.4", features = ["derive", "cargo"] }
rpassword = "7.3.1"
reqwest = { version = "0.13.2", default-features = false, features = ["blocking", "json", "rustls"] }
Expand Down
2 changes: 1 addition & 1 deletion jacs-duckdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "duckdb", "storage"]
categories = ["database", "data-structures"]

[dependencies]
jacs = { version = "0.9.8", path = "../jacs", default-features = false }
jacs = { version = "0.9.9", path = "../jacs", default-features = false }
duckdb = { version = "1.4", features = ["bundled", "json"] }
serde_json = "1.0"

Expand Down
6 changes: 3 additions & 3 deletions jacs-mcp/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jacs-mcp"
version = "0.9.8"
version = "0.9.9"
edition = "2024"
rust-version = "1.93"
description = "MCP server for JACS: data provenance and cryptographic signing of agent state"
Expand Down Expand Up @@ -45,8 +45,8 @@ tracing = "0.1"
tracing-subscriber = { version = "0.3", features = ["fmt", "env-filter"] }
rmcp = { version = "0.12", features = ["client", "server", "transport-io", "transport-child-process", "macros"], optional = true }
tokio = { version = "1", features = ["rt-multi-thread", "macros", "process", "time"], optional = true }
jacs = { version = "0.9.8", path = "../jacs", default-features = true }
jacs-binding-core = { version = "0.9.8", path = "../binding-core", features = ["a2a"] }
jacs = { version = "0.9.9", path = "../jacs", default-features = true }
jacs-binding-core = { version = "0.9.9", path = "../binding-core", features = ["a2a"] }
serde = { version = "1", features = ["derive"] }
serde_json = "1"
schemars = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion jacs-mcp/contract/jacs-mcp-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
"server": {
"name": "jacs-mcp",
"title": "JACS MCP Server",
"version": "0.9.8",
"version": "0.9.9",
"website_url": "https://humanassisted.github.io/JACS/",
"instructions": "This MCP server provides data provenance and cryptographic signing for agent state files and agent-to-agent messaging. Agent state tools: jacs_sign_state (sign files), jacs_verify_state (verify integrity), jacs_load_state (load with verification), jacs_update_state (update and re-sign), jacs_list_state (list signed docs), jacs_adopt_state (adopt external files). Memory tools: jacs_memory_save (save a memory), jacs_memory_recall (search memories by query), jacs_memory_list (list all memories), jacs_memory_forget (soft-delete a memory), jacs_memory_update (update an existing memory). Messaging tools: jacs_message_send (create and sign a message), jacs_message_update (update and re-sign a message), jacs_message_agree (co-sign/agree to a message), jacs_message_receive (verify and extract a received message). Agent management: jacs_create_agent (create new agent with keys), jacs_reencrypt_key (rotate private key password). A2A artifacts: jacs_wrap_a2a_artifact (sign artifact with provenance), jacs_verify_a2a_artifact (verify wrapped artifact), jacs_assess_a2a_agent (assess remote agent trust level). A2A discovery: jacs_export_agent_card (export Agent Card), jacs_generate_well_known (generate .well-known documents), jacs_export_agent (export full agent JSON). Trust store: jacs_trust_agent (add agent to trust store), jacs_untrust_agent (remove from trust store, requires JACS_MCP_ALLOW_UNTRUST=true), jacs_list_trusted_agents (list all trusted agent IDs), jacs_is_trusted (check if agent is trusted), jacs_get_trusted_agent (get trusted agent JSON). Attestation: jacs_attest_create (create signed attestation with claims), jacs_attest_verify (verify attestation, optionally with evidence checks), jacs_attest_lift (lift signed document into attestation), jacs_attest_export_dsse (export attestation as DSSE envelope). Security: jacs_audit (read-only security audit and health checks). Audit trail: jacs_audit_log (record events as signed audit entries), jacs_audit_query (search audit trail by action, target, time range), jacs_audit_export (export audit trail as signed bundle). Search: jacs_search (unified search across all signed documents)."
},
Expand Down
2 changes: 1 addition & 1 deletion jacs-postgresql/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "postgresql", "storage"]
categories = ["database", "data-structures"]

[dependencies]
jacs = { version = "0.9.8", path = "../jacs", default-features = false }
jacs = { version = "0.9.9", path = "../jacs", default-features = false }
sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio-rustls", "postgres"] }
tokio = { version = "1.0", features = ["rt-multi-thread"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion jacs-redb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ categories.workspace = true
description = "Redb (pure-Rust embedded KV) storage backend for JACS documents"

[dependencies]
jacs = { version = "0.9.8", path = "../jacs", default-features = false }
jacs = { version = "0.9.9", path = "../jacs", default-features = false }
redb = "3.1"
chrono = "0.4.40"
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion jacs-surrealdb/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ keywords = ["cryptography", "json", "surrealdb", "storage"]
categories = ["database", "data-structures"]

[dependencies]
jacs = { version = "0.9.8", path = "../jacs", default-features = false }
jacs = { version = "0.9.9", path = "../jacs", default-features = false }
surrealdb = { version = "3.0.2", default-features = false, features = ["kv-mem"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
Expand Down
2 changes: 1 addition & 1 deletion jacs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "jacs"
version = "0.9.8"
version = "0.9.9"
edition = "2024"
rust-version = "1.93"
resolver = "3"
Expand Down
204 changes: 28 additions & 176 deletions jacs/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@

use crate::error::JacsError;
use crate::schema::utils::{CONFIG_SCHEMA_STRING, EmbeddedSchemaResolver};
use crate::storage::jenv::{EnvError, get_env_var, get_required_env_var, set_env_var_override};
use crate::storage::jenv::{EnvError, get_env_var, get_required_env_var};
use getset::Getters;
use jsonschema::{Draft, Validator};
use serde::Deserialize;
Expand All @@ -15,7 +15,7 @@ use std::fs;
use std::str::FromStr;
use tracing::{error, info, warn};

use crate::validation::{are_valid_uuid_parts, split_agent_id};
use crate::validation::split_agent_id;

/// Source for resolving public keys during signature verification.
///
Expand Down Expand Up @@ -1193,180 +1193,6 @@ pub fn validate_config(config_json: &str) -> Result<Value, JacsError> {
Ok(instance)
}

/// DEPRECATED: Use `load_config_12factor_optional` instead.
///
/// Attempts to find and load a config file from the given path.
/// Falls back to Config::default() if file not found.
#[deprecated(
since = "0.2.0",
note = "Use load_config_12factor_optional() for 12-Factor compliant config loading"
)]
pub fn find_config(path: String) -> Result<Config, JacsError> {
let config: Config = match fs::read_to_string(format!("{}jacs.config.json", path)) {
Ok(content) => {
let validated_value = validate_config(&content)?;
serde_json::from_value(validated_value).map_err(|e| {
JacsError::ConfigError(format!("Failed to deserialize config: {}", e))
})?
}
Err(_) => Config::default(),
};
Ok(config)
}

/// DEPRECATED: Use `load_config_12factor` instead.
///
/// This function takes config file values and sets them as environment variables,
/// which is the OPPOSITE of 12-Factor principles. Environment variables should
/// be the source of truth, not the target.
///
/// This function is kept for backwards compatibility only. New code should use
/// `load_config_12factor()` which reads env vars INTO config (correct direction).
#[deprecated(
since = "0.2.0",
note = "Use load_config_12factor() - env vars should override config, not vice versa"
)]
pub fn set_env_vars(
do_override: bool,
config_json: Option<&str>,
ignore_agent_id: bool,
) -> Result<String, JacsError> {
let config: Config = match config_json {
Some(json_str) => {
let validated_value = validate_config(json_str)?;
serde_json::from_value(validated_value).map_err(|e| {
JacsError::ConfigError(format!("Failed to deserialize config: {}", e))
})?
}
None => find_config(".".to_string())?,
};
// debug!("configs from file {:?}", config);
validate_config(
&serde_json::to_string(&config)
.map_err(|e| JacsError::ConfigError(format!("Failed to serialize config: {}", e)))?,
)?;

// Security: Password should come from environment variable, not config file
if config.jacs_private_key_password.is_some() {
warn!(
"SECURITY WARNING: Password found in config file. \
This is insecure - passwords should only be set via JACS_PRIVATE_KEY_PASSWORD \
environment variable. The password in the config file will be ignored."
);
}
// Do NOT set password from config - it must come from env var only
// The password will be read directly from env var when needed

let jacs_use_security = config
.jacs_use_security
.as_ref()
.unwrap_or(&"false".to_string())
.clone();
set_env_var_override("JACS_USE_SECURITY", &jacs_use_security, do_override)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_data_directory = config
.jacs_data_directory
.as_ref()
.unwrap_or(
&std::env::current_dir()
.map(|p| p.to_string_lossy().to_string())
.unwrap_or_else(|_| "./jacs_data".to_string()),
)
.clone();
set_env_var_override("JACS_DATA_DIRECTORY", &jacs_data_directory, do_override)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_key_directory = config
.jacs_key_directory
.as_ref()
.unwrap_or(&".".to_string())
.clone();
set_env_var_override("JACS_KEY_DIRECTORY", &jacs_key_directory, do_override)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_agent_private_key_filename = config
.jacs_agent_private_key_filename
.as_ref()
.unwrap_or(&"jacs.private.pem.enc".to_string())
.clone();
set_env_var_override(
"JACS_AGENT_PRIVATE_KEY_FILENAME",
&jacs_agent_private_key_filename,
do_override,
)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_agent_public_key_filename = config
.jacs_agent_public_key_filename
.as_ref()
.unwrap_or(&"jacs.public.pem".to_string())
.clone();
set_env_var_override(
"JACS_AGENT_PUBLIC_KEY_FILENAME",
&jacs_agent_public_key_filename,
do_override,
)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_agent_key_algorithm = config
.jacs_agent_key_algorithm
.as_ref()
.unwrap_or(&"pq2025".to_string())
.clone();
set_env_var_override(
"JACS_AGENT_KEY_ALGORITHM",
&jacs_agent_key_algorithm,
do_override,
)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_default_storage = config
.jacs_default_storage
.as_ref()
.unwrap_or(&"fs".to_string())
.clone();
set_env_var_override("JACS_DEFAULT_STORAGE", &jacs_default_storage, do_override)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

let jacs_agent_id_and_version = config
.jacs_agent_id_and_version
.as_ref()
.unwrap_or(&"".to_string())
.clone();

if !jacs_agent_id_and_version.is_empty() {
if let Some((id, version)) = split_agent_id(&jacs_agent_id_and_version) {
if !are_valid_uuid_parts(id, version) {
warn!("ID and Version must be in the form UUID:UUID");
}
} else {
warn!("ID and Version must be in the form UUID:UUID");
}
}

set_env_var_override(
"JACS_AGENT_ID_AND_VERSION",
&jacs_agent_id_and_version,
do_override,
)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;

// Propagate keychain backend setting to env var if set in config
if let Some(ref backend) = config.jacs_keychain_backend {
set_env_var_override("JACS_KEYCHAIN_BACKEND", backend, do_override)
.map_err(|e| JacsError::ConfigError(e.to_string()))?;
}

let message = format!("{}", config);
info!("{}", message);
check_env_vars(ignore_agent_id).map_err(|e| {
error!("Error checking environment variables: {}", e);
JacsError::ConfigError(e.to_string())
})?;
Ok(message)
}

pub fn check_env_vars(ignore_agent_id: bool) -> Result<String, EnvError> {
let vars = [
("JACS_USE_SECURITY", true),
Expand Down Expand Up @@ -1888,6 +1714,32 @@ mod tests {
clear_jacs_env_vars();
}

#[test]
#[serial(jacs_env)]
fn test_apply_env_overrides_preserves_config_dir() {
clear_jacs_env_vars();

let mut config = Config::with_defaults();
let test_dir = std::path::PathBuf::from("/some/config/dir");
config.set_config_dir(Some(test_dir.clone()));

// Set some env overrides to trigger actual work
set_env_var("JACS_DATA_DIRECTORY", "/env/data").unwrap();

config.apply_env_overrides();

// config_dir must survive apply_env_overrides — it is runtime metadata
// that Agent::from_config uses for storage_root calculation.
// If this is wiped, storage resolves to CWD or "/" (Issue 024).
assert_eq!(
config.config_dir(),
Some(test_dir.as_path()),
"config_dir must be preserved through apply_env_overrides"
);

clear_jacs_env_vars();
}

#[test]
fn test_config_builder_defaults() {
// Builder with no options set should produce sensible defaults
Expand Down
2 changes: 1 addition & 1 deletion jacsnpm/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@hai.ai/jacs",
"version": "0.9.8",
"version": "0.9.9",
"description": "JACS (JSON Agent Communication Standard) - Data provenance and cryptographic signing for AI agents",
"main": "index.js",
"types": "index.d.ts",
Expand Down
2 changes: 1 addition & 1 deletion jacspy/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ requires = ["maturin>=1.0,<2.0"]
build-backend = "maturin"
[project]
name = "jacs"
version = "0.9.8"
version = "0.9.9"
description = "JACS - JSON AI Communication Standard: Cryptographic signing and verification for AI agents."
readme = "README.md"
requires-python = ">=3.10"
Expand Down
Loading