Skip to content

Commit a665652

Browse files
author
Symbiont OSS Sync
committed
Sync OSS release
1 parent 2bae90e commit a665652

File tree

13 files changed

+1430
-147
lines changed

13 files changed

+1430
-147
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
1+
# Worktrees
2+
.worktrees/
3+
14
# ---> Rust
25
# Generated by Cargo
36
# will have compiled files and executables

crates/runtime/src/api/server.rs

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -230,6 +230,13 @@ impl HttpApiServer {
230230
.await
231231
.map_err(|e| RuntimeError::Internal(format!("Failed to bind to {}: {}", addr, e)))?;
232232

233+
if std::env::var("SYMBIONT_API_TOKEN").is_err() {
234+
tracing::warn!(
235+
"SYMBIONT_API_TOKEN not set — API routes are effectively unauthenticated. \
236+
Set this variable in production."
237+
);
238+
}
239+
233240
tracing::info!("HTTP API server starting on {}", addr);
234241

235242
axum::serve(listener, app)
@@ -323,19 +330,24 @@ impl HttpApiServer {
323330
.layer(middleware::from_fn(auth_middleware))
324331
.with_state(provider.clone());
325332

326-
// Other routes with authentication
327-
let other_router = Router::new()
333+
// Protected routes (workflows + metrics) with authentication
334+
let protected_router = Router::new()
328335
.route("/api/v1/workflows/execute", post(execute_workflow))
329336
.route("/api/v1/metrics", get(get_metrics))
330-
.route("/api/v1/health/scheduler", get(get_scheduler_health))
331337
.layer(middleware::from_fn(auth_middleware))
332338
.with_state(provider.clone());
333339

340+
// Health routes — no auth so load-balancer probes work without credentials
341+
let health_router = Router::new()
342+
.route("/api/v1/health/scheduler", get(get_scheduler_health))
343+
.with_state(provider.clone());
344+
334345
router = router
335346
.merge(agent_router)
336347
.merge(schedule_router)
337348
.merge(channel_router)
338-
.merge(other_router);
349+
.merge(protected_router)
350+
.merge(health_router);
339351
}
340352

341353
// Add API key store as extension if available

crates/runtime/src/communication/mod.rs

Lines changed: 76 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,16 @@ pub trait CommunicationBus {
6767

6868
/// Check the health of the communication bus
6969
async fn check_health(&self) -> Result<ComponentHealth, CommunicationError>;
70+
71+
/// Create a properly signed internal message with real crypto
72+
fn create_internal_message(
73+
&self,
74+
sender: AgentId,
75+
recipient: AgentId,
76+
payload_data: bytes::Bytes,
77+
message_type: MessageType,
78+
ttl: std::time::Duration,
79+
) -> SecureMessage;
7080
}
7181

7282
/// Communication bus configuration
@@ -504,33 +514,13 @@ impl DefaultCommunicationBus {
504514
request_payload: bytes::Bytes,
505515
timeout_duration: Duration,
506516
) -> Result<SecureMessage, CommunicationError> {
507-
// Generate proper nonce
508-
let nonce = Self::generate_nonce();
509-
510-
// Create encrypted payload
511-
let payload = EncryptedPayload {
512-
data: request_payload,
513-
nonce,
514-
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
515-
};
516-
517-
// Create a message to sign (we'll sign the payload data)
518-
let message_data_to_sign = [payload.data.as_ref(), &payload.nonce].concat();
519-
520-
// Generate signature
521-
let signature = self.sign_message_data(&message_data_to_sign);
522-
523-
Ok(SecureMessage {
524-
id: MessageId::new(),
525-
sender: self.system_agent_id,
526-
recipient: Some(target_agent),
527-
topic: None,
528-
message_type: MessageType::Request(request_id),
529-
payload,
530-
signature,
531-
ttl: timeout_duration,
532-
timestamp: SystemTime::now(),
533-
})
517+
Ok(self.create_internal_message(
518+
self.system_agent_id,
519+
target_agent,
520+
request_payload,
521+
MessageType::Request(request_id),
522+
timeout_duration,
523+
))
534524
}
535525
}
536526

@@ -758,6 +748,39 @@ impl CommunicationBus for DefaultCommunicationBus {
758748
.with_metric("dead_letters".to_string(), dead_letter_count.to_string())
759749
.with_metric("message_trackers".to_string(), tracker_count.to_string()))
760750
}
751+
752+
fn create_internal_message(
753+
&self,
754+
sender: AgentId,
755+
recipient: AgentId,
756+
payload_data: bytes::Bytes,
757+
message_type: MessageType,
758+
ttl: Duration,
759+
) -> SecureMessage {
760+
let nonce = Self::generate_nonce();
761+
762+
let payload = EncryptedPayload {
763+
data: payload_data,
764+
nonce,
765+
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
766+
};
767+
768+
// Sign the payload data concatenated with the nonce
769+
let message_data_to_sign = [payload.data.as_ref(), &payload.nonce].concat();
770+
let signature = self.sign_message_data(&message_data_to_sign);
771+
772+
SecureMessage {
773+
id: MessageId::new(),
774+
sender,
775+
recipient: Some(recipient),
776+
topic: None,
777+
message_type,
778+
payload,
779+
signature,
780+
ttl,
781+
timestamp: SystemTime::now(),
782+
}
783+
}
761784
}
762785

763786
/// Message queue for an agent
@@ -892,21 +915,35 @@ mod tests {
892915

893916
fn create_test_message(sender: AgentId, recipient: AgentId) -> SecureMessage {
894917
use crate::types::RequestId;
918+
use aes_gcm::{aead::AeadCore, Aes256Gcm};
919+
use ed25519_dalek::Signer;
920+
921+
let mut secret_bytes = [0u8; 32];
922+
OsRng.fill_bytes(&mut secret_bytes);
923+
let signing_key = SigningKey::from_bytes(&secret_bytes);
924+
let verifying_key = signing_key.verifying_key();
925+
926+
let nonce = Aes256Gcm::generate_nonce(&mut OsRng).to_vec();
927+
let data: bytes::Bytes = b"test message".to_vec().into();
928+
929+
let message_data_to_sign = [data.as_ref(), &nonce].concat();
930+
let signature = signing_key.sign(&message_data_to_sign);
931+
895932
SecureMessage {
896933
id: MessageId::new(),
897934
sender,
898935
recipient: Some(recipient),
899936
message_type: MessageType::Request(RequestId::new()),
900937
topic: Some("test".to_string()),
901938
payload: EncryptedPayload {
902-
data: b"test message".to_vec().into(),
903-
nonce: [0u8; 12].to_vec(),
939+
data,
940+
nonce,
904941
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
905942
},
906943
signature: MessageSignature {
907-
signature: vec![0u8; 64],
944+
signature: signature.to_bytes().to_vec(),
908945
algorithm: SignatureAlgorithm::Ed25519,
909-
public_key: vec![0u8; 32],
946+
public_key: verifying_key.to_bytes().to_vec(),
910947
},
911948
ttl: Duration::from_secs(3600),
912949
timestamp: SystemTime::now(),
@@ -1108,27 +1145,15 @@ mod tests {
11081145
assert_eq!(messages.len(), 1);
11091146
assert!(matches!(messages[0].message_type, MessageType::Request(_)));
11101147

1111-
// Extract request ID and send response
1148+
// Extract request ID and send response using create_internal_message
11121149
if let MessageType::Request(request_id) = &messages[0].message_type {
1113-
let response_message = SecureMessage {
1114-
id: MessageId::new(),
1115-
sender: responder,
1116-
recipient: Some(requester),
1117-
topic: None,
1118-
message_type: MessageType::Response(*request_id),
1119-
payload: EncryptedPayload {
1120-
data: response_payload.clone(),
1121-
nonce: vec![0u8; 12],
1122-
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
1123-
},
1124-
signature: MessageSignature {
1125-
signature: vec![0u8; 64],
1126-
algorithm: SignatureAlgorithm::Ed25519,
1127-
public_key: vec![0u8; 32],
1128-
},
1129-
ttl: Duration::from_secs(3600),
1130-
timestamp: SystemTime::now(),
1131-
};
1150+
let response_message = bus_clone.create_internal_message(
1151+
responder,
1152+
requester,
1153+
response_payload.clone(),
1154+
MessageType::Response(*request_id),
1155+
Duration::from_secs(3600),
1156+
);
11321157

11331158
bus_clone.send_message(response_message).await.unwrap();
11341159
}

crates/runtime/src/http_input/config.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ use crate::types::AgentId;
1212
#[cfg(feature = "http-input")]
1313
#[derive(Debug, Clone, Serialize, Deserialize)]
1414
pub struct HttpInputConfig {
15-
/// Address to bind the HTTP server (e.g., "0.0.0.0" or "127.0.0.1")
15+
/// Address to bind the HTTP server (default: "127.0.0.1"; use "0.0.0.0" to listen on all interfaces)
1616
pub bind_address: String,
1717

1818
/// Port number to listen on (e.g., 8081)
@@ -45,8 +45,8 @@ pub struct HttpInputConfig {
4545
/// Optional headers to inject into agent input context
4646
pub forward_headers: Vec<String>,
4747

48-
/// Optional CORS support for browser-facing apps
49-
pub cors_enabled: bool,
48+
/// Optional CORS origin allow-list (empty = CORS disabled, `["*"]` = permissive)
49+
pub cors_origins: Vec<String>,
5050

5151
/// Enable structured audit logging of all received events
5252
pub audit_enabled: bool,
@@ -59,7 +59,7 @@ pub struct HttpInputConfig {
5959
impl Default for HttpInputConfig {
6060
fn default() -> Self {
6161
Self {
62-
bind_address: "0.0.0.0".to_string(),
62+
bind_address: "127.0.0.1".to_string(),
6363
port: 8081,
6464
path: "/webhook".to_string(),
6565
agent: AgentId::new(),
@@ -70,7 +70,7 @@ impl Default for HttpInputConfig {
7070
routing_rules: None,
7171
response_control: None,
7272
forward_headers: vec![],
73-
cors_enabled: false,
73+
cors_origins: vec![],
7474
audit_enabled: true,
7575
webhook_verify: None,
7676
}

0 commit comments

Comments
 (0)