@@ -141,34 +141,70 @@ binary_allowed(_, _) if {
141141 not binary_identity_required
142142}
143143
144+ # Binary path matching uses exact spelling by default. On Windows, Rust adds an
145+ # ASCII-folded candidate only after querying every parent directory of the
146+ # runtime-observed executable and confirming case-insensitive lookup throughout.
147+ binary_path_matches (binary, candidate) if {
148+ binary.path == candidate.path
149+ }
150+
151+ binary_path_matches (binary, candidate) if {
152+ candidate.ascii_case_folded != " "
153+ binary.ascii_case_folded_path == candidate.ascii_case_folded
154+ }
155+
156+ binary_glob_matches (binary, candidate) if {
157+ glob.match (binary.path, [" /" ], candidate.path)
158+ }
159+
160+ binary_glob_matches (binary, candidate) if {
161+ candidate.ascii_case_folded != " "
162+ glob.match (binary.ascii_case_folded_path, [" /" ], candidate.ascii_case_folded)
163+ }
164+
144165# Binary matching: exact path.
145166# SHA256 integrity is enforced in Rust via trust-on-first-use (TOFU) cache,
146167# not in Rego. The proxy computes and caches binary hashes at runtime.
147168binary_allowed (policy, exec) if {
148- some b
149- b := policy.binaries[_]
169+ some b in policy.binaries
150170 not contains (b.path, " *" )
151171 b.path == exec.path
152172}
153173
154174# Binary matching: ancestor exact path (e.g., claude spawns node).
155175binary_allowed (policy, exec) if {
156- some b
157- b := policy.binaries[_]
176+ some b in policy.binaries
158177 not contains (b.path, " *" )
159- ancestor := exec.ancestors[_]
178+ some ancestor in exec.ancestors
160179 b.path == ancestor
161180}
162181
182+ # Trusted runtime evidence may additionally authorize an ASCII case-folded
183+ # Windows path. Legacy and synthetic L7 inputs omit match_paths and continue to
184+ # use only the exact rules above.
185+ binary_allowed (policy, exec) if {
186+ some b in policy.binaries
187+ not contains (b.path, " *" )
188+ some candidate in exec.match_paths
189+ binary_path_matches (b, candidate)
190+ }
191+
163192# Binary matching: glob pattern against exe path or any ancestor.
164193# NOTE: cmdline_paths are intentionally excluded — argv[0] is trivially
165194# spoofable via execve and must not be used as a grant-access signal.
166195binary_allowed (policy, exec) if {
167196 some b in policy.binaries
168197 contains (b.path, " *" )
169198 all_paths := array.concat ([exec.path], exec.ancestors)
170- some p in all_paths
171- glob.match (b.path, [" /" ], p)
199+ some path in all_paths
200+ glob.match (b.path, [" /" ], path)
201+ }
202+
203+ binary_allowed (policy, exec) if {
204+ some b in policy.binaries
205+ contains (b.path, " *" )
206+ some candidate in exec.match_paths
207+ binary_glob_matches (b, candidate)
172208}
173209
174210# --- Network action (allow / deny) ---
0 commit comments