story-kit: enforce cryptographic identity verification for Matrix commands (story 246)
Remove the require_verified_devices config toggle. The bot now always requires encrypted rooms and cross-signing-verified devices before executing any command. Messages from unencrypted rooms or unverified devices are rejected. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,9 +77,6 @@ pub struct BotContext {
|
||||
/// bot so it can continue a conversation thread without requiring an
|
||||
/// explicit `@mention` on every follow-up.
|
||||
pub bot_sent_event_ids: Arc<TokioMutex<HashSet<OwnedEventId>>>,
|
||||
/// When `true`, the bot rejects messages from users whose devices have not
|
||||
/// been verified via cross-signing in encrypted rooms.
|
||||
pub require_verified_devices: bool,
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
@@ -192,12 +189,9 @@ pub async fn run_bot(config: BotConfig, project_root: PathBuf) -> Result<(), Str
|
||||
history: Arc::new(TokioMutex::new(HashMap::new())),
|
||||
history_size: config.history_size,
|
||||
bot_sent_event_ids: Arc::new(TokioMutex::new(HashSet::new())),
|
||||
require_verified_devices: config.require_verified_devices,
|
||||
};
|
||||
|
||||
if config.require_verified_devices {
|
||||
slog!("[matrix-bot] require_verified_devices is ON — messages from unverified devices in encrypted rooms will be rejected");
|
||||
}
|
||||
slog!("[matrix-bot] Cryptographic identity verification is always ON — commands from unencrypted rooms or unverified devices are rejected");
|
||||
|
||||
// Register event handlers and inject shared context.
|
||||
client.add_event_handler_context(ctx);
|
||||
@@ -475,28 +469,37 @@ async fn on_room_message(
|
||||
return;
|
||||
}
|
||||
|
||||
// When require_verified_devices is enabled and the room is encrypted,
|
||||
// reject messages from users whose devices have not been verified.
|
||||
if ctx.require_verified_devices && room.encryption_state().is_encrypted() {
|
||||
match check_sender_verified(&client, &ev.sender).await {
|
||||
Ok(true) => { /* sender has at least one verified device — proceed */ }
|
||||
Ok(false) => {
|
||||
slog!(
|
||||
"[matrix-bot] WARNING: Rejecting message from {} — \
|
||||
unverified device(s) in encrypted room {}",
|
||||
ev.sender,
|
||||
incoming_room_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
slog!(
|
||||
"[matrix-bot] Error checking verification for {}: {e} — \
|
||||
rejecting message (fail-closed)",
|
||||
ev.sender
|
||||
);
|
||||
return;
|
||||
}
|
||||
// Reject commands from unencrypted rooms — E2EE is mandatory.
|
||||
if !room.encryption_state().is_encrypted() {
|
||||
slog!(
|
||||
"[matrix-bot] Rejecting message from {} — room {} is not encrypted. \
|
||||
Commands are only accepted from encrypted rooms.",
|
||||
ev.sender,
|
||||
incoming_room_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Always verify that the sender has at least one cross-signing-verified
|
||||
// device. This check is unconditional and cannot be disabled via config.
|
||||
match check_sender_verified(&client, &ev.sender).await {
|
||||
Ok(true) => { /* sender has at least one verified device — proceed */ }
|
||||
Ok(false) => {
|
||||
slog!(
|
||||
"[matrix-bot] Rejecting message from {} — no cross-signing-verified \
|
||||
device found in encrypted room {}",
|
||||
ev.sender,
|
||||
incoming_room_id
|
||||
);
|
||||
return;
|
||||
}
|
||||
Err(e) => {
|
||||
slog!(
|
||||
"[matrix-bot] Error checking verification for {}: {e} — \
|
||||
rejecting message (fail-closed)",
|
||||
ev.sender
|
||||
);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -930,7 +933,9 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bot_context_require_verified_devices_field() {
|
||||
fn bot_context_has_no_require_verified_devices_field() {
|
||||
// Verification is always on — BotContext no longer has a toggle field.
|
||||
// This test verifies the struct can be constructed and cloned without it.
|
||||
let ctx = BotContext {
|
||||
bot_user_id: make_user_id("@bot:example.com"),
|
||||
target_room_ids: vec![],
|
||||
@@ -939,15 +944,9 @@ mod tests {
|
||||
history: Arc::new(TokioMutex::new(HashMap::new())),
|
||||
history_size: 20,
|
||||
bot_sent_event_ids: Arc::new(TokioMutex::new(HashSet::new())),
|
||||
require_verified_devices: true,
|
||||
};
|
||||
assert!(ctx.require_verified_devices);
|
||||
|
||||
let ctx_off = BotContext {
|
||||
require_verified_devices: false,
|
||||
..ctx
|
||||
};
|
||||
assert!(!ctx_off.require_verified_devices);
|
||||
// Clone must work (required by Matrix SDK event handler injection).
|
||||
let _cloned = ctx.clone();
|
||||
}
|
||||
|
||||
// -- drain_complete_paragraphs ------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user