Skip to content
Open
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
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ resolver = "3"
rust-version = "1.93"
readme = "README.md"
authors = ["HAI.AI <engineering@hai.io>"]
license = "Apache-2.0 OR MIT"
license = "Apache-2.0"
homepage = "https://humanassisted.github.io/JACS"
repository = "https://github.com/HumanAssisted/JACS"
keywords = ["cryptography", "json", "ai", "data", "ml-ops"]
Expand Down
4 changes: 2 additions & 2 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
This project is dual-licensed under Apache-2.0 OR MIT, at your option.
This project is licensed under Apache-2.0.

See LICENSE-APACHE and LICENSE-MIT for details.
See LICENSE-APACHE for details.

Copyright 2024, 2025, 2026 Human Assisted Intelligence, PBC
21 changes: 0 additions & 21 deletions LICENSE-MIT

This file was deleted.

12 changes: 6 additions & 6 deletions LINES_OF_CODE.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Language Files Lines Code Comments Blanks
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Go 13 5859 4344 708 807
Python 188 46396 36376 2037 7983
Go 16 6521 4827 813 881
Python 191 46952 36823 2064 8065
TypeScript 30 9105 6380 2000 725
─────────────────────────────────────────────────────────────────────────────────
Rust 258 106330 87477 6054 12799
|- Markdown 213 9703 491 7231 1981
(Total) 116033 87968 13285 14780
Rust 262 107208 88246 6071 12891
|- Markdown 217 9784 491 7296 1997
(Total) 116992 88737 13367 14888
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Total 489 177393 135068 18030 24295
Total 499 179570 136767 18244 24559
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,4 @@ Report vulnerabilities to security@hai.ai. Do not open public issues for securit

---

v0.9.7 | [Apache-2.0 OR MIT](./LICENSE-APACHE) | [Third-Party Notices](./THIRD-PARTY-NOTICES)
v0.9.7 | [Apache-2.0](./LICENSE-APACHE) | [Third-Party Notices](./THIRD-PARTY-NOTICES)
46 changes: 23 additions & 23 deletions THIRD-PARTY-NOTICES
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ THIRD-PARTY SOFTWARE NOTICES AND INFORMATION

JACS incorporates third-party software components. The following notices
are provided for informational purposes. JACS is licensed under
Apache-2.0 OR MIT (see LICENSE-APACHE and LICENSE-MIT).
Apache-2.0 (see LICENSE-APACHE).

===============================================================================

Expand Down Expand Up @@ -679,28 +679,28 @@ THE SOFTWARE IS PROVIDED "AS IS" AND ISC DISCLAIMS ALL WARRANTIES WITH REGARD TO
-------------------------------------------------------------------------------
--- MIT ---

The MIT License (MIT)
Copyright (c) 2016 Johann Tuffe
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
The MIT License (MIT)

Copyright (c) 2016 Johann Tuffe

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:


The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.


THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

-------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion binding-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ resolver = "3"
description = "Shared core logic for JACS language bindings (Python, Node.js, etc.)"
readme = "../README.md"
authors = ["JACS Contributors"]
license = "Apache-2.0 OR MIT"
license = "Apache-2.0"
repository = "https://github.com/HumanAssisted/JACS"
homepage = "https://humanassisted.github.io/JACS"

Expand Down
6 changes: 3 additions & 3 deletions deny.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,17 @@ allow = [
# Workspace crates without explicit license field use the workspace license
[[licenses.clarify]]
name = "jacsnpm"
expression = "Apache-2.0 OR MIT"
expression = "Apache-2.0"
license-files = []

[[licenses.clarify]]
name = "jacspy"
expression = "Apache-2.0 OR MIT"
expression = "Apache-2.0"
license-files = []

[[licenses.clarify]]
name = "jacsgo"
expression = "Apache-2.0 OR MIT"
expression = "Apache-2.0"
license-files = []

# ring contains legacy OpenSSL-licensed code alongside ISC/Apache-2.0
Expand Down
2 changes: 1 addition & 1 deletion jacs-cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ rust-version = "1.93"
description = "JACS CLI: command-line interface for JSON AI Communication Standard"
readme = "README.md"
authors = ["HAI.AI <engineering@hai.io>"]
license = "Apache-2.0 OR MIT"
license = "Apache-2.0"
homepage = "https://humanassisted.github.io/JACS"
repository = "https://github.com/HumanAssisted/JACS"
keywords = ["cryptography", "json", "ai", "data", "ml-ops"]
Expand Down
2 changes: 1 addition & 1 deletion jacs-mcp/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2024"
rust-version = "1.93"
description = "MCP server for JACS: data provenance and cryptographic signing of agent state"
readme = "README.md"
license = "Apache-2.0 OR MIT"
license = "Apache-2.0"
homepage = "https://humanassisted.github.io/JACS"
repository = "https://github.com/HumanAssisted/JACS"
authors = ["JACS Contributors"]
Expand Down
5 changes: 2 additions & 3 deletions jacs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ include = [
"README.md",
"LICENSE",
"LICENSE-APACHE",
"LICENSE-MIT",
"build.rs",
"CHANGELOG.md",
"basic-schemas.png",
Expand All @@ -44,7 +43,7 @@ include = [
description = "JACS JSON AI Communication Standard"
readme = "README.md"
authors = ["HAI.AI <engineering@hai.io>"]
license = "Apache-2.0 OR MIT"
license = "Apache-2.0"
homepage = "https://humanassisted.github.io/JACS"
repository = "https://github.com/HumanAssisted/JACS"
keywords = ["cryptography", "json", "ai", "data", "ml-ops"]
Expand Down Expand Up @@ -143,7 +142,7 @@ walkdir = "2.5.0"
object_store = { version ="0.12.0", features = ["serde","serde_json", "aws", "http"] }
# Post-quantum 2025 standards (ML-DSA and ML-KEM)
fips203 = "0.4.3"
fips204 = "0.4.3"
fips204 = "0.4.6"
# SQLite storage via sqlx (optional). PostgreSQL has been extracted to jacs-postgresql.
sqlx = { version = "0.8.6", default-features = false, features = ["runtime-tokio-rustls"], optional = true }
# rusqlite (sync SQLite bindings, optional -- lightweight alternative to sqlx SQLite)
Expand Down
2 changes: 1 addition & 1 deletion jacs/docs/nist_caisi_rfi_response.html
Original file line number Diff line number Diff line change
Expand Up @@ -711,7 +711,7 @@ <h2 id="references">7. References</h2>
team and is based on practical experience developing and deploying
cryptographic security controls for AI agent systems. All capabilities
described are implemented and tested in the JACS codebase (v0.9.2,
Apache 2.0 OR MIT license, 1,200+ tests across 5 language
Apache 2.0 license, 1,200+ tests across 5 language
targets).</em></p>
</body>
</html>
2 changes: 1 addition & 1 deletion jacs/docs/nist_caisi_rfi_response.md
Original file line number Diff line number Diff line change
Expand Up @@ -374,4 +374,4 @@ As agent-to-agent communication protocols mature (Google A2A, Anthropic MCP), se

---

*This response represents the views of the HAI.AI / JACS project team and is based on practical experience developing and deploying cryptographic security controls for AI agent systems. All capabilities described are implemented and tested in the JACS codebase (v0.9.2, Apache 2.0 OR MIT license, 1,200+ tests across 5 language targets).*
*This response represents the views of the HAI.AI / JACS project team and is based on practical experience developing and deploying cryptographic security controls for AI agent systems. All capabilities described are implemented and tested in the JACS codebase (v0.9.2, Apache 2.0 license, 1,200+ tests across 5 language targets).*
6 changes: 6 additions & 0 deletions jacs/src/agent/agreement.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,12 @@ use serde_json::json;
use std::collections::HashSet;
use tracing::{debug, info, warn};

// WARNING: This module uses `.expect()` in multiple signing/verification
// paths (12+ call sites). These will panic on malformed input.
// Acceptable because agreements are NOT in the launch path (2026-04-06).
// TODO: Convert all `.expect()` calls to `?` error propagation before
// enabling agreements in production. See docs/0403INCONCISTANCIES.md item B5.

/// Options for creating and checking agreements.
///
/// All fields are optional. When omitted, the existing behavior is preserved:
Expand Down
8 changes: 8 additions & 0 deletions jacs/src/crypt/pq2025.rs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,10 @@ pub fn sign_string(secret_key: Vec<u8>, data: &String) -> Result<String, JacsErr
e
))
})?;
// TODO(post-quantum-hardening): We currently sign with the empty ML-DSA
// context for compatibility. A future hardening pass could switch this to a
// fixed JACS domain-separation context (for example `b"jacs:document:v1"`)
// and record that context in signature metadata so verification can use it.
let sig = sk
.try_sign(data.as_bytes(), b"")
.map_err(|e| JacsError::CryptoError(format!("ML-DSA-87 signing failed: {}", e)))?; // empty context - returns [u8; 4627]
Expand Down Expand Up @@ -93,6 +97,10 @@ pub fn verify_string(
)
})?;

// TODO(post-quantum-hardening): If JACS introduces a non-empty ML-DSA
// context, verification should read the stored context from signature
// metadata and fall back to `b""` for legacy documents signed before the
// migration.
// verify() returns bool, not Result
if pk.verify(data.as_bytes(), &sig_array, b"") {
debug!("ML-DSA-87 signature verification succeeded");
Expand Down
104 changes: 98 additions & 6 deletions jacs/src/dns/bootstrap.rs
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,25 @@ pub fn emit_cloudflare_curl(rr: &DnsRecord, zone_id_hint: &str) -> String {
)
}

/// Find the first JACS-formatted TXT record from a list of individual TXT record strings.
///
/// DNS lookups may return multiple TXT records (SPF, DKIM, DMARC, etc.) at the same
/// domain name. This function filters for the record starting with `v=jacs` and ignores
/// all others, preventing non-JACS records from corrupting the parsed result.
///
/// Returns `Err(JacsError::DnsRecordInvalid)` if no JACS record is found.
pub fn find_jacs_txt_record(records: Vec<String>, domain: &str) -> Result<String, JacsError> {
for record in records {
if record.starts_with("v=jacs") {
return Ok(record);
}
}
Err(JacsError::DnsRecordInvalid {
domain: domain.to_string(),
reason: "No v=jacs TXT record found at this domain".to_string(),
})
}

#[cfg(not(target_arch = "wasm32"))]
pub fn resolve_txt_dnssec(owner: &str) -> Result<String, JacsError> {
use hickory_resolver::Resolver;
Expand All @@ -202,18 +221,20 @@ pub fn resolve_txt_dnssec(owner: &str) -> Result<String, JacsError> {
domain: owner.to_string(),
reason: format!("DNS lookup failed: {e}"),
})?;
let mut s = String::new();
let mut records = Vec::new();
for rr in resp.iter() {
let mut record = String::new();
for part in rr.txt_data() {
s.push_str(&String::from_utf8(part.to_vec()).map_err(|e| {
record.push_str(&String::from_utf8(part.to_vec()).map_err(|e| {
JacsError::DnsRecordInvalid {
domain: owner.to_string(),
reason: format!("UTF-8 decode failed: {e}"),
}
})?);
}
records.push(record);
}
Ok(s)
find_jacs_txt_record(records, owner)
}

#[cfg(not(target_arch = "wasm32"))]
Expand All @@ -234,18 +255,20 @@ pub fn resolve_txt_insecure(owner: &str) -> Result<String, JacsError> {
domain: owner.to_string(),
reason: format!("DNS lookup failed: {e}"),
})?;
let mut s = String::new();
let mut records = Vec::new();
for rr in resp.iter() {
let mut record = String::new();
for part in rr.txt_data() {
s.push_str(&String::from_utf8(part.to_vec()).map_err(|e| {
record.push_str(&String::from_utf8(part.to_vec()).map_err(|e| {
JacsError::DnsRecordInvalid {
domain: owner.to_string(),
reason: format!("UTF-8 decode failed: {e}"),
}
})?);
}
records.push(record);
}
Ok(s)
find_jacs_txt_record(records, owner)
}

pub fn verify_pubkey_via_dns_or_embedded(
Expand Down Expand Up @@ -868,6 +891,75 @@ mod tests {
}
}

// --- H1 fix: find_jacs_txt_record filters non-JACS TXT records ---

#[test]
fn test_find_jacs_txt_record_mixed_spf_and_jacs() {
let records = vec![
"v=spf1 include:_spf.google.com ~all".to_string(),
"v=jacs; jacs_agent_id=a1; alg=SHA-256; enc=base64; jac_public_key_hash=abc"
.to_string(),
];
let result = find_jacs_txt_record(records, "example.com").unwrap();
assert!(
result.starts_with("v=jacs"),
"Should return JACS record, got: {}",
result
);
assert!(
result.contains("jacs_agent_id=a1"),
"Should contain agent ID, got: {}",
result
);
}

#[test]
fn test_find_jacs_txt_record_no_jacs_record() {
let records = vec![
"v=spf1 include:_spf.google.com ~all".to_string(),
"google-site-verification=abc123".to_string(),
];
let result = find_jacs_txt_record(records, "example.com");
assert!(result.is_err(), "Should return error when no JACS record");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("No v=jacs TXT record"),
"Error should mention 'No v=jacs TXT record', got: {}",
err_msg
);
}

#[test]
fn test_find_jacs_txt_record_empty_records() {
let records: Vec<String> = vec![];
let result = find_jacs_txt_record(records, "example.com");
assert!(result.is_err(), "Should return error for empty records");
let err_msg = result.unwrap_err().to_string();
assert!(
err_msg.contains("No v=jacs TXT record"),
"Error should mention 'No v=jacs TXT record', got: {}",
err_msg
);
}

#[test]
fn test_find_jacs_txt_record_jacs_among_garbage() {
// JACS record between other unrelated TXT records
let records = vec![
"v=spf1 include:_spf.google.com ~all".to_string(),
"google-site-verification=abc123".to_string(),
"v=jacs; jacs_agent_id=agent-42; alg=SHA-256; enc=hex; jac_public_key_hash=deadbeef"
.to_string(),
"v=DMARC1; p=reject; rua=mailto:dmarc@example.com".to_string(),
];
let result = find_jacs_txt_record(records, "example.com").unwrap();
assert!(
result.contains("jacs_agent_id=agent-42"),
"Should extract the JACS record, got: {}",
result
);
}

#[test]
fn test_verify_pubkey_embedded_fallback_no_hai_references() {
// Test that embedded fingerprint verification works without any hai.ai involvement
Expand Down
Loading
Loading