Skip to repository content

tenant.openagents/omega

No repository description is available.

OpenAgents Git authority 2026-07-28T03:10:58.021Z Public web read
NIP-34 coordinate30617:7649603503856e5148d571eac2766b288a8ff1e9e35d380337a1d2b0015b4f92:omega
MaintainersHidden in public view
References2 branches · 1 tag
Read-only clonegit clone https://openagents.com/git/tenant.openagents/omega.git
Browse files

public_ref.rs

212 lines · 7.0 KB · rust
1//! Public-safe reference validation and redaction for the receipt inspector.
2//!
3//! Only bounded, opaque reference strings may reach the interface or a log.
4//! Raw tokens, credentials, private filesystem paths, and unbounded prose are
5//! rejected and never echoed.
6
7use serde::{Deserialize, Serialize};
8
9/// Maximum length for any public-safe reference shown in the inspector.
10pub const PUBLIC_REF_MAX_LEN: usize = 256;
11
12/// A reference that passed public-safety checks.
13#[derive(Clone, Debug, PartialEq, Eq, Hash, Serialize, Deserialize)]
14#[serde(transparent)]
15pub struct PublicRef(String);
16
17impl PublicRef {
18    /// Validate and wrap a candidate reference. Returns `None` when unsafe.
19    pub fn new(raw: impl AsRef<str>) -> Option<Self> {
20        sanitize_public_ref(raw.as_ref())
21    }
22
23    pub fn as_str(&self) -> &str {
24        &self.0
25    }
26
27    pub fn into_string(self) -> String {
28        self.0
29    }
30}
31
32impl std::fmt::Display for PublicRef {
33    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
34        f.write_str(&self.0)
35    }
36}
37
38impl AsRef<str> for PublicRef {
39    fn as_ref(&self) -> &str {
40        &self.0
41    }
42}
43
44/// Return true when `raw` is a bounded public-safe reference.
45pub fn is_public_safe_ref(raw: &str) -> bool {
46    sanitize_public_ref(raw).is_some()
47}
48
49/// Sanitize a candidate reference.
50///
51/// Accepts the same character class as OpenAgents public ref segments:
52/// ASCII letters, digits, `.`, `_`, `:`, and `-`, length 1..=256.
53/// Rejects tokens, private paths, whitespace, and other unsafe material.
54pub fn sanitize_public_ref(raw: &str) -> Option<PublicRef> {
55    let trimmed = raw.trim();
56    if trimmed.is_empty() || trimmed.len() > PUBLIC_REF_MAX_LEN {
57        return None;
58    }
59    if looks_like_private_path(trimmed) {
60        return None;
61    }
62    if looks_like_secret_or_token(trimmed) {
63        return None;
64    }
65    if trimmed.chars().any(|c| c.is_whitespace() || c == '\0') {
66        return None;
67    }
68    if !trimmed
69        .chars()
70        .all(|c| c.is_ascii_alphanumeric() || matches!(c, '.' | '_' | ':' | '-'))
71    {
72        return None;
73    }
74    // Unbounded prose often lacks separators; public refs use dotted segments.
75    // Allow short identifiers without dots (tool names, reason classes) up to
76    // a modest length; longer undotted strings are treated as opaque dumps.
77    if !trimmed.contains('.') && !trimmed.contains(':') && !trimmed.contains('_') && trimmed.len() > 64
78    {
79        return None;
80    }
81    Some(PublicRef(trimmed.to_string()))
82}
83
84fn looks_like_private_path(value: &str) -> bool {
85    let lower = value.to_ascii_lowercase();
86    value.starts_with('/')
87        || value.starts_with("~/")
88        || value.starts_with("\\\\")
89        || (value.len() > 2
90            && value.as_bytes()[1] == b':'
91            && (value.as_bytes()[2] == b'\\' || value.as_bytes()[2] == b'/'))
92        || lower.contains("/users/")
93        || lower.contains("/home/")
94        || lower.contains("/private/")
95        || lower.contains("\\users\\")
96        || lower.ends_with(".pem")
97        || lower.ends_with(".key")
98        || lower.contains("id_rsa")
99        || lower.contains(".ssh/")
100}
101
102fn looks_like_secret_or_token(value: &str) -> bool {
103    let lower = value.to_ascii_lowercase();
104    if lower.starts_with("npub1")
105        && lower.len() == 63
106        && lower[5..]
107            .chars()
108            .all(|character| "023456789acdefghjklmnpqrstuvwxyz".contains(character))
109    {
110        return false;
111    }
112    if lower.contains("bearer ")
113        || lower.contains("authorization:")
114        || lower.starts_with("sk-")
115        || lower.starts_with("sk_")
116        || lower.starts_with("ghp_")
117        || lower.starts_with("gho_")
118        || lower.starts_with("github_pat_")
119        || lower.starts_with("xox")
120        || lower.starts_with("nsec1")
121        || lower.starts_with("ncryptsec1")
122        || lower.contains("api_key")
123        || lower.contains("apikey")
124        || lower.contains("access_token")
125        || lower.contains("refresh_token")
126        || lower.contains("openagents_agent_token")
127        || lower.contains("client_secret")
128        || lower.contains("private_key")
129    {
130        return true;
131    }
132    // Long base64-ish blobs without ref structure are tokens, not refs.
133    if value.len() >= 40
134        && !value.contains('.')
135        && value
136            .chars()
137            .all(|c| c.is_ascii_alphanumeric() || c == '+' || c == '/' || c == '=' || c == '-' || c == '_')
138    {
139        // Still allow dotted public refs; this branch is undotted/opaque.
140        if !value.contains(':') {
141            return true;
142        }
143    }
144    false
145}
146
147#[cfg(test)]
148mod tests {
149    use super::*;
150
151    #[test]
152    fn accepts_dotted_public_refs() {
153        assert!(is_public_safe_ref("tool.sarah.list_full_auto_runs"));
154        assert!(is_public_safe_ref(
155            "receipt.authority.sarah.tool.abcdef.list_full_auto_runs.turn.1"
156        ));
157        assert!(is_public_safe_ref("decision.sarah.tool.call-1"));
158        assert!(is_public_safe_ref("openagents.sarah-owner-orchestrator"));
159        assert!(is_public_safe_ref("blocker.sarah.authority_refused"));
160        assert!(is_public_safe_ref("reserved_action"));
161    }
162
163    #[test]
164    fn rejects_private_paths() {
165        assert!(!is_public_safe_ref("/Users/christopherdavid/.codex/auth.json"));
166        assert!(!is_public_safe_ref("~/work/openagents/secrets"));
167        assert!(!is_public_safe_ref("C:\\Users\\owner\\token.txt"));
168        assert!(!is_public_safe_ref("/home/owner/.ssh/id_rsa"));
169    }
170
171    #[test]
172    fn rejects_tokens_and_secrets() {
173        assert!(!is_public_safe_ref("sk-ant-api03-WOOOOSECRETVALUEHERE"));
174        assert!(!is_public_safe_ref(
175            "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.payload.sig"
176        ));
177        assert!(!is_public_safe_ref("OPENAGENTS_AGENT_TOKEN=abc123"));
178        assert!(!is_public_safe_ref(
179            "ghp_abcdefghijklmnopqrstuvwxyz0123456789"
180        ));
181        assert!(!is_public_safe_ref(
182            "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
183        ));
184        assert!(!is_public_safe_ref(
185            "nsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
186        ));
187        assert!(!is_public_safe_ref(
188            "ncryptsec1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
189        ));
190        assert!(is_public_safe_ref(
191            "npub1qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqq"
192        ));
193    }
194
195    #[test]
196    fn rejects_unbounded_output() {
197        assert!(!is_public_safe_ref(
198            "tool said: here is a long answer with spaces and prose"
199        ));
200        assert!(!is_public_safe_ref("line1\nline2\nline3"));
201        assert!(!is_public_safe_ref(""));
202        let too_long = "a".repeat(PUBLIC_REF_MAX_LEN + 1);
203        assert!(!is_public_safe_ref(&too_long));
204    }
205
206    #[test]
207    fn never_echoes_unsafe_input() {
208        let raw = "sk-secret-token-value-that-must-not-leak";
209        assert!(sanitize_public_ref(raw).is_none());
210    }
211}
212
Served at tenant.openagents/omega Member data and write actions are omitted.