mirror of
https://github.com/outbackdingo/optimclaw.git
synced 2026-08-31 16:49:34 +00:00
fix(security): prevent metadata spoofing of internal job monitor flag (#1195)
The `__internal_job_monitor` metadata key that bypassed the entire agent pipeline (hooks, safety checks, LLM processing) was spoofable by external channels — WASM channel plugins could inject arbitrary metadata including this key, causing attacker-controlled content to be forwarded directly as assistant responses. Replace the metadata-based check with a dedicated `is_internal` field on `IncomingMessage` that can only be set via `into_internal()` by trusted in-process code. Both the field and setter are `pub(crate)` to prevent external crates from spoofing the flag. Also remove `notify_metadata` forwarding (the monitor only needs channel/user/thread routing) and the unused `__job_monitor_job_id` metadata key. Co-authored-by: Claude Opus 4.6 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
3f874e73af
commit
bde0b77a86
@@ -415,7 +415,19 @@ impl CreateJobTool {
|
||||
// loop stops consuming from inject_tx the send will fail and the
|
||||
// monitor terminates. No JoinHandle is retained.
|
||||
if let (Some(etx), Some(itx)) = (&self.event_tx, &self.inject_tx) {
|
||||
crate::agent::job_monitor::spawn_job_monitor(job_id, etx.subscribe(), itx.clone());
|
||||
if let Some(route) = monitor_route_from_ctx(ctx) {
|
||||
crate::agent::job_monitor::spawn_job_monitor(
|
||||
job_id,
|
||||
etx.subscribe(),
|
||||
itx.clone(),
|
||||
route,
|
||||
);
|
||||
} else {
|
||||
tracing::debug!(
|
||||
job_id = %job_id,
|
||||
"Skipping job monitor injection due to missing route metadata"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let result = serde_json::json!({
|
||||
@@ -680,6 +692,36 @@ fn resolve_project_dir(
|
||||
Ok((canonical_dir, browse_id))
|
||||
}
|
||||
|
||||
fn monitor_route_from_ctx(ctx: &JobContext) -> Option<crate::agent::job_monitor::JobMonitorRoute> {
|
||||
// notify_channel is required — without it we don't know which channel to
|
||||
// route the monitor output to, so return None to skip monitoring entirely.
|
||||
let channel = ctx
|
||||
.metadata
|
||||
.get("notify_channel")
|
||||
.and_then(|v| v.as_str())?
|
||||
.to_string();
|
||||
// notify_user is optional — fall back to the job's own user_id, which is
|
||||
// always present. The channel is the routing decision; the user is just
|
||||
// for attribution and can default safely.
|
||||
let user_id = ctx
|
||||
.metadata
|
||||
.get("notify_user")
|
||||
.and_then(|v| v.as_str())
|
||||
.unwrap_or(&ctx.user_id)
|
||||
.to_string();
|
||||
let thread_id = ctx
|
||||
.metadata
|
||||
.get("notify_thread_id")
|
||||
.and_then(|v| v.as_str())
|
||||
.map(|s| s.to_string());
|
||||
|
||||
Some(crate::agent::job_monitor::JobMonitorRoute {
|
||||
channel,
|
||||
user_id,
|
||||
thread_id,
|
||||
})
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
impl Tool for CreateJobTool {
|
||||
fn name(&self) -> &str {
|
||||
|
||||
Reference in New Issue
Block a user