Skip to main content

mur_common/agent/
transport.rs

1use super::*;
2
3#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
4pub struct TransportConfig {
5    pub stdio: bool,
6    pub socket: SocketTransportConfig,
7    #[serde(default)]
8    pub tcp: TcpTransportConfig,
9    /// Track C5 — HTTP webhook receiver. Default off; enabling
10    /// requires an HMAC secret in the OS keychain (`SecretRef`).
11    /// See `docs/superpowers/specs/2026-05-05-mur-agent-c5-webhook-design.md`.
12    #[serde(default)]
13    pub webhook: WebhookTransportConfig,
14}
15
16#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
17pub struct TcpTransportConfig {
18    #[serde(default)]
19    pub enabled: bool,
20    #[serde(default)]
21    pub bind: String,
22    #[serde(default)]
23    pub noise: NoiseConfig,
24}
25
26/// HTTP webhook receiver — Track C5.
27///
28/// External systems POST `SharePayload`-shaped JSON to
29/// `http://<bind>:<port>/agents/<slug>/webhook` with an
30/// `X-Mur-Signature: sha256=<hex>` header carrying an HMAC-SHA256
31/// over the raw body. The HMAC secret is stored in the OS keychain
32/// via `SecretRef` (same pattern as Telegram bot tokens in C2);
33/// `hmac_secret_ref` is the `service:account` lookup key.
34///
35/// `bind` defaults to `127.0.0.1` so a fresh enable doesn't
36/// inadvertently expose the agent to the local network. Users who
37/// want VPN / Tailscale reachability override to `0.0.0.0` or the
38/// VPN interface address explicitly.
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
40pub struct WebhookTransportConfig {
41    #[serde(default)]
42    pub enabled: bool,
43    #[serde(default = "default_webhook_bind")]
44    pub bind: String,
45    #[serde(default = "default_webhook_port")]
46    pub port: u16,
47    /// `service:account` key into the OS keychain. Empty string
48    /// when `enabled = false`; required (and validated) at startup
49    /// when enabled.
50    #[serde(default)]
51    pub hmac_secret_ref: String,
52}
53
54fn default_webhook_bind() -> String {
55    "127.0.0.1".to_string()
56}
57
58fn default_webhook_port() -> u16 {
59    6789
60}
61
62impl Default for WebhookTransportConfig {
63    fn default() -> Self {
64        Self {
65            enabled: false,
66            bind: default_webhook_bind(),
67            port: default_webhook_port(),
68            hmac_secret_ref: String::new(),
69        }
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
74pub struct NoiseConfig {
75    pub pattern: String,
76}
77
78impl Default for NoiseConfig {
79    fn default() -> Self {
80        Self {
81            pattern: "Noise_XK_25519_ChaChaPoly_BLAKE2s".into(),
82        }
83    }
84}
85
86#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
87pub struct SocketTransportConfig {
88    pub enabled: bool,
89    pub bind: String, // "unix:///path" or "tcp://host:port" (P0b)
90    #[serde(default, skip_serializing_if = "Option::is_none")]
91    pub auth: Option<AuthConfig>,
92}
93
94#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
95pub struct AuthConfig {
96    pub scheme: String,
97    pub token_file: String,
98}
99
100#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
101pub struct CommunicationConfig {
102    #[serde(default = "default_accepts_all")]
103    pub accepts_from: Vec<String>,
104    #[serde(default)]
105    pub sends_to: Vec<String>,
106}
107fn default_accepts_all() -> Vec<String> {
108    vec!["*".to_string()]
109}