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
5 changes: 4 additions & 1 deletion crates/py/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -410,7 +410,10 @@ impl PyAgentAuthClient {
// Convert headers to a HashMap to return to Python
let mut result: HashMap<String, String> = HashMap::new();
for (key, value) in &headers {
result.insert(key.as_str().to_string(), value.to_str().unwrap_or("").to_string());
result.insert(
key.as_str().to_string(),
value.to_str().unwrap_or("").to_string(),
);
}
Ok(result)
})
Expand Down
4 changes: 1 addition & 3 deletions crates/registry/src/handlers/agents.rs
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,7 @@ pub async fn bootstrap_agent(
// Sign the manifest using the registry's signing backend
let signature = state
.signer
.sign(&auth_core::crypto::manifest_signing_bytes(
&req.manifest,
))
.sign(&auth_core::crypto::manifest_signing_bytes(&req.manifest))
.await
.map_err(|e| RegistryError::Internal(format!("failed to sign manifest: {e}")))?;

Expand Down
4 changes: 1 addition & 3 deletions crates/registry/src/handlers/grants.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,7 @@
use crate::error::{RegistryError, Result};
use crate::services::{AuditEvent, AuditEventType};
use crate::state::AppState;
use auth_core::{
AgentId, BehavioralEnvelope, Capability, CapabilityGrant, GrantId, GrantStatus,
};
use auth_core::{AgentId, BehavioralEnvelope, Capability, CapabilityGrant, GrantId, GrantStatus};
use axum::{
extract::{Path, State},
http::StatusCode,
Expand Down
2 changes: 1 addition & 1 deletion services/registry/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

mod signing;

use axum::middleware;
use registry::{
config::RegistryConfig,
db::DbPool,
Expand All @@ -15,7 +16,6 @@ use registry::{
services::{AuditService, CacheService, GrantService, TokenService},
state::{AppState, HealthState},
};
use axum::middleware;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
Expand Down
2 changes: 1 addition & 1 deletion services/registry/src/signing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@

use auth_core::crypto::{Ed25519PublicKey, Signature, SigningBackend};
use auth_core::error::CryptoError;
use registry::config::KmsBackend;
use base64::Engine;
use registry::config::KmsBackend;
use reqwest::Client;
use serde::{Deserialize, Serialize};
use std::sync::Arc;
Expand Down
2 changes: 1 addition & 1 deletion services/verifier/src/handlers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

use crate::state::VerifierState;
use auth_core::TokenId;
use registry::db;
use axum::{extract::State, http::StatusCode, response::IntoResponse, Json};
use chrono::Utc;
use registry::db;
use serde::{Deserialize, Serialize};
use std::time::Duration;
use tracing::{info, warn};
Expand Down
2 changes: 1 addition & 1 deletion services/verifier/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,12 @@ mod state;
use crate::config::VerifierConfig;
use crate::handlers::{get_keys, liveness, metrics_handler, readiness, startup, verify_token};
use crate::state::{HealthState, VerifierState};
use registry::services::CacheService;
use axum::{
middleware,
routing::{get, post},
Router,
};
use registry::services::CacheService;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::net::TcpListener;
Expand Down
1 change: 1 addition & 0 deletions tests/compliance/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
name = "compliance-tests"
version = "0.1.0"
edition = "2021"
license = "MIT OR Apache-2.0"
publish = false

[[test]]
Expand Down
38 changes: 13 additions & 25 deletions tests/compliance/audit_integrity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ impl AuditEvent {
let event_id = Uuid::now_v7();
let timestamp = Utc::now();

let content = Self::content_bytes(&event_id, &service_provider_id, &agent_id, action, &timestamp);
let content = Self::content_bytes(
&event_id,
&service_provider_id,
&agent_id,
action,
&timestamp,
);
let row_hash = hash_chain_event(&previous_hash, &content);

Self {
Expand Down Expand Up @@ -184,10 +190,7 @@ fn test_tampered_row_hash_detected() {
// Tamper with the row hash
chain.events[0].row_hash[0] ^= 0xFF;

assert!(
!chain.verify_chain(),
"tampered row_hash MUST be detected"
);
assert!(!chain.verify_chain(), "tampered row_hash MUST be detected");
}

/// COMPLIANCE: Inserted event MUST break the chain.
Expand All @@ -213,10 +216,7 @@ fn test_inserted_event_breaks_chain() {

chain.events.insert(1, fake_event);

assert!(
!chain.verify_chain(),
"inserted event MUST break the chain"
);
assert!(!chain.verify_chain(), "inserted event MUST break the chain");
}

/// COMPLIANCE: Deleted event MUST break the chain.
Expand All @@ -233,10 +233,7 @@ fn test_deleted_event_breaks_chain() {
// Remove the middle event
chain.events.remove(1);

assert!(
!chain.verify_chain(),
"deleted event MUST break the chain"
);
assert!(!chain.verify_chain(), "deleted event MUST break the chain");
}

/// COMPLIANCE: Reordered events MUST break the chain.
Expand Down Expand Up @@ -404,10 +401,7 @@ fn test_hash_chain_deterministic() {
let hash1 = hash_chain_event(&previous, content);
let hash2 = hash_chain_event(&previous, content);

assert_eq!(
hash1, hash2,
"hash_chain_event MUST be deterministic"
);
assert_eq!(hash1, hash2, "hash_chain_event MUST be deterministic");
}

/// COMPLIANCE: Constant time comparison is used for hashes.
Expand All @@ -431,10 +425,7 @@ fn test_constant_time_eq_for_hashes() {
#[test]
fn test_empty_chain_valid() {
let chain = AuditChain::new();
assert!(
chain.verify_chain(),
"empty audit chain MUST be valid"
);
assert!(chain.verify_chain(), "empty audit chain MUST be valid");
}

/// COMPLIANCE: Single event chain is valid.
Expand All @@ -446,8 +437,5 @@ fn test_single_event_chain_valid() {

chain.append(sp_id, agent_id, "single_event");

assert!(
chain.verify_chain(),
"single event chain MUST be valid"
);
assert!(chain.verify_chain(), "single event chain MUST be valid");
}
5 changes: 1 addition & 4 deletions tests/compliance/behavioral_envelope.rs
Original file line number Diff line number Diff line change
Expand Up @@ -159,10 +159,7 @@ fn test_zero_session_duration_rejected() {
fn test_valid_envelope_accepted() {
let valid = BehavioralEnvelope::default_restrictive();

assert!(
valid.validate().is_ok(),
"valid envelope MUST be accepted"
);
assert!(valid.validate().is_ok(), "valid envelope MUST be accepted");
}

/// COMPLIANCE: Envelope with requires_human_online MUST block when human offline.
Expand Down
66 changes: 27 additions & 39 deletions tests/compliance/capability_boundary.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
//! Verifies that agents cannot request or use capabilities beyond their manifest.

use auth_core::types::{
AgentAccessToken, AgentId, AgentManifest, BehavioralEnvelope, Capability,
HumanPrincipalId, ServiceProviderId, TokenId,
AgentAccessToken, AgentId, AgentManifest, BehavioralEnvelope, Capability, HumanPrincipalId,
ServiceProviderId, TokenId,
};
use chrono::Utc;
use std::collections::HashMap;
Expand Down Expand Up @@ -42,34 +42,28 @@ fn create_test_token(capabilities: Vec<Capability>) -> AgentAccessToken {
}

/// Checks if requested capabilities are a subset of manifest capabilities.
fn capabilities_within_manifest(
requested: &[Capability],
manifest: &AgentManifest,
) -> bool {
fn capabilities_within_manifest(requested: &[Capability], manifest: &AgentManifest) -> bool {
requested.iter().all(|req| {
manifest.capabilities_requested.iter().any(|m| {
req.capability_type() == m.capability_type() && req.resource() == m.resource()
})
manifest
.capabilities_requested
.iter()
.any(|m| req.capability_type() == m.capability_type() && req.resource() == m.resource())
})
}

/// COMPLIANCE: Agent cannot request capabilities beyond manifest.
#[test]
fn test_cannot_request_beyond_manifest() {
let manifest = create_test_manifest(vec![
Capability::Read {
resource: "calendar".to_string(),
filter: None,
},
]);
let manifest = create_test_manifest(vec![Capability::Read {
resource: "calendar".to_string(),
filter: None,
}]);

// Try to request write capability (not in manifest)
let requested = vec![
Capability::Write {
resource: "calendar".to_string(),
conditions: None,
},
];
let requested = vec![Capability::Write {
resource: "calendar".to_string(),
conditions: None,
}];

assert!(
!capabilities_within_manifest(&requested, &manifest),
Expand All @@ -92,12 +86,10 @@ fn test_can_request_within_manifest() {
]);

// Request only read (subset of manifest)
let requested = vec![
Capability::Read {
resource: "calendar".to_string(),
filter: None,
},
];
let requested = vec![Capability::Read {
resource: "calendar".to_string(),
filter: None,
}];

assert!(
capabilities_within_manifest(&requested, &manifest),
Expand All @@ -108,20 +100,16 @@ fn test_can_request_within_manifest() {
/// COMPLIANCE: Agent cannot request capabilities for different resource.
#[test]
fn test_cannot_request_different_resource() {
let manifest = create_test_manifest(vec![
Capability::Read {
resource: "calendar".to_string(),
filter: None,
},
]);
let manifest = create_test_manifest(vec![Capability::Read {
resource: "calendar".to_string(),
filter: None,
}]);

// Try to request same capability type but different resource
let requested = vec![
Capability::Read {
resource: "email".to_string(),
filter: None,
},
];
let requested = vec![Capability::Read {
resource: "email".to_string(),
filter: None,
}];

assert!(
!capabilities_within_manifest(&requested, &manifest),
Expand Down
6 changes: 5 additions & 1 deletion tests/compliance/dpop_binding.rs
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ fn test_proof_with_access_token_binding() {
let access_token = "eyJhbGciOiJFZDI1NTE5IiwidHlwIjoiSldUIn0.test.signature";

let proof_with_token = generator
.generate("POST", "https://api.example.com/resource", Some(access_token))
.generate(
"POST",
"https://api.example.com/resource",
Some(access_token),
)
.expect("create proof with ath");

let proof_without_token = generator
Expand Down
6 changes: 3 additions & 3 deletions tests/compliance/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@
//! - Capability boundaries are enforced
//! - Audit log integrity is maintained

mod token_security;
mod audit_integrity;
mod behavioral_envelope;
mod capability_boundary;
mod dpop_binding;
mod nonce_replay;
mod capability_boundary;
mod audit_integrity;
mod token_security;
26 changes: 5 additions & 21 deletions tests/compliance/nonce_replay.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,15 @@ fn test_nonce_uniqueness() {
// Generate 10,000 nonces and verify all are unique
for _ in 0..10_000 {
let nonce = generate_nonce();
assert!(
nonces.insert(nonce),
"generated nonces MUST be unique"
);
assert!(nonces.insert(nonce), "generated nonces MUST be unique");
}
}

/// COMPLIANCE: Nonce MUST be 32 bytes.
#[test]
fn test_nonce_length() {
let nonce = generate_nonce();
assert_eq!(
nonce.len(),
32,
"nonce MUST be exactly 32 bytes"
);
assert_eq!(nonce.len(), 32, "nonce MUST be exactly 32 bytes");
}

/// COMPLIANCE: Nonces MUST have sufficient entropy.
Expand All @@ -38,17 +31,11 @@ fn test_nonce_entropy() {

// Check that the nonce is not all zeros
let all_zeros = nonce.iter().all(|&b| b == 0);
assert!(
!all_zeros,
"nonce MUST have entropy (not all zeros)"
);
assert!(!all_zeros, "nonce MUST have entropy (not all zeros)");

// Check that the nonce is not all ones
let all_ones = nonce.iter().all(|&b| b == 0xFF);
assert!(
!all_ones,
"nonce MUST have entropy (not all ones)"
);
assert!(!all_ones, "nonce MUST have entropy (not all ones)");

// Check that we have reasonable byte diversity
let unique_bytes: HashSet<u8> = nonce.iter().copied().collect();
Expand Down Expand Up @@ -101,10 +88,7 @@ fn test_nonce_replay_detection() {
"replayed nonce MUST be detected"
);

assert!(
store.is_used(&nonce),
"used nonce MUST be tracked"
);
assert!(store.is_used(&nonce), "used nonce MUST be tracked");
}

/// COMPLIANCE: Different nonces MUST be allowed.
Expand Down
Loading
Loading