Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 49 additions & 0 deletions src/openhuman/agent/harness/credentials.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,53 @@ mod tests {
let output = scrub_credentials(input);
assert!(output.contains("api_key: 1234*[REDACTED]"));
}

// #4453: bare, unlabelled secrets that show up in env dumps / API responses.

#[test]
fn scrubs_bare_aws_access_key() {
let out = scrub_credentials("config dump AKIAIOSFODNN7EXAMPLE trailing text");
assert!(
!out.contains("AKIAIOSFODNN7EXAMPLE"),
"bare AWS access key must be redacted: {out}"
);
assert!(
out.contains("[REDACTED]"),
"redaction marker present: {out}"
);
}

#[test]
fn scrubs_bare_openai_key() {
let out = scrub_credentials("response body sk-abcdefghij1234567890ABCDEFGHIJ end");
assert!(
!out.contains("abcdefghij1234567890ABCDEFGHIJ"),
"openai secret body must be redacted: {out}"
);
assert!(
out.contains("sk-"),
"the sk- scheme is kept for context: {out}"
);
assert!(
out.contains("[REDACTED]"),
"redaction marker present: {out}"
);
}

#[test]
fn scrubs_space_separated_bearer_token() {
let out = scrub_credentials("Authorization: Bearer abcDEF1234567890ghijklmnop done");
assert!(
!out.contains("abcDEF1234567890ghijklmnop"),
"space-separated bearer token must be redacted: {out}"
);
assert!(
out.contains("Bearer"),
"the scheme word is kept for context: {out}"
);
assert!(
out.contains("[REDACTED]"),
"redaction marker present: {out}"
);
}
}
40 changes: 40 additions & 0 deletions src/openhuman/tinyagents/middleware.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3140,6 +3140,46 @@ impl Middleware<()> for ImageAwareMessageTrimMiddleware {
#[cfg(test)]
mod tests {
use super::*;

// #4462: image-aware token estimation. A base64 image marker must be priced
// at the flat IMAGE_MARKER_TOKEN_COST, not chars/4 of its payload — otherwise
// one image reads as millions of tokens and the trimmer evicts everything,
// including the system prompt.

#[test]
fn estimate_text_tokens_markerless_is_chars_over_four() {
assert_eq!(estimate_text_tokens(&"a".repeat(40)), (40 + 3) / 4);
assert_eq!(estimate_text_tokens(""), 0);
}

#[test]
fn estimate_text_tokens_prices_image_marker_flat_not_by_length() {
let huge = "x".repeat(40_000);
let text = format!("[IMAGE:{huge}]");
let tokens = estimate_text_tokens(&text);
// chars/4 of the payload would be ~10_000; the flat price is 1_200.
assert!(
tokens >= IMAGE_MARKER_TOKEN_COST,
"at least the flat image cost: {tokens}"
);
assert!(
tokens < 2_000,
"image priced flat, not by base64 length: {tokens}"
);
}

#[test]
fn estimate_text_tokens_charges_each_image_marker_once() {
let tokens = estimate_text_tokens("[IMAGE:aaaa] and [IMAGE:bbbb]");
assert!(
tokens >= 2 * IMAGE_MARKER_TOKEN_COST,
"two images each priced: {tokens}"
);
assert!(
tokens < 2 * IMAGE_MARKER_TOKEN_COST + 100,
"no runaway from the surrounding text: {tokens}"
);
}
use serde_json::json;
use tinyagents::harness::context::{RunConfig, RunContext};
use tinyagents::harness::model::ModelRequest;
Expand Down
27 changes: 27 additions & 0 deletions src/openhuman/tinyagents/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -684,3 +684,30 @@ fn record_unobserved_turn_usage_gates_on_observed_tokens() {
assert!(record_unobserved_turn_usage("m", 0, 3, 0, 0.0));
assert!(record_unobserved_turn_usage("m", 10, 3, 2, 0.5));
}

#[test]
fn spawn_and_delegate_tools_are_never_registered_on_subagents() {
// #4452: a child run must never be able to register a spawn/delegate tool,
// even if the resolved allowlist somehow contains one — the registration
// site strips these unconditionally as defense-in-depth.
for name in [
"spawn_subagent",
"spawn_worker_thread",
"use_tinyplace",
"agent_prepare_context",
"delegate_research",
"delegate_",
] {
assert!(
is_subagent_spawn_or_delegate_tool(name),
"{name} must be treated as a spawn/delegate tool"
);
}
// Ordinary tools (and near-miss names) must NOT be stripped.
for name in ["shell", "read_file", "web_search", "spawn", "subagent"] {
assert!(
!is_subagent_spawn_or_delegate_tool(name),
"{name} is a normal tool and must not be stripped"
);
}
}
21 changes: 21 additions & 0 deletions src/openhuman/tool_status/ops.rs
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,27 @@ mod tests {
);
}

#[test]
fn policy_denied_marker_is_denied_and_not_recoverable() {
use crate::openhuman::security::POLICY_DENIED_MARKER;
let text = format!("{POLICY_DENIED_MARKER} you declined this shell action");
assert_eq!(class_of(&text), ToolFailureClass::Denied);
// UserDeclined family — never eligible for an auto-retry (#4459).
assert!(!classify(&text, false).recoverable);
}

#[test]
fn policy_denied_ttl_expiry_is_approval_expired_not_timeout() {
use crate::openhuman::security::POLICY_DENIED_MARKER;
// A TTL-expiry deny reason literally contains "timed out" — the policy
// marker must win over the timeout sniff so it classifies as an expired
// approval, NOT an execution Timeout that promises an auto-retry (#4459).
let text = format!("{POLICY_DENIED_MARKER} Approval for 'shell' timed out after 600s");
assert_eq!(class_of(&text), ToolFailureClass::ApprovalExpired);
assert_ne!(class_of(&text), ToolFailureClass::Timeout);
assert!(!classify(&text, false).recoverable);
}

#[test]
fn numeric_status_codes_need_word_boundaries() {
// `403`/`503` embedded in a longer digit run must NOT trip the code
Expand Down
Loading