Native library reference. Extended CLI examples use
jacs-compat. This source reference can include unreleased changes; see the package guide for installation and MCP profiles for available tools.
Security Model
JACS verifies signed data under selected keys. Applications establish identity, trust and authorization around that cryptographic evidence.
Security Model (v0.6.0+)
- Passwords: The private key password can be provided via
JACS_PRIVATE_KEY_PASSWORDenvironment variable,JACS_PASSWORD_FILE, or the OS keychain (macOS Keychain / Linux Secret Service). It is never stored in config files. - Keys: New private-key envelopes use AES-256-GCM with Argon2id. Legacy PBKDF2 envelopes remain readable; 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. - Strict JSON ingress: Raw JSON crossing a signing, verification, hashing,
schema, or protocol boundary must pass the shared
jacs-corestrict decoder. Duplicate decoded object names are rejected recursively before aserde_json::Valuecan collapse them; escape-equivalent names are duplicates, while repeated array elements remain valid. - 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": "pq2025"
}
}
Native signing defaults to post-quantum pq2025 (ML-DSA-87 / FIPS-204).
Explicit ed25519 creation is supported and emits the canonical
ring-Ed25519 label. Routine rotation preserves the current algorithm;
Ed25519 can explicitly upgrade to pq2025, while PQ-to-Ed25519 downgrade is rejected.
2. Document Integrity
Signatures and hashes protect the signed bytes under the selected public key. Version IDs do not establish a complete history, and signer-supplied timestamps do not prove an independent signing time or real-world authorship.
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 the language bindings for local diagnostics and automation.
Threat Model
Protected Against
| Threat | Protection |
|---|---|
| Tampering | Content hashes detect modifications |
| Impersonation | Cryptographic signatures verify identity |
| Replay Attacks | HTTP/RPC payload and W3C request proofs use timestamp validation plus atomic nonce consumption; document signatures remain durable by design |
| 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
Replay Store Deployment
The default replay backend is a bounded, atomic, process-local Moka cache. It accepts exactly one concurrent use of a nonce within one process and is suitable for local development or a genuinely single-process service.
Multi-replica production services must install an implementation of the Rust
ReplayStore trait backed by a shared atomic primitive (for example Redis
SET key value NX EX ttl or a database unique insert), then set
JACS_REQUIRE_SHARED_REPLAY_STORE=true. With that setting JACS fails closed
if the installed backend reports itself as process-local or returns an error.
The library never silently falls back to memory after a shared-store failure.
Replay decisions emit structured events without nonce contents and increment
jacs_replay_checks_total{backend,outcome}. Use
JACS_PAYLOAD_MAX_REPLAY_SECONDS to configure the default payload window. For
an accepted future-skewed payload, JACS retains its nonce through the signed
timestamp plus that window, not merely for a fixed interval from verification.
Remote JWKS Retrieval
A2A Agent Card verification keeps JWKS network access disabled unless
JACS_ALLOW_JWKS_FETCH=true (or the broader JACS_ALLOW_NETWORK=true) is set.
Even after that opt-in, JACS accepts only HTTPS origins, resolves and validates
every redirect hop, rejects private/reserved IP answers, pins the validated DNS
answer into the HTTP client, disables proxy resolution, allows at most three
same-origin redirects, and requires a JSON Content-Type. The whole operation is
limited to five seconds and 256 KiB.
Loopback HTTP remains available for deliberate local development only when
both the network capability and JACS_ALLOW_PRIVATE_JWKS=true are set. Do not
enable the private-JWKS override in a service that assesses Agent Cards supplied
by untrusted callers.
Request-Bound HTTP Authorization
Use the request builder exposed by your API with the actual HTTP method, absolute URL, exact body bytes (empty bytes for no body), and a service-specific audience:
| API | Request-bound builder |
|---|---|
Rust SimpleAgent | build_request_auth_header(method, url, body, audience) |
Python SimpleAgent | build_request_auth_header(method, url, body, audience) |
Node JacsSimpleAgent | buildRequestAuthHeader(method, url, body, audience) |
Go JacsSimpleAgent | BuildRequestAuthHeader(method, url, body, audience) |
The resulting JACS v2... header authenticates the signer/key ID, normalized
scheme/authority/path/query, SHA-256 content digest, audience, timestamp, and
nonce. verify_request_auth_header requires the trusted key and the actual
request context; any method, endpoint, query, body, signer, or audience
substitution fails before replay state is consumed.
Build the header after the final body serialization and send those same bytes.
On verification, reconstruct the absolute URL from trusted server/framework
configuration rather than trusting an attacker-controlled forwarding header.
The standard Rust verifier atomically consumes replay state after all other
checks pass. Servers with an application-owned shared store use
verify_request_auth_header_with_trusted_key_without_replay, then grant access
only if an atomic insert such as Redis SET ... NX EX ... accepts the returned
nonce. Use request_auth_replay_ttl(&verified_claims, max_age) for the Redis
expiry (or retain it longer). A fixed max_age TTL is insufficient when the
signer's accepted clock skew is positive: the credential's absolute expiry is
issued_at + max_age.
The older no-argument Rust build_auth_header and equivalent legacy class
methods sign only identity, time, and nonce. They remain available for source
compatibility and emit a WARN because a first-use credential could be moved to
a different request. The additive request-bound methods require method, URL,
body, and audience and emit v2. Strict deployments can reject legacy
construction with JACS_REJECT_UNBOUND_AUTH_HEADER=true.
Encrypted-Key KDF Resource Policy
V2 private-key envelopes accept only the versioned Argon2id resource profile:
8,192–19,456 KiB memory, one or two passes, and parallelism one. JACS validates
these values before base64 allocation or Argon2 execution, and also caps the
serialized envelope/ciphertext and enforces exact salt/nonce sizes. A policy
rejection emits encrypted_key_kdf_policy_rejected at WARN and increments
jacs_kdf_policy_rejections_total{kdf="Argon2id",profile="v2"}; neither signal
contains passwords, key bytes, or ciphertext.
Signed Response and Event Contract
sign_response emits response envelope version 2.0.0 with signature scope
jacs-response-v2. Its domain-separated signature authenticates the complete
envelope except for the signature bytes themselves: payload, protocol
version/type, issuer, document ID, payload hash, timestamp, signer, algorithm,
public-key hash, and any additional fields. This is a protocol-specific
contract; do not replace it with generic document verification or verification
of data alone.
Use verify_response_json_with_key in Rust or unwrap_signed_event /
unwrapSignedEvent in a native binding with a trusted map from signer ID to
public key. Trusted keys may be raw bytes/base64 or canonical PEM, depending on
the binding. On success the bindings return only verified data plus provenance:
{
"data": {"decision": "allow"},
"verified": true,
"status": "verified",
"signerId": "agent-id:version",
"timestamp": "2026-07-09T12:00:00Z",
"algorithm": "pq2025",
"documentId": "2f1e2bdb-55f4-4c99-8f42-d45249808f4b"
}
Plain events, legacy payload-only response envelopes, unknown signers, and
invalid signatures are errors. There is no successful verified: false result;
callers must never dispatch an event by catching that error and reparsing its
untrusted input.
These protocols authenticate control of the private key corresponding to the
public key that the verifier selected. Signed agentID, name, domain, and time
fields are not independent proof of a person, organization, service, or DNS
identity. Establish that association through a pinned key, strict local trust
configuration, or another explicitly chosen verification policy before making
an authorization decision.
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
Compatibility key binding (P2)
An agent's ES256 ecosystem_signing key is authorized per-export by a
compatibility key binding: a compatibilityKeyBinding JACS document
signed by the current native root, persisted as one canonical-JSON file
(jacs_keys/jacs.compat-binding.json). Lifecycle rules:
- granting or widening a scope always requires the native root's signature — the ES256 holder cannot self-escalate;
- re-issue replaces the file (latest
issuedAtwins; no version chains); - native key rotation supersedes the binding — verification fails with a "re-issue" error until a new binding is signed by the current root;
- an expired binding denies export;
expiresAt: nullis permitted in P2; - default issuance grants only the identity scopes (
jwks,did,a2a-agent-card,w3c-agent-identity); content scopes (ap2-mandate,agreement-vc) require an explicit re-issue.
Be precise about what this is: scope checks are a locally enforced authorization policy and an auditable delegation record for relying parties — not isolation. Both private keys live in the same directory under the same password; a compromised host is outside this model. The sound property is non-self-escalation via the native-root signature requirement.
The ES256 private key is encrypted at rest with the same AES-256-GCM + Argon2id envelope as the native root key — the post-quantum library is a signing primitive, not an encryption primitive, so compatibility keys reuse the existing audited envelope rather than inventing a PQ-encrypted key format.
Targeted content exports (P2)
Content exporters (the AP2 mandate export, the Agreement-v2-as-VC export) sign ecosystem artifacts with the ES256 compatibility key through purpose-built, schema-pinned paths — there is no generic "sign this document with algorithm X" API. Two properties matter here:
- Classical verification is not PQ trust. A stock JOSE/Data
Integrity verifier can check the ES256 signature with nothing but the
public key; that proves possession of the compatibility key, not the
agent's native identity. Tracing the export to that root additionally
requires verifying the native-root-signed compatibility key
binding (
jacs-compat agent export-compat-binding). - Native documents are never mutated. Exports are derived views;
the native
jacsSignaturekeeps the agent's selected algorithm and the source document's bytes and verification are unchanged after every export. These are targeted ecosystem artifacts, not JACS projections — native documents never gainproofor other projection fields.
Exports are one-way in P2: JACS emits these artifacts, but verifying incoming AP2 mandates or third-party VCs is out of scope — use the target ecosystem's stock verifiers for that.
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:
New keys use the V2 AES-256-GCM/Argon2id envelope; PBKDF2 envelopes are read-only compatibility. Never store the password in config files.
# Option 1: Environment variable (recommended for CI/servers)
export JACS_PRIVATE_KEY_PASSWORD="secure-password"
# Option 2: OS keychain (recommended for developer workstations)
jacs-compat keychain set --agent-id <YOUR_AGENT_UUID>
Important: The CLI can prompt for the password during
jacs-compat init, but scripts and servers must setJACS_PRIVATE_KEY_PASSWORDas an environment variable or use the OS keychain.
OS Keychain Integration:
On macOS and Linux desktops, JACS can store and retrieve the private key password from the OS credential store, eliminating the need for environment variables or plaintext password files during day-to-day development:
- macOS: Uses Security.framework (Keychain Access)
- Linux: Uses the freedesktop.org D-Bus Secret Service API (GNOME Keyring, KDE Wallet, KeePassXC)
Each password is keyed by agent ID, so multiple agents can coexist on the same machine without overwriting each other. Store your password once with jacs-compat keychain set --agent-id <ID>, and all subsequent JACS operations will find it automatically. The password resolution order is:
JACS_PRIVATE_KEY_PASSWORDenv var (highest priority -- explicit always wins)JACS_PASSWORD_FILE/ legacy.jacs_passwordfile- OS keychain keyed by agent ID (lowest priority among explicit sources)
To disable keychain lookups (recommended for CI and headless environments):
export JACS_KEYCHAIN_BACKEND=disabled
Or in jacs.config.json:
{
"jacs_keychain_backend": "disabled"
}
Keychain entries are selected by agent ID; a generic shared default entry is
not the current per-agent password contract.
Security note: The OS keychain is encrypted at rest by the OS and unlocked by the user's login session. It is the same mechanism used by
git,ssh-agent,docker, and other CLI tools. Thekeychainfeature is optional and not compiled into thejacscore crate by default -- it is enabled by default injacs-cli-compat.
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
Native rotation preserves the current algorithm unless explicitly overridden.
Use jacs-compat agent rotate-keys --algorithm pq2025 to upgrade Ed25519;
a PQ identity cannot downgrade to Ed25519. Rotation also supersedes the
ES256 compatibility key binding: re-issue it under the new native root
(jacs-compat agent issue-compat-binding) before the next ecosystem export.
TLS Certificate Validation
JACS includes configurable TLS certificate validation for secure network communication.
Default Behavior (Development)
TLS certificate validation is strict by default. JACS_STRICT_TLS=false is an
explicit insecure development override for the schema-fetching client; do not
use it in production. Omitting the variable or setting it to true retains
certificate-chain validation. This setting does not describe every network
client in the repository.
Registry verification requires HTTPS (JACS_REGISTRY_URL, legacy alias
HAI_API_URL); localhost HTTP is restricted to test/development endpoints.
Signature Timestamp Validation
A document's signed timestamp records the signer's claimed signing time. It is not an independently trusted timestamp or proof that a later request is fresh. JACS document and agent-identity signatures are archival: the generic signature timestamp validator does not impose a maximum age.
What the Timestamp Check Establishes
validate_signature_timestamp parses an RFC 3339 timestamp and rejects a date
more than five minutes in the future relative to the verifier's clock.
Older dates pass this timestamp check; they do not need to fall within five
minutes of the current time. The signature, content hash and applicable trust
policy must still be checked separately.
JACS_MAX_SIGNATURE_AGE_SECONDS is not a supported document-signature setting
in the current runtime. Applications that need an expiration or current-authority
policy must evaluate it explicitly; successful archival verification alone does
not authorize a new action.
Freshness and Replay Protection
A valid timestamp does not prevent copying a signed document or replaying a request. Use the request-bound HTTP authorization contract for actual HTTP actions, with the expected method, URL, body and atomic nonce consumption. The replay store must cover the service's deployment scope; a process-local store does not coordinate multiple replicas.
HTTP/RPC payload verification has a separate replay window, controlled by
JACS_PAYLOAD_MAX_REPLAY_SECONDS (default 300 seconds). That window does not
change archival document-signature age semantics. Strict A2A admission separately
checks the compatibility binding's issued time and expiry; see
Compatibility key binding.
Clock Synchronization
Keep verifier clocks synchronized. The five-minute future tolerance permits bounded clock skew; it does not establish when an artifact was actually created, prevent pre-signing, or prove that a past authorization remains current.
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"
}
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>. - Strict mode validates DNSSEC; optional modes have different guarantees
- Fingerprint is compared against agent's public key
Configuration
Select strict validation explicitly:
jacs-compat agent verify --require-strict-dns
For Rust, configure AgentBuilder::dns_strict(true) together with required
DNS validation. Do not rely on a jacs_dns_strict JSON field being applied by
the native loader. Persisted validate/required settings and domain presence
control the ordinary defaults; verified claims force strict, required checks.
See DNS trust anchoring for the full flag matrix.
Trust Store Management
JACS maintains a trust store for managing trusted agent relationships.
Trusting Agents
First enrollment requires a full self-signed agent document and an independently authenticated public key. Use client.trust_agent_with_key(agent_json, public_key_pem); the older trust_agent(agent_json) only refreshes an enrolled identity. Follow the runnable enrollment example.
Untrusting Agents
untrust_agent() persists a stable-identity distrust marker while retaining historical public documents and keys. It does not delete verification history. Application admission must honor the distrust state.
Trust Store Security
Native trust operations validate identity/path inputs and verify enrollment signatures under the supplied key. Protect the OS/XDG trusted_agents directory from unauthorized writes. A matching self-signature or fetched fingerprint alone does not establish who owns the key or which actions it may authorize.
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
Legacy v1 sidecars inspect present payload signatures without authenticating participant, role, quorum, question or context policy. Native Agreement v2 also reports inspection only; mathematical validity is not consent-policy acceptance. See the Agreement v2 boundary. New portable consent uses Agreement v3, followed by application authorization.
Request/Response Security
For HTTP, use the request-bound authorization and signed response/event contracts above. Generic document signing does not bind an HTTP method, URL, audience, or request body, and generic verification of only the response payload does not authenticate the v2 envelope metadata.
MCP messages that need portable artifact integrity can carry ordinary signed JACS documents. That is separate from HTTP request authentication: the MCP stdio transport has no HTTP target to bind, and authorization remains the MCP host's responsibility.
Algorithm Security
Supported Algorithms
| Algorithm | Type | Role |
|---|---|---|
pq2025 | Post-Quantum (FIPS-204 ML-DSA-87) | Default native root; cannot rotate down to Ed25519 |
ring-Ed25519 | Elliptic Curve | Supported native creation, signing, and verification; user-facing input alias: ed25519 |
| ES256 | Elliptic Curve (P-256) | Compatibility key for targeted ecosystem exports only — never a valid native jacsSignature algorithm |
Algorithm Selection
Native JACS signing defaults to pq2025; explicit Ed25519 creation is
supported without substitution. Routine rotation preserves the algorithm;
an Ed25519-to-PQ upgrade is explicit.
An ES256 content signature does not create native JACS trust: classical verification proves possession of the compatibility key only. The native-root-signed compatibility key binding is what ties that key to the agent — see Compatibility key binding (P2).
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
# Option A: Use environment variables (CI, servers)
export JACS_PRIVATE_KEY_PASSWORD="$(pass show jacs/key-password)"
# Option B: Use OS keychain (developer workstations)
jacs-compat keychain set --agent-id <AGENT_UUID> # stores password securely in OS credential store
# Option C: Disable keychain for headless/CI environments
export JACS_KEYCHAIN_BACKEND=disabled
3. Transport Security
Always use TLS for network communication:
# HTTPS for web transport
client = JACSMCPClient(
"https://localhost:8000/sse",
expected_peer_agent_id="SERVER_AGENT_ID",
) # TLS plus an explicit signer pin
# Plain HTTP remains loopback-only and should not be exposed remotely.
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 or OS keychain for secrets (never store
jacs_private_key_passwordin config) -
Set
JACS_KEYCHAIN_BACKEND=disabledon CI/headless servers - 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-compat quickstart --name replacement-agent --domain replacement.example.com
"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-compat agent dns --domain example.com --provider plain - 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
- Verified claims require strict DNSSEC; fix DNSSEC before verifying them.
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-compat agent verify
# Verify DNS record is correct
jacs-compat agent verify --require-strict-dns
# Test verification
jacs-compat agent verify
See Also
- Cryptographic Algorithms - Algorithm details
- DNS Verification - DNS-based identity
- Configuration - Security configuration
- Agreements - Multi-party agreements