From 48594348b475ef7deaf9d4e8a39ee09de13e174c Mon Sep 17 00:00:00 2001 From: PunkDevRobot Date: Fri, 29 May 2026 13:55:17 +0100 Subject: [PATCH] fix(builtins): match sensitive-data keywords on filename, not full path is_sensitive_file matched its keyword set ("private", "auth", "secret", "token", ...) with contains() against the entire canonical path. On macOS the kernel canonicalizes /tmp and /var under /private, so the "private" keyword flagged EVERY file beneath /tmp and /var as sensitive and denied reads of plainly benign files. Any path with a "private"/"auth" component (e.g. /Users/, oauth dirs) hit the same footgun. Scope the keyword match to the file basename, which is the documented intent ("Files with sensitive keywords"). Directory-scoped secrets remain covered by the dedicated ssh/cloud/package/vcs clauses that match on full path prefixes. Cursor ships a different ruleset (is_sensitive_path) without this clause and is unaffected. Verified with opa 1.7.1: /private/tmp/x/greet.txt -> allowed (was denied) /Users/x/private/report.txt -> allowed (was denied) secret.txt, .env, id_rsa, my-passwords.kdbx -> still denied --- .../claude/sensitive_data_protection.rego | 10 ++++++++-- .../factory/sensitive_data_protection.rego | 10 ++++++++-- .../opencode/sensitive_data_protection.rego | 10 ++++++++-- 3 files changed, 24 insertions(+), 6 deletions(-) diff --git a/fixtures/global_builtins/claude/sensitive_data_protection.rego b/fixtures/global_builtins/claude/sensitive_data_protection.rego index 5e4ec5be..44ed5885 100644 --- a/fixtures/global_builtins/claude/sensitive_data_protection.rego +++ b/fixtures/global_builtins/claude/sensitive_data_protection.rego @@ -87,7 +87,13 @@ is_sensitive_file(path) if { } is_sensitive_file(path) if { - lower_path := lower(path) + # Match keywords against the file name only, not the full path. Matching the + # entire path produced false positives: macOS canonicalizes /tmp and /var + # under /private, so the "private" keyword (and "auth") flagged every file + # beneath those roots. Directory-scoped secrets stay covered by the dedicated + # ssh/cloud/package/vcs clauses, which match on their full path prefixes. + segments := split(lower(path), "/") + filename := segments[count(segments) - 1] # Files with sensitive keywords sensitive_keywords := { @@ -104,7 +110,7 @@ is_sensitive_file(path) if { } some keyword in sensitive_keywords - contains(lower_path, keyword) + contains(filename, keyword) } is_sensitive_file(path) if { diff --git a/fixtures/global_builtins/factory/sensitive_data_protection.rego b/fixtures/global_builtins/factory/sensitive_data_protection.rego index fc6dbce6..e61bbd8f 100644 --- a/fixtures/global_builtins/factory/sensitive_data_protection.rego +++ b/fixtures/global_builtins/factory/sensitive_data_protection.rego @@ -87,7 +87,13 @@ is_sensitive_file(path) if { } is_sensitive_file(path) if { - lower_path := lower(path) + # Match keywords against the file name only, not the full path. Matching the + # entire path produced false positives: macOS canonicalizes /tmp and /var + # under /private, so the "private" keyword (and "auth") flagged every file + # beneath those roots. Directory-scoped secrets stay covered by the dedicated + # ssh/cloud/package/vcs clauses, which match on their full path prefixes. + segments := split(lower(path), "/") + filename := segments[count(segments) - 1] # Files with sensitive keywords sensitive_keywords := { @@ -104,7 +110,7 @@ is_sensitive_file(path) if { } some keyword in sensitive_keywords - contains(lower_path, keyword) + contains(filename, keyword) } is_sensitive_file(path) if { diff --git a/fixtures/global_builtins/opencode/sensitive_data_protection.rego b/fixtures/global_builtins/opencode/sensitive_data_protection.rego index fc6dbce6..e61bbd8f 100644 --- a/fixtures/global_builtins/opencode/sensitive_data_protection.rego +++ b/fixtures/global_builtins/opencode/sensitive_data_protection.rego @@ -87,7 +87,13 @@ is_sensitive_file(path) if { } is_sensitive_file(path) if { - lower_path := lower(path) + # Match keywords against the file name only, not the full path. Matching the + # entire path produced false positives: macOS canonicalizes /tmp and /var + # under /private, so the "private" keyword (and "auth") flagged every file + # beneath those roots. Directory-scoped secrets stay covered by the dedicated + # ssh/cloud/package/vcs clauses, which match on their full path prefixes. + segments := split(lower(path), "/") + filename := segments[count(segments) - 1] # Files with sensitive keywords sensitive_keywords := { @@ -104,7 +110,7 @@ is_sensitive_file(path) if { } some keyword in sensitive_keywords - contains(lower_path, keyword) + contains(filename, keyword) } is_sensitive_file(path) if {