mirror of
https://github.com/tinyhumansai/openhuman.git
synced 2026-07-27 21:08:00 +00:00
This commit is contained in:
@@ -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}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user