1use crate::audit::vep::{
2 AuthoritySegment, EvidenceCapsuleV0, IdentitySegment, IntentSegment, RequestCommitment,
3 WitnessSegment,
4};
5use crate::gate::Gate;
6use async_trait::async_trait;
7use once_cell::sync::Lazy;
8use regex::Regex;
9use std::collections::HashMap;
10use std::path::{Path, PathBuf};
11use std::sync::Arc;
12use tokio::sync::Mutex;
13use uuid::Uuid;
14use vex_core::audit::EvidenceCapsule;
15use vex_llm::{Capability, LlmProvider};
16
17use vex_chora::client::AuthorityClient;
18use vex_hardware::api::AgentIdentity;
19
20static L1_RULES: Lazy<Vec<Regex>> = Lazy::new(|| {
23 vec![
24 Regex::new(r"(?i)rm\s+-rf\s+/").expect("L1 regex: rm -rf"),
25 Regex::new(r"(?i)drop\s+table").expect("L1 regex: drop table"),
26 Regex::new(r"(?i)chmod\s+777").expect("L1 regex: chmod 777"),
27 Regex::new(r"(?i)169\.254\.169\.254").expect("L1 regex: metadata service"),
28 Regex::new(r"(?i)\.\./\.\./").expect("L1 regex: path traversal"),
29 Regex::new(r"(?i)shutdown\s+-h\s+now").expect("L1 regex: shutdown"),
30 ]
31});
32
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum SecurityProfile {
35 Standard,
36 Fortress,
37}
38
39#[derive(Debug)]
44pub struct TitanGate {
45 pub inner: Arc<dyn Gate>,
46 pub llm: Arc<dyn LlmProvider>,
47 pub chora: Arc<dyn AuthorityClient>,
48 pub identity: AgentIdentity,
49 pub profile: SecurityProfile,
50 pub throttler: Arc<Mutex<ThrottleGovernor>>,
51}
52
53struct TempFileGuard {
54 path: std::path::PathBuf,
55}
56
57impl Drop for TempFileGuard {
58 fn drop(&mut self) {
59 if self.path.exists() {
60 let _ = std::fs::remove_file(&self.path);
61 }
62 }
63}
64
65pub struct SecurePathResolver;
67
68impl SecurePathResolver {
69 pub fn resolve_deterministic(path: &Path) -> Result<PathBuf, String> {
72 #[cfg(windows)]
73 {
74 use std::os::windows::io::AsRawHandle;
75 use windows_sys::Win32::Storage::FileSystem::{
76 GetFinalPathNameByHandleW, FILE_NAME_NORMALIZED, VOLUME_NAME_DOS,
77 };
78
79 let file = std::fs::File::open(path).map_err(|e| format!("IO_OPEN_ERROR: {}", e))?;
80 let handle = file.as_raw_handle() as *mut std::ffi::c_void;
81 let mut buffer = [0u16; 1024];
82 let len = unsafe {
83 GetFinalPathNameByHandleW(
84 handle,
85 buffer.as_mut_ptr(),
86 buffer.len() as u32,
87 FILE_NAME_NORMALIZED | VOLUME_NAME_DOS,
88 )
89 };
90
91 if len == 0 {
92 return Err("WIN32_PATH_RESOLVE_FAILED".to_string());
93 }
94
95 let path_str = String::from_utf16_lossy(&buffer[..len as usize]);
96 Ok(PathBuf::from(path_str.trim_start_matches(r"\\?\")))
97 }
98
99 #[cfg(target_os = "linux")]
100 {
101 std::fs::canonicalize(path).map_err(|e| format!("LINUX_PATH_RESOLVE_FAILED: {}", e))
104 }
105
106 #[cfg(not(any(windows, target_os = "linux")))]
107 {
108 std::fs::canonicalize(path).map_err(|e| format!("OS_PATH_RESOLVE_FAILED: {}", e))
109 }
110 }
111}
112
113pub struct EntropyGovernor;
115
116impl EntropyGovernor {
117 pub fn calculate_shannon_entropy(data: &str) -> f64 {
120 if data.is_empty() {
121 return 0.0;
122 }
123
124 let mut counts = [0usize; 256];
125 for &byte in data.as_bytes() {
126 counts[byte as usize] += 1;
127 }
128
129 let total = data.len() as f64;
130 let mut entropy = 0.0;
131
132 for &count in &counts {
133 if count > 0 {
134 let p = count as f64 / total;
135 entropy -= p * p.log2();
136 }
137 }
138
139 entropy
140 }
141
142 pub fn check_exfiltration(output: &str, threshold: f64) -> bool {
144 Self::calculate_shannon_entropy(output) > threshold
145 }
146}
147
148#[derive(Debug)]
151pub struct ThrottleGovernor {
152 agent_states: HashMap<Uuid, AgentThrottleState>,
153}
154
155#[derive(Debug)]
156struct AgentThrottleState {
157 entropy_history: Vec<f64>,
158}
159
160impl AgentThrottleState {
161 fn new() -> Self {
162 Self {
163 entropy_history: Vec::with_capacity(5),
164 }
165 }
166
167 fn push_entropy(&mut self, entropy: f64) {
168 if self.entropy_history.len() >= 5 {
169 self.entropy_history.remove(0);
170 }
171 self.entropy_history.push(entropy);
172 }
173
174 fn average_entropy(&self) -> f64 {
175 if self.entropy_history.is_empty() {
176 return 0.0;
177 }
178 let sum: f64 = self.entropy_history.iter().sum();
179 sum / self.entropy_history.len() as f64
180 }
181}
182
183impl ThrottleGovernor {
184 pub fn new() -> Self {
185 Self {
186 agent_states: HashMap::new(),
187 }
188 }
189}
190
191impl Default for ThrottleGovernor {
192 fn default() -> Self {
193 Self::new()
194 }
195}
196
197impl ThrottleGovernor {
198 pub fn check_throttle(
199 &mut self,
200 agent_id: Uuid,
201 current_entropy: f64,
202 profile: SecurityProfile,
203 ) -> Result<f64, String> {
204 let state = self
205 .agent_states
206 .entry(agent_id)
207 .or_insert_with(AgentThrottleState::new);
208 state.push_entropy(current_entropy);
209
210 let avg = state.average_entropy();
211 let threshold = if profile == SecurityProfile::Fortress {
212 5.5
213 } else {
214 6.8
215 };
216
217 if state.entropy_history.len() >= 3 && avg > threshold {
218 return Err(format!(
219 "CUMULATIVE_ENTROPY_VIOLATION: AVG_{:.2} > LIMIT_{:.1}",
220 avg, threshold
221 ));
222 }
223
224 Ok(avg)
225 }
226}
227
228impl TitanGate {
229 pub fn new(
230 inner: Arc<dyn Gate>,
231 llm: Arc<dyn LlmProvider>,
232 chora: Arc<dyn AuthorityClient>,
233 identity: AgentIdentity,
234 profile: SecurityProfile,
235 ) -> Self {
236 #[allow(clippy::arc_with_non_send_sync)]
237 Self {
238 inner,
239 llm,
240 chora,
241 identity,
242 profile,
243 throttler: Arc::new(Mutex::new(ThrottleGovernor::new())),
244 }
245 }
246
247 async fn verify_formal_intent(
250 &self,
251 suggested_output: &str,
252 _role: &str,
253 digest: &str,
254 ) -> Result<String, String> {
255 use tokio::fs;
256 use tokio::process::Command;
257 use tokio::time::{timeout, Duration};
258
259 let mut builder = MagpieAstBuilder::new(self.profile, digest.to_string());
261
262 builder.add_intent(suggested_output)?;
264
265 let mp_source = builder.build();
267
268 let tmp_filename = format!("gate_intent_{}.mp", &Uuid::new_v4().to_string()[..8]);
269 let mut tmp_path = std::env::temp_dir();
270 let magpie_path = crate::utils::find_magpie_binary();
271
272 if cfg!(target_os = "linux") && magpie_path.ends_with(".exe") {
274 if let Ok(cwd) = std::env::current_dir() {
277 if cwd.to_string_lossy().starts_with("/mnt/") {
278 tmp_path = cwd;
279 }
280 }
281 }
282
283 tmp_path.push(&tmp_filename);
284
285 let _guard = TempFileGuard {
287 path: tmp_path.clone(),
288 };
289
290 fs::write(&tmp_path, &mp_source)
291 .await
292 .map_err(|e| format!("IO_ERROR: Failed to write intent file: {}", e))?;
293
294 let mut arg_path = tmp_path.to_string_lossy().to_string();
295
296 if cfg!(target_os = "linux") && magpie_path.ends_with(".exe") {
298 if arg_path.starts_with("/mnt/") {
299 let drive_letter = arg_path.chars().nth(5).unwrap_or('c').to_uppercase();
301 arg_path = format!("{}:\\{}", drive_letter, arg_path[7..].replace('/', "\\"));
302 } else if arg_path.starts_with('/') {
303 let distro =
304 std::env::var("WSL_DISTRO_NAME").unwrap_or_else(|_| "Ubuntu".to_string());
305 arg_path = format!(
306 "\\\\wsl.localhost\\{}{}",
307 distro,
308 arg_path.replace('/', "\\")
309 );
310 }
311 }
312
313 let mut cmd = Command::new(&magpie_path);
314 cmd.arg("--output")
315 .arg("json")
316 .arg("--entry")
317 .arg(&arg_path)
318 .arg("parse");
319
320 let output = timeout(Duration::from_millis(5000), cmd.output())
322 .await
323 .map_err(|_| "MAGPIE_TIMEOUT: Formal verification exceeded 5000ms limit")?
324 .map_err(|e| format!("MAGPIE_SPAWN_ERROR: {}", e))?;
325
326 if output.status.success() {
327 Ok(mp_source)
328 } else {
329 let stdout = String::from_utf8_lossy(&output.stdout);
330 let stderr = String::from_utf8_lossy(&output.stderr);
331
332 let diagnostic_msg =
334 if let Ok(json) = serde_json::from_str::<serde_json::Value>(&stdout) {
335 if let Some(diags) = json.get("diagnostics").and_then(|d| d.as_array()) {
336 let mut collected = Vec::new();
337 for d in diags {
338 if let Some(msg) = d.get("message").and_then(|m| m.as_str()) {
339 collected.push(msg.to_string());
340 }
341 }
342 if !collected.is_empty() {
343 collected.join(" | ")
344 } else {
345 stderr.to_string()
346 }
347 } else {
348 stderr.to_string()
349 }
350 } else {
351 stderr.to_string()
352 };
353
354 Err(format!("MAGPIE_FORMAL_ERROR: {}", diagnostic_msg))
355 }
356 }
357
358 }
360
361struct MagpieAstBuilder {
364 module_name: String,
365 profile: SecurityProfile,
366 digest: String,
367 body_instructions: Vec<String>,
368}
369
370impl MagpieAstBuilder {
371 fn new(profile: SecurityProfile, digest: String) -> Self {
372 Self {
373 module_name: match profile {
374 SecurityProfile::Standard => "standard".to_string(), SecurityProfile::Fortress => "fortress.verify".to_string(),
376 },
377 profile,
378 digest,
379 body_instructions: Vec::new(),
380 }
381 }
382
383 fn add_intent(&mut self, input: &str) -> Result<(), String> {
384 if input.contains('}') {
386 return Err("INJECTION_ATTACK: Input contains forbidden closing brace '}'".to_string());
387 }
388
389 let forbidden_keywords = ["module", "fn", "exports", "imports", "digest"];
391 let scan_input = input.to_lowercase();
392 for &keyword in &forbidden_keywords {
393 let escaped = regex::escape(keyword);
394 let pattern = format!(r"(?i)\b{}\b", escaped);
395 let re = Regex::new(&pattern).map_err(|e| {
396 format!(
397 "INTERNAL_ERROR: Failed to compile keyword regex '{}': {}",
398 keyword, e
399 )
400 })?;
401 if re.is_match(&scan_input) {
402 return Err(format!(
403 "INJECTION_ATTACK: Input contains forbidden keyword '{}'",
404 keyword
405 ));
406 }
407 }
408
409 for line in input.lines() {
411 if !line.trim().is_empty() {
412 self.body_instructions.push(line.trim().to_string());
413 }
414 }
415
416 Ok(())
417 }
418
419 fn build(&self) -> String {
420 let mut out = String::new();
421 out.push_str(&format!("module {}\n", self.module_name));
422 out.push_str("exports { @intent }\n");
423 out.push_str("imports { }\n");
424 out.push_str(&format!("digest \"{}\"\n\n", self.digest));
425
426 if self.profile == SecurityProfile::Fortress {
427 out.push_str(";; THE ONLY PERMITTED SIDE-EFFECT\n");
428 }
429 out.push_str(
430 "fn @log_safe(%msg: Str) -> unit meta { } {\n bb0:\n ret const.unit unit\n}\n\n",
431 );
432
433 out.push_str("fn @intent() -> i32 meta { } {\n bb0:\n");
434 if self.profile == SecurityProfile::Fortress {
435 out.push_str(" ;; Default Deny: If the agent tries to call something unknown, it won't even parse/link.\n");
436 }
437
438 for inst in &self.body_instructions {
439 out.push_str(&format!(" {}\n", inst));
440 }
441
442 let has_ret = self
444 .body_instructions
445 .iter()
446 .any(|i| i.trim_start().starts_with("ret "));
447 if !has_ret {
448 out.push_str(" ret const.i32 0\n");
449 }
450 out.push_str("}\n");
451 out
452 }
453}
454
455#[async_trait]
456impl Gate for TitanGate {
457 async fn execute_gate(
458 &self,
459 agent_id: Uuid,
460 task_prompt: &str,
461 suggested_output: &str,
462 intent_data: Option<vex_core::segment::IntentData>,
463 confidence: f64,
464 capabilities: &[Capability],
465 nonce: Uuid,
466 ) -> EvidenceCapsule {
467 if let Some(ref data) = intent_data {
470 if let Some(token) = data.continuation_token() {
471 let intent_jcs = data
474 .to_jcs_hash()
475 .unwrap_or_else(|_| vex_core::Hash::from_bytes([0; 32]));
476 let local_intent_hash = intent_jcs.to_hex();
477
478 let nonce_str = nonce.to_string();
479 let expected_nonce = data
480 .metadata()
481 .get("original_nonce")
482 .and_then(|v: &serde_json::Value| v.as_str())
483 .or(Some(&nonce_str));
484
485 let parent_root = data
486 .metadata()
487 .get("source_capsule_root")
488 .and_then(|v: &serde_json::Value| v.as_str());
489
490 let aid = self.identity.public_key_hex();
491 match self
492 .chora
493 .verify_continuation_token(
494 token,
495 Some(&aid),
496 Some(&local_intent_hash),
497 data.circuit_id().as_deref(),
498 expected_nonce,
499 parent_root,
500 )
501 .await
502 {
503 Ok(true) => {
504 if token.payload.execution_target.aid != aid {
506 return EvidenceCapsule {
507 capsule_id: format!("halt-binding-{}", &nonce.to_string()[..8]),
508 outcome: "HALT".into(),
509 reason_code:
510 "INVALID_CONTINUATION_TOKEN: EXECUTION_TARGET_MISMATCH".into(),
511 witness_receipt: "target-mismatch".into(),
512 nonce: "0".to_string(),
513 magpie_source: None,
514 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
515 "error": "Token is bound to another agent",
516 "token_target": token.payload.execution_target.aid,
517 "local_aid": aid
518 })),
519 reproducibility_context: vex_core::segment::SchemaValue(
520 serde_json::json!({"gate": "TitanGate/AEM"}),
521 ),
522 resolution_vep_hash: None,
523 escalation_id: None,
524 continuation_token: None,
525 intent_data: intent_data.clone(),
526 vep_blob: None,
527 };
528 }
529
530 if token.payload.execution_target.intent_hash != local_intent_hash {
532 return EvidenceCapsule {
533 capsule_id: format!("halt-intent-{}", &nonce.to_string()[..8]),
534 outcome: "HALT".into(),
535 reason_code: "INVALID_CONTINUATION_TOKEN: INTENT_BINDING_FAILURE"
536 .into(),
537 witness_receipt: "intent-mismatch".into(),
538 nonce: "0".to_string(),
539 magpie_source: None,
540 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
541 "error": "Token is bound to a different intent",
542 "token_intent": token.payload.execution_target.intent_hash,
543 "local_intent": local_intent_hash
544 })),
545 reproducibility_context: vex_core::segment::SchemaValue(
546 serde_json::json!({"gate": "TitanGate/AEM"}),
547 ),
548 resolution_vep_hash: None,
549 escalation_id: None,
550 continuation_token: None,
551 intent_data: intent_data.clone(),
552 vep_blob: None,
553 };
554 }
555
556 tracing::info!("AEM: Continuation Token v3.1 verified for context binding (Silicon Seal).");
557 }
558 Ok(false) | Err(_) => {
559 return EvidenceCapsule {
560 capsule_id: format!("halt-token-err-{}", &nonce.to_string()[..8]),
561 outcome: "HALT".into(),
562 reason_code: "INVALID_CONTINUATION_TOKEN: SIGNATURE_OR_LIFECYCLE_FAIL"
563 .into(),
564 witness_receipt: "token-reject".into(),
565 nonce: "0".to_string(),
566 magpie_source: None,
567 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
568 "error": "Signature check or lifecycle validation failed at the boundary"
569 })),
570 reproducibility_context: vex_core::segment::SchemaValue(
571 serde_json::json!({"gate": "TitanGate/AEM"}),
572 ),
573 resolution_vep_hash: None,
574 escalation_id: None,
575 continuation_token: None,
576 intent_data: intent_data.clone(),
577 vep_blob: None,
578 };
579 }
580 }
581 }
582 }
583
584 let entropy = EntropyGovernor::calculate_shannon_entropy(suggested_output);
587 let entropy_threshold = if self.profile == SecurityProfile::Fortress {
588 6.0
589 } else {
590 7.5
591 };
592
593 if entropy > entropy_threshold {
594 return EvidenceCapsule {
595 capsule_id: format!("l1-block-{}", &nonce.to_string()[..8]),
596 outcome: "HALT".into(),
597 reason_code: "L1_ENTROPY_VIOLATION: HIGH_EXFILTRATION_RISK".into(),
598 witness_receipt: "entropy-block".into(),
599 nonce: "0".to_string(),
600 magpie_source: None,
601 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
602 "layer": "L1",
603 "entropy": entropy,
604 "threshold": entropy_threshold,
605 "trigger": "BEH-007"
606 })),
607 reproducibility_context: vex_core::segment::SchemaValue(
608 serde_json::json!({"gate": "TitanGate/L1"}),
609 ),
610 resolution_vep_hash: None,
611 escalation_id: None,
612 continuation_token: None,
613 intent_data: intent_data.clone(),
614 vep_blob: None,
615 };
616 }
617
618 {
620 let mut throttler = self.throttler.lock().await;
621 if let Err(e) = throttler.check_throttle(agent_id, entropy, self.profile) {
622 return EvidenceCapsule {
623 capsule_id: format!("l1-block-{}", &Uuid::new_v4().to_string()[..8]),
624 outcome: "HALT".into(),
625 reason_code: format!("L1_THROTTLE_VIOLATION: {}", e),
626 witness_receipt: "stateful-throttle-block".into(),
627 nonce: "0".to_string(),
628 magpie_source: None,
629 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
630 "layer": "L1",
631 "cumulative_check": "FAILED",
632 "error": e
633 })),
634 reproducibility_context: vex_core::segment::SchemaValue(
635 serde_json::json!({"gate": "TitanGate/L1"}),
636 ),
637 resolution_vep_hash: None,
638 escalation_id: None,
639 continuation_token: None,
640 intent_data: intent_data.clone(),
641 vep_blob: None,
642 };
643 }
644 }
645
646 if (suggested_output.contains('/') || suggested_output.contains('\\'))
649 && suggested_output.len() < 256
650 {
651 let path = Path::new(suggested_output);
652 if path.exists() {
653 match SecurePathResolver::resolve_deterministic(path) {
654 Ok(resolved) => {
655 let resolved_str = resolved.to_string_lossy().to_lowercase();
656 if resolved_str.contains("etc") || resolved_str.contains("system32") {
657 return EvidenceCapsule {
658 capsule_id: format!(
659 "l1-block-{}",
660 &Uuid::new_v4().to_string()[..8]
661 ),
662 outcome: "HALT".into(),
663 reason_code: "L1_PATH_VIOLATION: SENSITIVE_SYSTEM_PATH".into(),
664 witness_receipt: "path-resolve-block".into(),
665 nonce: "0".to_string(),
666 magpie_source: None,
667 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
668 "layer": "L1",
669 "attempted_path": suggested_output,
670 "resolved_physical_path": resolved_str
671 })),
672 reproducibility_context: vex_core::segment::SchemaValue(
673 serde_json::json!({"gate": "TitanGate/L1"}),
674 ),
675 resolution_vep_hash: None,
676 escalation_id: None,
677 continuation_token: None,
678 intent_data: intent_data.clone(),
679 vep_blob: None,
680 };
681 }
682 }
683 Err(e) => {
684 return EvidenceCapsule {
687 capsule_id: format!("l1-block-{}", &Uuid::new_v4().to_string()[..8]),
688 outcome: "HALT".into(),
689 reason_code: format!("L1_PATH_RESOLUTION_ERROR: {}", e),
690 witness_receipt: "path-resolve-failed".into(),
691 nonce: "0".to_string(),
692 magpie_source: None,
693 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
694 "layer": "L1",
695 "attempted_path": suggested_output,
696 "error": e
697 })),
698 reproducibility_context: vex_core::segment::SchemaValue(
699 serde_json::json!({"gate": "TitanGate/L1"}),
700 ),
701 resolution_vep_hash: None,
702 escalation_id: None,
703 continuation_token: None,
704 intent_data: intent_data.clone(),
705 vep_blob: None,
706 };
707 }
708 }
709 }
710 }
711
712 for rule in L1_RULES.iter() {
714 if rule.is_match(suggested_output) {
715 return EvidenceCapsule {
716 capsule_id: format!("l1-block-{}", &nonce.to_string()[..8]),
717 outcome: "HALT".into(),
718 reason_code: format!("L1_RULE_VIOLATION: {:?}", rule),
719 witness_receipt: "deterministic-none".into(),
720 nonce: "0".to_string(),
721 magpie_source: None,
722 gate_sensors: vex_core::segment::SchemaValue(
723 serde_json::json!({"layer": "L1", "rule": format!("{:?}", rule)}),
724 ),
725 reproducibility_context: vex_core::segment::SchemaValue(
726 serde_json::json!({"gate": "TitanGate/L1"}),
727 ),
728 resolution_vep_hash: None,
729 escalation_id: None,
730 continuation_token: None,
731 intent_data: intent_data.clone(),
732 vep_blob: None,
733 };
734 }
735 }
736
737 let capsule_id = format!("gate-{}", &Uuid::new_v4().to_string()[..8]);
738
739 use sha2::{Digest, Sha256};
742 let mut hasher = Sha256::new();
743 hasher.update(capsule_id.as_bytes());
744 let digest_hex = format!("{:x}", hasher.finalize());
745
746 match self
747 .verify_formal_intent(suggested_output, "StandardAgent", &digest_hex)
748 .await
749 {
750 Ok(mp_source) => {
751 let intent_payload = suggested_output.as_bytes();
754
755 match self
756 .chora
757 .request_attestation(intent_payload, &nonce.to_string())
758 .await
759 {
760 Ok(chora_resp) => {
761 if chora_resp.authority_class.as_deref()
765 == Some(vex_core::segment::AuthorityData::CLASS_AMBIGUITY)
766 {
767 return EvidenceCapsule {
768 capsule_id: format!(
769 "escalate-{}",
770 &chora_resp.authority.capsule_id[..8]
771 ),
772 outcome: "ESCALATE".into(),
773 reason_code: "AMBIGUITY_BAND_DETECTION".into(),
774 witness_receipt: chora_resp.signature.clone(),
775 nonce: chora_resp.authority.nonce.clone(),
776 magpie_source: Some(mp_source.clone()),
777 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
778 "authority_class": "ESCALATABLE_AMBIGUITY",
779 "requires_ems": true,
780 "trace_root": chora_resp.authority.trace_root
781 })),
782 reproducibility_context: vex_core::segment::SchemaValue(
783 serde_json::json!({"gate": "TitanGate/AEM"}),
784 ),
785 resolution_vep_hash: None,
786 escalation_id: chora_resp.authority.escalation_id.clone(),
787 continuation_token: None,
788 intent_data: intent_data.clone(),
789 vep_blob: None,
790 };
791 }
792 let intent = IntentSegment {
794 request_sha256: digest_hex.clone(),
795 confidence,
796 capabilities: capabilities.iter().map(|c| format!("{:?}", c)).collect(),
797 magpie_source: Some(mp_source.clone()),
798 circuit_id: None,
799 intent_data: intent_data.clone(),
800 metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
801 };
802
803 let authority = AuthoritySegment {
804 capsule_id: chora_resp.authority.capsule_id.clone(),
805 outcome: chora_resp.authority.outcome.clone(),
806 reason_code: chora_resp.authority.reason_code.clone(),
807 trace_root: chora_resp.authority.trace_root.clone(),
808 nonce: chora_resp.authority.nonce.clone(),
809 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({
810 "profile": format!("{:?}", self.profile),
811 "l2_digest": digest_hex.clone()
812 })),
813 escalation_id: chora_resp.authority.escalation_id.clone(),
814 binding_status: chora_resp.authority.binding_status.clone(),
815 continuation_token: chora_resp.authority.continuation_token.clone(),
816 authority_class: chora_resp.authority_class.clone(),
817 metadata: chora_resp.authority.metadata,
818 };
819
820 let pcrs = self.identity.get_pcrs(&[0, 7, 11]).await.ok();
821 let identity = IdentitySegment {
822 aid: self.identity.public_key_hex(),
823 identity_type: "TPM_ECC_PERSISTENT".to_string(), pcrs,
825 metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
826 };
827
828 let request_commitment = Some(RequestCommitment {
829 canonicalization: "JCS-RFC8785".to_string(),
830 payload_sha256: digest_hex.clone(),
831 payload_encoding: "application/json".to_string(),
832 });
833
834 let witness = WitnessSegment {
835 chora_node_id: "chora-primary-v1".to_string(),
836 receipt_hash: chora_resp.signature.clone(),
837 timestamp: chrono::Utc::now().timestamp() as u64,
838 metadata: vex_core::segment::SchemaValue(serde_json::json!({})),
839 };
840
841 let mut v0_capsule = match EvidenceCapsuleV0::new(
842 intent,
843 authority,
844 identity,
845 witness,
846 request_commitment,
847 ) {
848 Ok(c) => c,
849 Err(e) => {
850 return EvidenceCapsule {
851 capsule_id: format!("vep-err-{}", &capsule_id),
852 outcome: "HALT".into(),
853 reason_code: format!("VEP_GENERATION_ERROR: {}", e),
854 witness_receipt: "none".into(),
855 nonce: "0".to_string(),
856 magpie_source: None,
857 gate_sensors: vex_core::segment::SchemaValue(
858 serde_json::json!({"layer": "L3", "error": format!("{}", e)}),
859 ),
860 reproducibility_context: vex_core::segment::SchemaValue(
861 serde_json::json!({"gate": "TitanGate/L3"}),
862 ),
863 resolution_vep_hash: None,
864 escalation_id: None,
865 continuation_token: None,
866 intent_data: None,
867 vep_blob: None,
868 };
869 }
870 };
871
872 let root_bytes = match hex::decode(&v0_capsule.capsule_root) {
874 Ok(b) => b,
875 Err(e) => {
876 return EvidenceCapsule {
877 capsule_id: format!("root-err-{}", &capsule_id),
878 outcome: "HALT".into(),
879 reason_code: format!("ROOT_DECODE_ERROR: {}", e),
880 witness_receipt: "none".into(),
881 nonce: "0".to_string(),
882 magpie_source: None,
883 gate_sensors: vex_core::segment::SchemaValue(
884 serde_json::json!({"layer": "L3", "error": format!("{}", e)}),
885 ),
886 reproducibility_context: vex_core::segment::SchemaValue(
887 serde_json::json!({"gate": "TitanGate/L3"}),
888 ),
889 resolution_vep_hash: None,
890 escalation_id: None,
891 continuation_token: None,
892 intent_data: None,
893 vep_blob: None,
894 };
895 }
896 };
897 let hardware_sig = self.identity.sign(&root_bytes);
898 v0_capsule.set_signature(&hardware_sig);
899
900 if let Ok(binary) = v0_capsule.to_vep_binary() {
902 let vep_path = std::env::temp_dir()
903 .join(format!("vep_{}.capsule", v0_capsule.capsule_id));
904 let _ = std::fs::write(vep_path, binary);
905 }
906
907 if v0_capsule.authority.outcome == "ALLOW" {
909 let mut final_result = self
910 .inner
911 .execute_gate(
912 agent_id,
913 task_prompt,
914 suggested_output,
915 intent_data.clone(),
916 confidence,
917 capabilities,
918 nonce,
919 )
920 .await;
921
922 if final_result.vep_blob.is_none() {
924 final_result.vep_blob = v0_capsule.to_vep_binary().ok();
925 }
926 final_result
927 } else {
928 EvidenceCapsule {
929 capsule_id: v0_capsule.capsule_id.clone(),
930 outcome: v0_capsule.authority.outcome.clone(),
931 reason_code: v0_capsule.authority.reason_code.clone(),
932 witness_receipt: v0_capsule.witness_hash.clone(),
933 nonce: v0_capsule.authority.nonce.to_string(),
934 magpie_source: None,
935 gate_sensors: vex_core::segment::SchemaValue(
936 serde_json::json!({"layer": "L3", "chora_sig": chora_resp.signature}),
937 ),
938 reproducibility_context: vex_core::segment::SchemaValue(
939 serde_json::json!({"gate": "TitanGate/L3"}),
940 ),
941 resolution_vep_hash: None,
942 escalation_id: chora_resp.authority.escalation_id.clone(),
943 continuation_token: chora_resp.authority.continuation_token.clone(),
944 intent_data: None,
945 vep_blob: v0_capsule.to_vep_binary().ok(),
946 }
947 }
948 }
949 Err(e) => EvidenceCapsule {
950 capsule_id: format!("l3-err-{}", &capsule_id),
951 outcome: "HALT".into(),
952 reason_code: format!("CHORA_CONNECTION_ERROR: {}", e),
953 witness_receipt: "none".into(),
954 nonce: "0".to_string(),
955 magpie_source: None,
956 gate_sensors: vex_core::segment::SchemaValue(
957 serde_json::json!({"layer": "L3", "error": e}),
958 ),
959 reproducibility_context: vex_core::segment::SchemaValue(
960 serde_json::json!({"gate": "TitanGate/L3"}),
961 ),
962 resolution_vep_hash: None,
963 escalation_id: None,
964 continuation_token: None,
965 intent_data: None,
966 vep_blob: None,
967 },
968 }
969 }
970 Err(e) => EvidenceCapsule {
971 capsule_id: format!("l2-block-{}", &capsule_id),
972 outcome: "HALT".into(),
973 reason_code: format!("L2_FORMAL_VIOLATION: {}", e),
974 witness_receipt: "semantic-none".into(),
975 nonce: "0".to_string(),
976 magpie_source: None,
977 gate_sensors: vex_core::segment::SchemaValue(
978 serde_json::json!({"layer": "L2", "error": e, "digest": digest_hex}),
979 ),
980 reproducibility_context: vex_core::segment::SchemaValue(
981 serde_json::json!({"gate": "TitanGate/L2"}),
982 ),
983 resolution_vep_hash: None,
984 escalation_id: None,
985 continuation_token: None,
986 intent_data: None,
987 vep_blob: None,
988 },
989 }
990 }
991
992 async fn verify_token(
993 &self,
994 token: &vex_core::ContinuationToken,
995 expected_aid: Option<&str>,
996 expected_intent_hash: Option<&str>,
997 expected_circuit_id: Option<&str>,
998 ) -> Result<bool, String> {
999 self.inner
1000 .verify_token(
1001 token,
1002 expected_aid,
1003 expected_intent_hash,
1004 expected_circuit_id,
1005 )
1006 .await
1007 }
1008}
1009
1010#[cfg(test)]
1011mod tests {
1012 use super::*;
1013 use vex_llm::LlmRequest;
1014
1015 #[derive(Debug)]
1016 struct MockInnerGate;
1017 #[async_trait]
1018 impl Gate for MockInnerGate {
1019 async fn execute_gate(
1020 &self,
1021 _id: Uuid,
1022 _p: &str,
1023 _o: &str,
1024 _intent: Option<vex_core::segment::IntentData>,
1025 _c: f64,
1026 _cap: &[Capability],
1027 _n: Uuid,
1028 ) -> EvidenceCapsule {
1029 EvidenceCapsule {
1030 capsule_id: "inner".into(),
1031 outcome: "ALLOW".into(),
1032 reason_code: "OK".into(),
1033 witness_receipt: "root".into(),
1034 nonce: "0".to_string(),
1035 magpie_source: None,
1036 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({})),
1037 reproducibility_context: vex_core::segment::SchemaValue(serde_json::json!({})),
1038 resolution_vep_hash: None,
1039 escalation_id: None,
1040 continuation_token: None,
1041 intent_data: None,
1042 vep_blob: None,
1043 }
1044 }
1045
1046 async fn verify_token(
1047 &self,
1048 _token: &vex_core::ContinuationToken,
1049 _expected_aid: Option<&str>,
1050 _expected_intent_hash: Option<&str>,
1051 _expected_circuit_id: Option<&str>,
1052 ) -> Result<bool, String> {
1053 Ok(true)
1054 }
1055 }
1056
1057 #[derive(Debug)]
1058 struct MockLlm;
1059 #[async_trait]
1060 impl LlmProvider for MockLlm {
1061 fn name(&self) -> &str {
1062 "mock"
1063 }
1064 async fn is_available(&self) -> bool {
1065 true
1066 }
1067 async fn complete(
1068 &self,
1069 _req: LlmRequest,
1070 ) -> std::result::Result<vex_llm::LlmResponse, vex_llm::LlmError> {
1071 Ok(vex_llm::LlmResponse {
1072 content: "mock".into(),
1073 model: "mock".into(),
1074 tokens_used: None,
1075 latency_ms: 0,
1076 trace_root: None,
1077 })
1078 }
1079 }
1080
1081 #[derive(Debug)]
1082 struct MockChora;
1083 #[async_trait]
1084 impl vex_chora::client::AuthorityClient for MockChora {
1085 async fn request_attestation(
1086 &self,
1087 _p: &[u8],
1088 _nonce: &str,
1089 ) -> std::result::Result<vex_chora::client::ChoraResponse, String> {
1090 Ok(vex_chora::client::ChoraResponse {
1091 authority: vex_core::segment::AuthorityData {
1092 capsule_id: "chora".into(),
1093 outcome: "ALLOW".into(),
1094 reason_code: "OK".into(),
1095 trace_root: "00".repeat(32),
1096 nonce: "42".into(),
1097 gate_sensors: vex_core::segment::SchemaValue(serde_json::json!({})),
1098 metadata: vex_core::segment::SchemaValue(serde_json::Value::Null),
1099 escalation_id: None,
1100 continuation_token: None,
1101 binding_status: None,
1102 authority_class: Some("ALLOW_PATH".to_string()),
1103 },
1104 signature: "sig".into(),
1105 intent_hash: None,
1106 authority_class: Some("ALLOW_PATH".to_string()),
1107 })
1108 }
1109 async fn verify_witness_signature(
1110 &self,
1111 _p: &[u8],
1112 _s: &[u8],
1113 ) -> std::result::Result<bool, String> {
1114 Ok(true)
1115 }
1116 async fn verify_continuation_token(
1117 &self,
1118 _token: &vex_core::segment::ContinuationToken,
1119 _expected_aid: Option<&str>,
1120 _expected_intent_hash: Option<&str>,
1121 _expected_circuit_id: Option<&str>,
1122 _expected_nonce: Option<&str>,
1123 _expected_source_capsule_root: Option<&str>,
1124 ) -> std::result::Result<bool, String> {
1125 Ok(true)
1126 }
1127 async fn request_escalation(
1128 &self,
1129 _id: &str,
1130 _context: &serde_json::Value,
1131 ) -> std::result::Result<vex_chora::client::ChoraResponse, String> {
1132 self.request_attestation(b"escalated", "mock-nonce").await
1133 }
1134 }
1135
1136 #[tokio::test]
1137 async fn test_titan_gate_pcr_binding() {
1138 std::env::set_var("VEX_HARDWARE_ATTESTATION", "false");
1140
1141 let identity = AgentIdentity::new();
1142 let gate = TitanGate::new(
1143 Arc::new(MockInnerGate),
1144 Arc::new(MockLlm),
1145 Arc::new(MockChora),
1146 identity.clone(),
1147 SecurityProfile::Standard,
1148 );
1149
1150 let capsule = gate
1151 .execute_gate(
1152 Uuid::new_v4(),
1153 "prompt",
1154 "ret const.i32 0",
1155 None,
1156 1.0,
1157 &[],
1158 Uuid::new_v4(),
1159 )
1160 .await;
1161
1162 if capsule.outcome != "ALLOW" {
1163 println!("GATE_HALT: {} - {}", capsule.outcome, capsule.reason_code);
1164 }
1165 assert_eq!(capsule.outcome, "ALLOW");
1166
1167 let blob = capsule.vep_blob.expect("Missing VEP blob");
1168 let packet = vex_core::vep::VepPacket::new(&blob).unwrap();
1169 let core_capsule = packet.to_capsule().unwrap();
1170
1171 assert_eq!(core_capsule.identity.aid, identity.public_key_hex());
1172 if let Some(pcrs) = core_capsule.identity.pcrs {
1173 for (idx, hash) in pcrs {
1174 println!("✅ HARDWARE PCR BINDING VERIFIED: PCR {} = {}", idx, hash);
1175 }
1176 }
1177 }
1178
1179 #[tokio::test]
1180 async fn test_titan_entropy_halt() {
1181 let identity = AgentIdentity::new();
1182 let gate = TitanGate::new(
1183 Arc::new(MockInnerGate),
1184 Arc::new(MockLlm),
1185 Arc::new(MockChora),
1186 identity.clone(),
1187 SecurityProfile::Fortress,
1188 );
1189
1190 let high_entropy = (0..=255u8).map(|b| b as char).collect::<String>();
1192
1193 let capsule = gate
1194 .execute_gate(
1195 Uuid::new_v4(),
1196 "prompt",
1197 &high_entropy,
1198 None,
1199 1.0,
1200 &[],
1201 Uuid::new_v4(),
1202 )
1203 .await;
1204
1205 assert_eq!(capsule.outcome, "HALT");
1206 assert!(capsule.reason_code.contains("ENTROPY_VIOLATION"));
1207 println!(
1208 "✅ ENTROPY EXFILTRATION BLOCK VERIFIED: {}",
1209 capsule.reason_code
1210 );
1211 }
1212
1213 #[tokio::test]
1214 async fn test_titan_path_resolution() {
1215 let identity = AgentIdentity::new();
1216 let gate = TitanGate::new(
1217 Arc::new(MockInnerGate),
1218 Arc::new(MockLlm),
1219 Arc::new(MockChora),
1220 identity.clone(),
1221 SecurityProfile::Standard,
1222 );
1223
1224 let temp = tempfile::NamedTempFile::new().unwrap();
1226 let path_str = temp.path().to_string_lossy().to_string();
1227
1228 let capsule = gate
1229 .execute_gate(
1230 Uuid::new_v4(),
1231 "prompt",
1232 &path_str,
1233 None,
1234 1.0,
1235 &[],
1236 Uuid::new_v4(),
1237 )
1238 .await;
1239
1240 assert_ne!(
1243 capsule.reason_code,
1244 "L1_PATH_VIOLATION: SENSITIVE_SYSTEM_PATH"
1245 );
1246 println!(
1247 "✅ DETERMINISTIC PATH RESOLUTION VERIFIED (L1 PASSED): {}",
1248 path_str
1249 );
1250 }
1251
1252 #[tokio::test]
1253 async fn test_titan_stateful_throttling() {
1254 let identity = AgentIdentity::new();
1255 let gate = TitanGate::new(
1256 Arc::new(MockInnerGate),
1257 Arc::new(MockLlm),
1258 Arc::new(MockChora),
1259 identity.clone(),
1260 SecurityProfile::Fortress,
1261 );
1262
1263 let agent_id = Uuid::new_v4();
1264 let med_entropy =
1267 "ret const.i32 0 ;; ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+".to_owned();
1268 let e = EntropyGovernor::calculate_shannon_entropy(&med_entropy);
1269 println!("DEBUG: med_entropy score = {:.4}", e);
1270
1271 for i in 1..=2 {
1272 let capsule = gate
1273 .execute_gate(
1274 agent_id,
1275 "prompt",
1276 &med_entropy,
1277 None,
1278 1.0,
1279 &[],
1280 Uuid::new_v4(),
1281 )
1282 .await;
1283 assert_eq!(
1284 capsule.outcome, "ALLOW",
1285 "Call {} failed: {} (Entropy: {:.4})",
1286 i, capsule.reason_code, e
1287 );
1288 }
1289
1290 let capsule = gate
1291 .execute_gate(
1292 agent_id,
1293 "prompt",
1294 &med_entropy,
1295 None,
1296 1.0,
1297 &[],
1298 Uuid::new_v4(),
1299 )
1300 .await;
1301 assert_eq!(capsule.outcome, "HALT");
1302 assert!(
1303 capsule.reason_code.contains("L1_THROTTLE_VIOLATION"),
1304 "Expected throttle violation, got: {}",
1305 capsule.reason_code
1306 );
1307 println!("✅ STATEFUL THROTTLING VERIFIED: {}", capsule.reason_code);
1308 }
1309
1310 #[tokio::test]
1311 async fn test_titan_throttle_recovery() {
1312 let identity = AgentIdentity::new();
1313 let gate = TitanGate::new(
1314 Arc::new(MockInnerGate),
1315 Arc::new(MockLlm),
1316 Arc::new(MockChora),
1317 identity.clone(),
1318 SecurityProfile::Fortress,
1319 );
1320
1321 let agent_id = Uuid::new_v4();
1322 let med_entropy =
1323 "ret const.i32 0 ;; ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()_+".to_owned();
1324 let low_entropy = "ret const.i32 0".to_owned();
1325
1326 for _ in 0..2 {
1327 gate.execute_gate(
1328 agent_id,
1329 "prompt",
1330 &med_entropy,
1331 None,
1332 1.0,
1333 &[],
1334 Uuid::new_v4(),
1335 )
1336 .await;
1337 }
1338
1339 for i in 0..3 {
1340 let capsule = gate
1341 .execute_gate(
1342 agent_id,
1343 "prompt",
1344 &low_entropy,
1345 None,
1346 1.0,
1347 &[],
1348 Uuid::new_v4(),
1349 )
1350 .await;
1351 assert_eq!(
1352 capsule.outcome, "ALLOW",
1353 "Recovery call {} failed: {}",
1354 i, capsule.reason_code
1355 );
1356 }
1357
1358 let capsule = gate
1359 .execute_gate(
1360 agent_id,
1361 "prompt",
1362 &med_entropy,
1363 None,
1364 1.0,
1365 &[],
1366 Uuid::new_v4(),
1367 )
1368 .await;
1369 assert_eq!(
1370 capsule.outcome, "ALLOW",
1371 "Call after recovery failed: {}",
1372 capsule.reason_code
1373 );
1374 println!("✅ THROTTLE RECOVERY VERIFIED");
1375 }
1376
1377 #[tokio::test]
1378 async fn test_titan_path_block() {
1379 let identity = AgentIdentity::new();
1380 let gate = TitanGate::new(
1381 Arc::new(MockInnerGate),
1382 Arc::new(MockLlm),
1383 Arc::new(MockChora),
1384 identity.clone(),
1385 SecurityProfile::Standard,
1386 );
1387
1388 let sensitive = if cfg!(windows) {
1389 "C:\\Windows\\System32\\drivers\\etc\\hosts"
1390 } else {
1391 "/etc/shadow"
1392 };
1393
1394 let capsule = gate
1395 .execute_gate(
1396 Uuid::new_v4(),
1397 "prompt",
1398 sensitive,
1399 None,
1400 1.0,
1401 &[],
1402 Uuid::new_v4(),
1403 )
1404 .await;
1405
1406 assert_eq!(capsule.outcome, "HALT");
1407 assert!(
1408 capsule.reason_code.contains("L1_PATH_VIOLATION")
1409 || capsule.reason_code.contains("L1_RULE_VIOLATION")
1410 );
1411 println!("✅ SENSITIVE PATH BLOCK VERIFIED: {}", sensitive);
1412 }
1413}