Skip to content

Commit c00f063

Browse files
author
Symbiont OSS Sync
committed
Update secrets and messaging
1 parent aea02b0 commit c00f063

File tree

8 files changed

+277
-16
lines changed

8 files changed

+277
-16
lines changed

LICENSE

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,30 @@
1-
MIT License
1+
# Symbiont by ThirdKey.ai
2+
Combined Licensing Notice
3+
4+
This repository includes software licensed under both the MIT License and the GNU Affero General Public License v3.0 (AGPL-3.0).
5+
6+
Please review the license terms for each component carefully.
7+
8+
--------------------------------------------------------------------------------
9+
MIT License Applies To:
10+
11+
All files in this repository **except**:
12+
- `/crates/runtime/src/communication/`
13+
14+
This includes:
15+
- `/crates/runtime/` (except `src/communication/`)
16+
- `/crates/cli/`
17+
- `/crates/sdk-python/`, `/sdk-go/`, `/sdk-js/`
18+
- `/examples/`, `/docs/`, `/tools/`, and all other paths
219

320
Copyright (c) 2025 Jascha Wanger / ThirdKey.ai
421

522
Permission is hereby granted, free of charge, to any person obtaining a copy
623
of this software and associated documentation files (the "Software"), to deal
7-
in the Software without restriction, including without limitation the rights
8-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9-
copies of the Software, and to permit persons to whom the Software is
10-
furnished to do so, subject to the following conditions:
24+
in the Software without restriction, including without limitation the rights to
25+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
26+
of the Software, and to permit persons to whom the Software is furnished to do
27+
so, subject to the following conditions:
1128

1229
The above copyright notice and this permission notice shall be included in all
1330
copies or substantial portions of the Software.
@@ -18,4 +35,28 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
1835
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
1936
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2037
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21-
SOFTWARE.
38+
SOFTWARE.
39+
40+
--------------------------------------------------------------------------------
41+
AGPL-3.0 License Applies To:
42+
43+
The communication layer code located in:
44+
45+
- `/crates/runtime/src/communication/`
46+
47+
This component implements the Symbiont Message Bus and Coordination Layer.
48+
49+
It is licensed under the GNU Affero General Public License v3.0:
50+
51+
https://www.gnu.org/licenses/agpl-3.0.html
52+
53+
### Commercial Licensing
54+
55+
If you wish to:
56+
- Use the Symbiont Message Bus in a proprietary or closed-source application,
57+
- Offer it as part of a hosted or managed service,
58+
- Embed it within a commercial product,
59+
60+
you must obtain a commercial license from ThirdKey.ai.
61+
62+
Contact: **licensing@thirdkey.ai**

README.md

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,9 @@ symbi/
9797
- **RAG Engine**: Retrieval-augmented generation with vector search
9898
- **Context Management**: Persistent agent memory and knowledge storage
9999
- **Vector Database**: Qdrant integration for semantic search
100-
- **Basic Secrets Management**: Local encrypted file storage for configurations
101-
- **Cryptographic CLI**: Tool for encrypting/decrypting secret files
100+
- **Comprehensive Secrets Management**: HashiCorp Vault integration with multiple auth methods
101+
- **Encrypted File Backend**: AES-256-GCM encryption with OS keychain integration
102+
- **Secrets CLI Tools**: Complete encrypt/decrypt/edit operations with audit trails
102103
- **HTTP API**: Optional RESTful interface (feature-gated)
103104
104105
### 🏢 Enterprise Features (License Required)
@@ -140,12 +141,45 @@ agent analyze_data(input: DataSet) -> Result {
140141
}
141142
```
142143
144+
## 🔐 Secrets Management
145+
146+
Symbi provides enterprise-grade secrets management with multiple backend options:
147+
148+
### Backend Options
149+
- **HashiCorp Vault**: Production-ready secrets management with multiple authentication methods
150+
- Token-based authentication
151+
- Kubernetes service account authentication
152+
- **Encrypted Files**: Local AES-256-GCM encrypted storage with OS keychain integration
153+
- **Agent Namespaces**: Scoped secrets access per agent for isolation
154+
155+
### CLI Operations
156+
```bash
157+
# Encrypt secrets file
158+
symbi secrets encrypt config.json --output config.enc
159+
160+
# Decrypt secrets file
161+
symbi secrets decrypt config.enc --output config.json
162+
163+
# Edit encrypted secrets directly
164+
symbi secrets edit config.enc
165+
166+
# Configure Vault backend
167+
symbi secrets configure vault --endpoint https://vault.company.com
168+
```
169+
170+
### Audit & Compliance
171+
- Complete audit trails for all secrets operations
172+
- Cryptographic integrity verification
173+
- Agent-scoped access controls
174+
- Tamper-evident logging
175+
143176
## 🔒 Security Model
144177
145178
### Basic Security (Community)
146179
- **Tier 1 Isolation**: Docker containerized agent execution
147180
- **Schema Verification**: Cryptographic tool validation with SchemaPin
148181
- **Policy Engine**: Basic resource access control
182+
- **Secrets Management**: Vault integration and encrypted file storage
149183
- **Audit Logging**: Operation tracking and compliance
150184
151185
### Advanced Security (Enterprise)

SECURITY.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ We take security vulnerabilities seriously. If you discover a security vulnerabi
1818

1919
Instead, please:
2020

21-
1. **Email**: Send details to [INSERT SECURITY EMAIL]
21+
1. **Email**: Send details to security@symbiont.dev
2222
2. **Subject**: Include "SECURITY" in the subject line
2323
3. **Content**: Include the following information:
2424
- Description of the vulnerability

crates/runtime/src/communication/mod.rs

Lines changed: 174 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ use parking_lot::RwLock;
77
use std::collections::HashMap;
88
use std::sync::Arc;
99
use std::time::{Duration, SystemTime};
10-
use tokio::sync::{mpsc, Notify};
11-
use tokio::time::interval;
10+
use tokio::sync::{mpsc, oneshot, Notify};
11+
use tokio::time::{interval, timeout};
1212

1313
use crate::types::*;
1414

@@ -50,6 +50,14 @@ pub trait CommunicationBus {
5050
/// Unregister an agent
5151
async fn unregister_agent(&self, agent_id: AgentId) -> Result<(), CommunicationError>;
5252

53+
/// Send a request and wait for response with timeout
54+
async fn request(
55+
&self,
56+
target_agent: AgentId,
57+
request_payload: bytes::Bytes,
58+
timeout_duration: Duration,
59+
) -> Result<bytes::Bytes, CommunicationError>;
60+
5361
/// Shutdown the communication bus
5462
async fn shutdown(&self) -> Result<(), CommunicationError>;
5563
}
@@ -89,6 +97,7 @@ pub struct DefaultCommunicationBus {
8997
subscriptions: Arc<RwLock<HashMap<String, Vec<AgentId>>>>,
9098
message_tracker: Arc<RwLock<HashMap<MessageId, MessageTracker>>>,
9199
dead_letter_queue: Arc<RwLock<DeadLetterQueue>>,
100+
pending_requests: Arc<RwLock<HashMap<RequestId, oneshot::Sender<bytes::Bytes>>>>,
92101
event_sender: mpsc::UnboundedSender<CommunicationEvent>,
93102
shutdown_notify: Arc<Notify>,
94103
is_running: Arc<RwLock<bool>>,
@@ -103,6 +112,7 @@ impl DefaultCommunicationBus {
103112
let dead_letter_queue = Arc::new(RwLock::new(DeadLetterQueue::new(
104113
config.dead_letter_queue_size,
105114
)));
115+
let pending_requests = Arc::new(RwLock::new(HashMap::new()));
106116
let (event_sender, event_receiver) = mpsc::unbounded_channel();
107117
let shutdown_notify = Arc::new(Notify::new());
108118
let is_running = Arc::new(RwLock::new(true));
@@ -113,6 +123,7 @@ impl DefaultCommunicationBus {
113123
subscriptions,
114124
message_tracker,
115125
dead_letter_queue,
126+
pending_requests,
116127
event_sender,
117128
shutdown_notify,
118129
is_running,
@@ -134,6 +145,7 @@ impl DefaultCommunicationBus {
134145
let subscriptions = self.subscriptions.clone();
135146
let message_tracker = self.message_tracker.clone();
136147
let dead_letter_queue = self.dead_letter_queue.clone();
148+
let pending_requests = self.pending_requests.clone();
137149
let shutdown_notify = self.shutdown_notify.clone();
138150
let config = self.config.clone();
139151

@@ -148,6 +160,7 @@ impl DefaultCommunicationBus {
148160
&subscriptions,
149161
&message_tracker,
150162
&dead_letter_queue,
163+
&pending_requests,
151164
&config,
152165
).await;
153166
} else {
@@ -198,13 +211,24 @@ impl DefaultCommunicationBus {
198211
subscriptions: &Arc<RwLock<HashMap<String, Vec<AgentId>>>>,
199212
message_tracker: &Arc<RwLock<HashMap<MessageId, MessageTracker>>>,
200213
dead_letter_queue: &Arc<RwLock<DeadLetterQueue>>,
214+
pending_requests: &Arc<RwLock<HashMap<RequestId, oneshot::Sender<bytes::Bytes>>>>,
201215
config: &CommunicationConfig,
202216
) {
203217
match event {
204218
CommunicationEvent::MessageSent { message } => {
205219
let recipient = message.recipient;
206220
let message_id = message.id;
207221

222+
// Check if this is a response to a pending request
223+
if let MessageType::Response(request_id) = &message.message_type {
224+
if let Some(sender) = pending_requests.write().remove(request_id) {
225+
// Send response payload to waiting request
226+
let _ = sender.send(message.payload.data.clone());
227+
tracing::debug!("Response {} sent for request {:?}", message_id, request_id);
228+
return;
229+
}
230+
}
231+
208232
// Add to message tracker
209233
message_tracker
210234
.write()
@@ -302,6 +326,7 @@ impl DefaultCommunicationBus {
302326
subscriptions,
303327
message_tracker,
304328
dead_letter_queue,
329+
pending_requests,
305330
config,
306331
))
307332
.await;
@@ -518,6 +543,66 @@ impl CommunicationBus for DefaultCommunicationBus {
518543
Ok(())
519544
}
520545

546+
async fn request(
547+
&self,
548+
target_agent: AgentId,
549+
request_payload: bytes::Bytes,
550+
timeout_duration: Duration,
551+
) -> Result<bytes::Bytes, CommunicationError> {
552+
if !*self.is_running.read() {
553+
return Err(CommunicationError::ShuttingDown);
554+
}
555+
556+
// Create request ID and oneshot channel for response
557+
let request_id = RequestId::new();
558+
let (response_sender, response_receiver) = oneshot::channel();
559+
560+
// Store the response sender
561+
self.pending_requests.write().insert(request_id, response_sender);
562+
563+
// Create request message
564+
let request_message = SecureMessage {
565+
id: MessageId::new(),
566+
sender: AgentId::new(), // TODO: Should be the actual sender agent ID
567+
recipient: Some(target_agent),
568+
topic: None,
569+
message_type: MessageType::Request(request_id),
570+
payload: EncryptedPayload {
571+
data: request_payload,
572+
nonce: vec![0u8; 12], // TODO: Generate proper nonce
573+
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
574+
},
575+
signature: MessageSignature {
576+
signature: vec![0u8; 64], // TODO: Generate proper signature
577+
algorithm: SignatureAlgorithm::Ed25519,
578+
public_key: vec![0u8; 32], // TODO: Use proper public key
579+
},
580+
ttl: timeout_duration,
581+
timestamp: SystemTime::now(),
582+
};
583+
584+
// Send the request
585+
self.send_message(request_message).await?;
586+
587+
// Wait for response with timeout
588+
match timeout(timeout_duration, response_receiver).await {
589+
Ok(Ok(response_payload)) => Ok(response_payload),
590+
Ok(Err(_)) => {
591+
// Remove from pending requests if channel was dropped
592+
self.pending_requests.write().remove(&request_id);
593+
Err(CommunicationError::RequestCancelled { request_id })
594+
}
595+
Err(_) => {
596+
// Timeout occurred
597+
self.pending_requests.write().remove(&request_id);
598+
Err(CommunicationError::RequestTimeout {
599+
request_id,
600+
timeout: timeout_duration,
601+
})
602+
}
603+
}
604+
}
605+
521606
async fn shutdown(&self) -> Result<(), CommunicationError> {
522607
tracing::info!("Shutting down communication bus");
523608

@@ -826,4 +911,91 @@ mod tests {
826911
let result = bus.receive_messages(agent_id).await;
827912
assert!(result.is_err());
828913
}
914+
915+
#[tokio::test]
916+
async fn test_request_response_timeout() {
917+
let bus = DefaultCommunicationBus::new(CommunicationConfig::default())
918+
.await
919+
.unwrap();
920+
let target_agent = AgentId::new();
921+
922+
// Register target agent (but it won't respond)
923+
bus.register_agent(target_agent).await.unwrap();
924+
tokio::time::sleep(Duration::from_millis(50)).await;
925+
926+
// Make request with short timeout
927+
let request_payload = bytes::Bytes::from("test request");
928+
let timeout = Duration::from_millis(100);
929+
930+
let result = bus.request(target_agent, request_payload, timeout).await;
931+
assert!(result.is_err());
932+
933+
if let Err(CommunicationError::RequestTimeout { request_id: _, timeout: actual_timeout }) = result {
934+
assert_eq!(actual_timeout, timeout);
935+
} else {
936+
panic!("Expected RequestTimeout error");
937+
}
938+
}
939+
940+
#[tokio::test]
941+
async fn test_request_response_success() {
942+
let bus = DefaultCommunicationBus::new(CommunicationConfig::default())
943+
.await
944+
.unwrap();
945+
let requester = AgentId::new();
946+
let responder = AgentId::new();
947+
948+
// Register both agents
949+
bus.register_agent(requester).await.unwrap();
950+
bus.register_agent(responder).await.unwrap();
951+
tokio::time::sleep(Duration::from_millis(50)).await;
952+
953+
let request_payload = bytes::Bytes::from("test request");
954+
let response_payload = bytes::Bytes::from("test response");
955+
956+
// Start request in background
957+
let bus_clone = Arc::new(bus);
958+
let request_bus = bus_clone.clone();
959+
let request_handle = tokio::spawn(async move {
960+
request_bus.request(responder, request_payload, Duration::from_secs(5)).await
961+
});
962+
963+
// Give request time to be sent
964+
tokio::time::sleep(Duration::from_millis(100)).await;
965+
966+
// Responder receives the request and sends response
967+
let messages = bus_clone.receive_messages(responder).await.unwrap();
968+
assert_eq!(messages.len(), 1);
969+
assert!(matches!(messages[0].message_type, MessageType::Request(_)));
970+
971+
// Extract request ID and send response
972+
if let MessageType::Request(request_id) = &messages[0].message_type {
973+
let response_message = SecureMessage {
974+
id: MessageId::new(),
975+
sender: responder,
976+
recipient: Some(requester),
977+
topic: None,
978+
message_type: MessageType::Response(*request_id),
979+
payload: EncryptedPayload {
980+
data: response_payload.clone(),
981+
nonce: vec![0u8; 12],
982+
encryption_algorithm: EncryptionAlgorithm::Aes256Gcm,
983+
},
984+
signature: MessageSignature {
985+
signature: vec![0u8; 64],
986+
algorithm: SignatureAlgorithm::Ed25519,
987+
public_key: vec![0u8; 32],
988+
},
989+
ttl: Duration::from_secs(3600),
990+
timestamp: SystemTime::now(),
991+
};
992+
993+
bus_clone.send_message(response_message).await.unwrap();
994+
}
995+
996+
// Wait for request to complete
997+
let result = request_handle.await.unwrap();
998+
assert!(result.is_ok());
999+
assert_eq!(result.unwrap(), response_payload);
1000+
}
8291001
}

0 commit comments

Comments
 (0)