@miller-tech/uap 2.21.1 → 2.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/policies/enforcers/__pycache__/_common.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/expert_review_required.cpython-312.pyc +0 -0
- package/src/policies/enforcers/__pycache__/quality_metrics_gate.cpython-312.pyc +0 -0
- package/src/policies/enforcers/enforcement_self_protect.py +9 -1
- package/templates/hooks/__pycache__/deliver_autoroute.cpython-312.pyc +0 -0
- package/templates/hooks/uap-policy-gate.sh +8 -1
- package/tools/agents/scripts/__pycache__/toolcall_path_normalizer.cpython-312.pyc +0 -0
- package/tools/agents/tests/test_enforcement_self_protect.py +19 -0
package/package.json
CHANGED
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -734,7 +734,15 @@ def main() -> None:
|
|
|
734
734
|
op, args = parse_cli()
|
|
735
735
|
|
|
736
736
|
if op in EDIT_OPS:
|
|
737
|
-
|
|
737
|
+
# Key list widened for opencode, which sends camelCase `filePath`.
|
|
738
|
+
# It previously landed on the fail-open "no file path in args" branch,
|
|
739
|
+
# so agent edits to protected files (.uap.json, enforcers, hooks) were
|
|
740
|
+
# allowed whenever the caller used a different key convention — the
|
|
741
|
+
# gate armed its fail-closed net, then this enforcer blinked first and
|
|
742
|
+
# allowed. Widening only ever BLOCKS: every added key names a file the
|
|
743
|
+
# agent is editing, not one it is merely reading.
|
|
744
|
+
target = (args.get("file_path") or args.get("filePath") or args.get("path")
|
|
745
|
+
or args.get("target") or args.get("filename") or args.get("file") or "")
|
|
738
746
|
if not target:
|
|
739
747
|
emit(True, "no file path in args")
|
|
740
748
|
rp = Path(target).resolve()
|
|
Binary file
|
|
@@ -252,7 +252,14 @@ except Exception: a = {}
|
|
|
252
252
|
markers = ("/.policy-tools/", "/src/policies/", "/policies/", "/.uap.json",
|
|
253
253
|
".uap.json", "/.uap/", "anthropic-proxy.env", "uap-policy-gate.sh",
|
|
254
254
|
"uap-reactor-prompt.sh", "pre-tool-use")
|
|
255
|
-
|
|
255
|
+
# Key list matches the one at the top of this script (worktree resolution):
|
|
256
|
+
# harnesses disagree on the arg name (file_path vs filePath vs target/…), and
|
|
257
|
+
# an op whose path key this list misses scores SEC_SENSITIVE=0 — arming
|
|
258
|
+
# neither the fail-closed net nor the fastpath skip. Observed live: opencode
|
|
259
|
+
# sends filePath (camelCase), so a trivial agent edit to .uap.json was
|
|
260
|
+
# fast-pathed around the self-protect enforcer. Widening only TIGHTENS.
|
|
261
|
+
target = (a.get("file_path") or a.get("filePath") or a.get("path")
|
|
262
|
+
or a.get("target") or a.get("filename") or a.get("file") or "")
|
|
256
263
|
cmd = a.get("command") or ""
|
|
257
264
|
# Scan the COMMAND as well as file_path. Only file_path was checked, so for
|
|
258
265
|
# every Bash call SEC_SENSITIVE was 0 unless the command set a bypass var --
|
|
Binary file
|
|
@@ -37,6 +37,25 @@ class SelfProtectTest(unittest.TestCase):
|
|
|
37
37
|
rc, out = run("Edit", {"file_path": str(REPO / ".uap.json")})
|
|
38
38
|
self.assertEqual(rc, 2)
|
|
39
39
|
|
|
40
|
+
def test_blocks_edit_to_uap_json_camel_case(self):
|
|
41
|
+
# opencode sends camelCase `filePath`. The extraction previously read
|
|
42
|
+
# only file_path/path/target, so this shape landed on the fail-open
|
|
43
|
+
# "no file path in args" branch and the edit was ALLOWED — observed
|
|
44
|
+
# live: a looping client repeatedly tried to strip a policy flag from
|
|
45
|
+
# .uap.json and every attempt sailed past this enforcer.
|
|
46
|
+
rc, out = run("Edit", {"filePath": str(REPO / ".uap.json"), "oldString": "x", "newString": "y"})
|
|
47
|
+
self.assertEqual(rc, 2)
|
|
48
|
+
self.assertFalse(out["allowed"])
|
|
49
|
+
self.assertIn("BLOCKED", out["reason"])
|
|
50
|
+
|
|
51
|
+
def test_allows_normal_file_edit_camel_case(self):
|
|
52
|
+
# The widened key list must not over-block: an ordinary file named with
|
|
53
|
+
# the same convention still allows, with the same reason as before.
|
|
54
|
+
rc, out = run("Edit", {"filePath": str(REPO / "README.md"), "oldString": "x", "newString": "y"})
|
|
55
|
+
self.assertEqual(rc, 0)
|
|
56
|
+
self.assertTrue(out["allowed"])
|
|
57
|
+
self.assertEqual(out["reason"], "not an enforcement-control file")
|
|
58
|
+
|
|
40
59
|
def test_blocks_edit_to_policy_tools(self):
|
|
41
60
|
rc, out = run("Write", {"file_path": str(REPO / ".policy-tools/abc_delivery_enforcement.py")})
|
|
42
61
|
self.assertEqual(rc, 2)
|