Security Model
JACS implements a comprehensive security model designed to ensure authenticity, integrity, and non-repudiation for all agent communications and documents.
Security Model (v0.6.0)
- Passwords: The private key password must be set only via the
JACS_PRIVATE_KEY_PASSWORDenvironment variable. It is never stored in config files. - Keys: Private keys are encrypted at rest (AES-256-GCM with PBKDF2, 600k iterations). Public keys and config may be stored on disk.
- Path validation: All paths built from untrusted input (e.g.
publicKeyHash, filenames) are validated viarequire_relative_path_safe()to prevent directory traversal. This single validation function is used in data and key directory path builders and the trust store. It rejects empty segments,.,.., null bytes, and Windows drive-prefixed paths. - Trust ID canonicalization: Trust-store operations normalize canonical agent docs (
jacsId+jacsVersion) into a safeUUID:VERSION_UUIDidentifier before filesystem use, preserving path-safety checks while supporting standard agent document layout. - Filesystem schema policy: Local schema loading is disabled by default and requires
JACS_ALLOW_FILESYSTEM_SCHEMAS=true. When enabled, schema paths must remain within configured roots (JACS_DATA_DIRECTORYand/orJACS_SCHEMA_DIRECTORY) after normalized/canonical path checks. - Network endpoint policy: Registry verification requires HTTPS for
JACS_REGISTRY_URL(legacy alias:HAI_API_URL). Localhost HTTP is allowed for local testing only. - No secrets in config: Config files must not contain passwords or other secrets. The example config (
jacs.config.example.json) does not includejacs_private_key_password. - Dependency auditing: Run
cargo audit(Rust),npm audit(Node.js), orpip audit(Python) to check for known vulnerabilities.
Core Security Principles
1. Cryptographic Identity
Every JACS agent has a unique cryptographic identity:
- Key Pair: Each agent possesses a private/public key pair
- Agent ID: Unique UUID identifying the agent
- Public Key Hash: SHA-256 hash of the public key for verification
{
"jacsSignature": {
"agentID": "550e8400-e29b-41d4-a716-446655440000",
"publicKeyHash": "sha256-of-public-key",
"signingAlgorithm": "ring-Ed25519"
}
}
2. Document Integrity
All documents include cryptographic guarantees:
- Signature: Cryptographic signature over specified fields
- Hash: SHA-256 hash of document contents
- Version Tracking: Immutable version history
3. Non-Repudiation
Signatures provide proof of origin:
- Agents cannot deny signing a document
- Timestamps record when signatures were made
- Public keys enable independent verification
Security Audit (audit())
JACS provides a read-only security audit that checks configuration, directories, secrets, trust store, storage, quarantine/failed files, and optionally re-verifies recent documents. It does not modify state.
Purpose: Surface misconfiguration, missing keys, unexpected paths, and verification failures in one report.
Options (all optional):
config_path: Path tojacs.config.json(default: 12-factor load)data_directory/key_directory: Override pathsrecent_verify_count: Number of recent documents to re-verify (default 10, max 100)
Return structure: AuditResult with overall_status, risks (list of AuditRisk), health_checks (list of ComponentHealth), summary, checked_at, and optional quarantine_entries / failed_entries.
Rust:
#![allow(unused)] fn main() { use jacs::audit::{audit, AuditOptions}; let result = audit(AuditOptions::default())?; println!("{}", jacs::format_audit_report(&result)); }
Python:
import jacs.simple as jacs
result = jacs.audit() # dict with risks, health_checks, summary, overall_status
print(f"Risks: {len(result['risks'])}, Status: {result['overall_status']}")
Node.js:
import * as jacs from '@hai.ai/jacs/simple';
const result = jacs.audit({ recentN: 5 });
console.log(`Risks: ${result.risks.length}, Status: ${result.overall_status}`);
Available in all bindings and as an MCP tool (jacs_audit) for automation.
Threat Model
Protected Against
| Threat | Protection |
|---|---|
| Tampering | Content hashes detect modifications |
| Impersonation | Cryptographic signatures verify identity |
| Replay Attacks | Timestamps and version IDs ensure freshness; future timestamps rejected; optional signature expiration via JACS_MAX_SIGNATURE_AGE_SECONDS |
| Man-in-the-Middle | DNS verification via DNSSEC; TLS certificate validation |
| Key Compromise | Key rotation through versioning |
| Weak Passwords | Minimum 28-bit entropy enforcement (35-bit for single class) |
Trust Assumptions
- Private keys are kept secure
- Cryptographic algorithms are sound
- DNS infrastructure (when used) is trustworthy
Signature Process
Signing a Document
- Field Selection: Determine which fields to sign
- Canonicalization: Serialize fields deterministically
- Signature Generation: Sign with private key
- Hash Computation: Compute SHA-256 of signed document
import jacs
import json
agent = jacs.JacsAgent()
agent.load('./jacs.config.json')
# Create signed document
doc = agent.create_document(json.dumps({
'title': 'Confidential Report',
'content': 'Sensitive data here'
}))
# Document now includes jacsSignature and jacsSha256
Verifying a Document
- Hash Verification: Recompute hash and compare
- Signature Verification: Verify signature with public key
- Agent Verification: Optionally verify agent identity via DNS
is_valid = agent.verify_document(doc_json)
is_signature_valid = agent.verify_signature(doc_json)
Key Management
Key Generation
JACS generates cryptographic key pairs during agent creation:
# Keys are created in the configured key directory
jacs_keys/
├── private.pem # Private key (keep secure!)
└── public.pem # Public key (can be shared)
Key Protection
Encryption at Rest:
Private keys are encrypted using AES-256-GCM with a key derived via PBKDF2-HMAC-SHA256 (600,000 iterations). Never store the password in config files.
# Set via environment variable only
export JACS_PRIVATE_KEY_PASSWORD="secure-password"
Important: The CLI can prompt for the password during
jacs init, but scripts and servers must setJACS_PRIVATE_KEY_PASSWORDas an environment variable.
Password Entropy Requirements:
JACS enforces password entropy minimums for private key encryption. Password validation is performed at encryption time, and weak passwords are rejected with helpful error messages:
- Minimum 28-bit entropy for passwords with 2+ character classes (mixed case, numbers, symbols)
- Minimum 35-bit entropy for single-character-class passwords (e.g., all lowercase)
- Entropy is calculated based on character class diversity and length
- Weak passwords result in immediate rejection during key encryption
- Error messages guide users toward stronger password choices
Example of rejected weak passwords:
password- Too common and predictable12345678- Insufficient character diversityabc- Too short
File Permissions:
chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem
Key Rotation
Update agent version to rotate keys:
- Generate new key pair
- Create new agent version
- Sign new version with old key
- Update configuration to use new keys
TLS Certificate Validation
JACS includes configurable TLS certificate validation for secure network communication.
Default Behavior (Development)
By default, JACS warns about invalid TLS certificates but accepts them to facilitate development environments with self-signed certificates:
WARNING: Invalid TLS certificate detected. Set JACS_STRICT_TLS=true for production.
Production Configuration
For production deployments, enable strict TLS validation:
export JACS_STRICT_TLS=true
When enabled, JACS will:
- Reject connections with invalid, expired, or self-signed certificates
- Enforce proper certificate chain validation
- Fail fast with clear error messages for certificate issues
Implementation: Certificate validation logic is located in jacs/src/schema/utils.rs.
Security Implications
| Mode | Behavior | Use Case |
|---|---|---|
| Default (dev) | Warn on invalid certs, allow connection | Local development, testing |
Strict (JACS_STRICT_TLS=true) | Reject invalid certs | Production, staging |
For registry verification endpoints, JACS_REGISTRY_URL (legacy HAI_API_URL) must use HTTPS. HTTP is only allowed for localhost test endpoints.
Signature Timestamp Validation
JACS signatures include timestamps to prevent replay attacks and ensure temporal integrity.
How It Works
- Timestamp Inclusion: Every signature includes a UTC timestamp recording when it was created
- Future Timestamp Rejection: Signatures with timestamps more than 5 minutes in the future are rejected
- Optional Signature Expiration: Configurable via
JACS_MAX_SIGNATURE_AGE_SECONDS(disabled by default since JACS documents are designed to be eternal) - Validation: Timestamp validation occurs during signature verification
Configuring Signature Expiration
By default, signatures do not expire. JACS documents are designed to be idempotent and eternal. For use cases that require expiration:
# Enable expiration (e.g., 90 days)
export JACS_MAX_SIGNATURE_AGE_SECONDS=7776000
# Default: no expiration (0)
export JACS_MAX_SIGNATURE_AGE_SECONDS=0
Protection Against Replay Attacks
The 5-minute future tolerance window:
- Allows for reasonable clock skew between systems
- Prevents attackers from creating signatures with future timestamps
- Ensures signatures cannot be pre-generated for later fraudulent use
{
"jacsSignature": {
"agentID": "550e8400-e29b-41d4-a716-446655440000",
"signature": "...",
"date": "2024-01-15T10:30:00Z" // Must be within 5 min of verifier's clock
}
}
Clock Synchronization
For reliable timestamp validation across distributed systems:
- Ensure all agents use NTP or similar time synchronization
- Monitor for clock drift in production environments
- Consider the 5-minute tolerance when debugging verification failures
Verification Claims
Agents can claim a verification level that determines security requirements. This follows the principle: "If you claim it, you must prove it."
Claim Levels
| Claim | Required Conditions | Behavior |
|---|---|---|
unverified (default) | None | Relaxed DNS/TLS settings allowed; self-asserted identity |
verified | Domain with DNSSEC | Strict TLS, strict DNS with DNSSEC validation required |
verified-registry | Above + registry verification | Must be registered and verified by a JACS registry |
verified-hai.ai (legacy alias) | Same as verified-registry | Backward-compatible alias |
Setting a Verification Claim
Add the jacsVerificationClaim field to your agent definition:
{
"jacsAgentType": "ai",
"jacsVerificationClaim": "verified",
"jacsAgentDomain": "myagent.example.com",
"jacsServices": [...]
}
Claim Enforcement
When an agent claims verified, verified-registry, or legacy verified-hai.ai:
- Domain Required: The
jacsAgentDomainfield must be set - Strict DNS: DNS lookup uses DNSSEC validation (no insecure fallback)
- DNS Required: Public key fingerprint must match DNS TXT record
- Strict TLS: TLS certificate validation is mandatory (no self-signed certs)
For verified-registry (or legacy verified-hai.ai) claims, additional enforcement:
- Registry Registration: Agent must be registered with the configured registry (for HAI-hosted registry, hai.ai)
- Public Key Match: Registered public key must match the agent's key
- Network Required: Verification fails if the registry API is unreachable
Backward Compatibility
- Agents without
jacsVerificationClaimare treated asunverified - Existing agents continue to work with their current DNS settings
- No breaking changes for agents that don't opt into verified status
Error Messages
If verification fails, clear error messages explain what's wrong:
Verification claim 'verified' failed: Verified agents must have jacsAgentDomain set.
Agents claiming 'verified' must meet the required security conditions.
Verification claim 'verified-registry' failed: Agent 'uuid' is not registered with the configured registry.
Agents claiming 'verified-registry' must be registered with a reachable registry endpoint.
Security Considerations
- No Downgrade: Once an agent claims
verified, it cannot be verified with relaxed settings - Claim Changes: Changing the claim requires creating a new agent version
- Network Dependency:
verified-registryrequires network access to the registry endpoint - Audit Trail: Verification claim and enforcement results are logged
DNS-Based Verification
JACS supports DNSSEC-validated identity verification:
How It Works
- Agent publishes public key fingerprint in DNS TXT record
- Verifier queries DNS for
_v1.agent.jacs.<domain>. - DNSSEC validates the response authenticity
- Fingerprint is compared against agent's public key
Configuration
{
"jacs_agent_domain": "myagent.example.com",
"jacs_dns_validate": true,
"jacs_dns_strict": true
}
Security Levels
| Mode | Description |
|---|---|
jacs_dns_validate: false | No DNS verification |
jacs_dns_validate: true | Attempt DNS verification, allow fallback |
jacs_dns_strict: true | Require DNSSEC validation |
jacs_dns_required: true | Fail if domain not present |
Trust Store Management
JACS maintains a trust store for managing trusted agent relationships.
Trusting Agents
Before trusting an agent, JACS performs public key hash verification:
# Trust an agent after verifying their public key hash
agent.trust_agent(agent_id, public_key_hash)
Untrusting Agents
The untrust_agent() method properly handles the case when an agent is not in the trust store:
try:
agent.untrust_agent(agent_id)
except AgentNotTrusted as e:
# Agent was not in the trust store
print(f"Agent {agent_id} was not trusted: {e}")
Trust Store Security
| Operation | Validation |
|---|---|
trust_agent() | UUID format validation, path traversal rejection, public key hash verification, self-signature verification before adding |
untrust_agent() | UUID format validation, path containment check, returns AgentNotTrusted error if agent not found |
get_trusted_agent() | UUID format validation, path containment check |
is_trusted() | UUID format validation, safe lookup without side effects |
Key cache (load_public_key_from_cache) | require_relative_path_safe() rejects traversal in publicKeyHash |
Key cache (save_public_key_to_cache) | require_relative_path_safe() rejects traversal in publicKeyHash |
Path Traversal Protection (v0.6.0): All trust store operations that construct file paths from agent IDs or key hashes use defense-in-depth:
- UUID format validation: Agent IDs must match
UUID:UUIDformat (rejects special characters) - Path character rejection: Explicit rejection of
..,/,\, and null bytes - Path containment check: For existing files, canonicalized paths are verified to stay within the trust store directory
require_relative_path_safe(): Key hashes are validated to prevent traversal before constructing cache file paths
Best Practices
- Verify Before Trust: Always verify an agent's public key hash through an out-of-band channel before trusting
- Audit Trust Changes: Log all trust store modifications for security auditing
- Periodic Review: Regularly review and prune the trust store
Agreement Security
Multi-party agreements provide additional security:
Agreement Structure
{
"jacsAgreement": {
"agentIDs": ["agent-1", "agent-2", "agent-3"],
"signatures": [
{
"agentID": "agent-1",
"signature": "...",
"responseType": "agree",
"date": "2024-01-15T10:00:00Z"
}
]
},
"jacsAgreementHash": "hash-at-agreement-time"
}
Agreement Guarantees
- Content Lock:
jacsAgreementHashensures all parties agreed to same content - Individual Consent: Each signature records explicit agreement
- Response Types: Support for agree, disagree, or reject
- Timestamp: Records when each party signed
Request/Response Security
For MCP and HTTP communication:
Request Signing
signed_request = agent.sign_request({
'method': 'tools/call',
'params': {'name': 'echo', 'arguments': {'text': 'hello'}}
})
The signed request includes:
- Full JACS document structure
- Agent signature
- Timestamp
- Content hash
Response Verification
result = agent.verify_response(response_string)
payload = result.get('payload')
agent_id = result.get('agentId') # Who signed the response
Algorithm Security
Supported Algorithms
| Algorithm | Type | Security Level |
|---|---|---|
ring-Ed25519 | Elliptic Curve | High (recommended) |
RSA-PSS | RSA | High |
pq-dilithium | Post-Quantum | Quantum-resistant |
pq2025 | Composite | Transitional |
Algorithm Selection
Choose based on requirements:
- General Use:
ring-Ed25519- fast, secure, small signatures - Legacy Systems:
RSA-PSS- widely supported - Future-Proofing:
pq-dilithium- quantum-resistant - Transition:
pq2025- hybrid classical/post-quantum
Security Best Practices
1. Key Storage
# Never commit keys to version control
echo "jacs_keys/" >> .gitignore
# Secure file permissions
chmod 700 ./jacs_keys
chmod 600 ./jacs_keys/private.pem
2. Password Handling
# Use environment variables
export JACS_PRIVATE_KEY_PASSWORD="$(pass show jacs/key-password)"
3. Transport Security
Always use TLS for network communication:
# HTTPS for web transport
client = JACSMCPClient("https://localhost:8000/sse") # Good
# client = JACSMCPClient("http://localhost:8000/sse") # Avoid in production
4. Verification Policies
{
"jacs_dns_strict": true,
"jacs_dns_required": true,
"jacs_enable_filesystem_quarantine": "true"
}
5. Audit Logging
Enable observability for security auditing:
{
"observability": {
"logs": {
"enabled": true,
"level": "info"
}
}
}
Security Checklist
Development
- Generate unique keys for each environment
- Never commit private keys
- Use test keys separate from production
Production
- Encrypt private keys at rest
-
Use environment variables for secrets (never store
jacs_private_key_passwordin config) - Enable DNS verification
- Configure strict security mode
- Enable audit logging
- Use TLS for all network transport
- Restrict key file permissions (0600 for keys, 0700 for key directory)
- Implement key rotation policy
-
Set
JACS_STRICT_TLS=truefor certificate validation - Use strong passwords (28+ bit entropy, 35+ for single character class)
- Enable signature timestamp validation
- Verify public key hashes before trusting agents
-
Run
cargo audit/npm audit/pip auditregularly for dependency vulnerabilities
Verification
- Always verify documents before trusting
- Verify agent signatures
- Check agreement completeness
- Validate DNS records when required
Security Considerations
Supply Chain
- Verify JACS packages are from official sources
- Use package checksums
- Keep dependencies updated
Side Channels
- Use constant-time comparison for signatures
- Protect against timing attacks
- Secure memory handling for keys
Recovery
- Backup key material securely
- Document key recovery procedures
- Plan for key compromise scenarios
Troubleshooting Verification Claims
Common Issues and Solutions
"Verified agents must have jacsAgentDomain set"
Problem: You set jacsVerificationClaim to verified but didn't specify a domain.
Solution: Either add a domain or use unverified:
// Option 1: Add a domain (recommended for production)
{
"jacsVerificationClaim": "verified",
"jacsAgentDomain": "myagent.example.com"
}
// Option 2: Use unverified if DNS verification isn't needed
{
"jacsVerificationClaim": "unverified"
}
"Agent is not registered with the registry"
Problem: You're using verified-registry (or legacy verified-hai.ai) but the agent isn't registered.
Solution:
- Register your agent with your configured registry (for HAI-hosted registry, hai.ai)
- Or use
verifiedfor DNS-only verification:
{
"jacsVerificationClaim": "verified",
"jacsAgentDomain": "myagent.example.com"
}
"Cannot downgrade from 'verified' to 'unverified'"
Problem: You're trying to change an existing agent's claim to a lower level.
Solution: Verification claims cannot be downgraded for security. Options:
- Keep the current claim level
- Create a new agent with the desired claim level
- If this is a test/development scenario, start fresh
# Create a new agent instead
jacs create --type ai --claim unverified
"DNS fingerprint mismatch"
Problem: The public key hash in DNS doesn't match your agent's key.
Solution:
- Regenerate the DNS record with your current keys:
jacs dns-record - Update your DNS TXT record with the new value
- Wait for DNS propagation (can take up to 48 hours)
"Strict DNSSEC validation failed"
Problem: Your domain doesn't have DNSSEC enabled.
Solution:
- Enable DNSSEC with your domain registrar
- Publish DS records at the parent zone
- Or use
verifiedwith non-strict DNS (development only)
Claim Level Reference
| Claim | Security Level | Requirements |
|---|---|---|
unverified | 0 (lowest) | None - self-asserted identity |
verified | 1 | Domain + DNS TXT record + DNSSEC |
verified-registry | 2 (highest) | Above + registry registration |
verified-hai.ai (legacy alias) | 2 (highest) | Alias of verified-registry |
Upgrade vs Downgrade Rules
- Upgrades allowed:
unverified→verified→verified-registry(legacy aliasverified-hai.aiis same level) - Downgrades blocked: Cannot go from higher to lower claim
- Same level allowed: Can update agent while keeping same claim
Quick Diagnostic Commands
# Check your agent's current claim
jacs info | grep jacsVerificationClaim
# Verify DNS record is correct
jacs dns-check
# Test verification
jacs verify --agent your-agent-id:version
See Also
- Cryptographic Algorithms - Algorithm details
- DNS Verification - DNS-based identity
- Configuration - Security configuration
- Agreements - Multi-party agreements