@miller-tech/uap 1.78.0 → 1.79.0

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miller-tech/uap",
3
- "version": "1.78.0",
3
+ "version": "1.79.0",
4
4
  "description": "Autonomous AI agent memory system with CLAUDE.md protocol enforcement",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -100,7 +100,16 @@ def main() -> None:
100
100
 
101
101
  mode = os.environ.get("UAP_ENFORCE_DELIVERY", "block").lower()
102
102
  if mode == "block":
103
- emit(False, msg)
103
+ # R1: emit a machine-actionable routing signal so a capable harness can
104
+ # auto-route the blocked change INTO `uap deliver` instead of leaving the
105
+ # agent to flail. `route`/`deliverHint` are advisory extra fields; harness
106
+ # adapters that understand them route, others just show `reason`.
107
+ emit(
108
+ False,
109
+ msg,
110
+ route="deliver",
111
+ deliverHint=f'uap deliver "implement the intended change to {rel_posix}"',
112
+ )
104
113
 
105
114
  # Advisory (opt-out): never blocks. Surface the nudge, then allow.
106
115
  print(f"[delivery-enforcement advisory] {msg}", file=sys.stderr)
@@ -0,0 +1,119 @@
1
+ """enforcement-self-protect enforcer: the model must not be able to disable
2
+ the delivery gate.
3
+
4
+ A weak local model that hits the delivery-enforcement block tends to try to
5
+ ESCAPE it — observed live (qwen <think>): "set enforcement to advisory mode",
6
+ "just use UAP_DELIVER_BYPASS=1". A single shell `export` does not persist across
7
+ tool calls, so the only way to actually weaken the gate is to PERSIST a bypass:
8
+ write it into a config/env file, or edit the policy/enforcer itself. This
9
+ enforcer blocks exactly those persistence paths.
10
+
11
+ Scope:
12
+ - Edit/Write/MultiEdit to the enforcement control surface (policy md/enforcers,
13
+ .policy-tools/, .uap.json, proxy env, the policy-gate hook scripts).
14
+ - Bash commands that set/export UAP_DELIVER_BYPASS or relax UAP_ENFORCE_DELIVERY,
15
+ write those to a file, or delete/chmod a policy enforcer.
16
+
17
+ There is intentionally NO model-reachable bypass: honoring UAP_DELIVER_BYPASS here
18
+ would reopen the hole (the model would just set it). Operator override is
19
+ out-of-band only: UAP_SELF_PROTECT_OFF=1 in the trusted launch env.
20
+ """
21
+ import os
22
+ import re
23
+ import sys
24
+ from pathlib import Path
25
+
26
+ sys.path.insert(0, str(Path(__file__).parent))
27
+ from _common import emit, parse_cli, repo_root # noqa: E402
28
+
29
+ EDIT_OPS = {"Edit", "Write", "MultiEdit", "edit", "write", "multiedit"}
30
+
31
+ # Path substrings that control delivery enforcement. A write touching any of
32
+ # these can weaken the gate, so it is protected regardless of file extension.
33
+ PROTECTED_MARKERS = (
34
+ "/.policy-tools/",
35
+ "/src/policies/",
36
+ "/policies/", # the policy-definition markdown dir
37
+ "/.uap.json",
38
+ "/.uap/",
39
+ "anthropic-proxy.env",
40
+ "uap-policy-gate.sh",
41
+ "uap-reactor-prompt.sh",
42
+ "pre-tool-use",
43
+ )
44
+ # Normalize leading-slash matching for repo-relative paths.
45
+ PROTECTED_REL = tuple(m.lstrip("/") for m in PROTECTED_MARKERS)
46
+
47
+ # Bash patterns that persist a bypass or relax enforcement.
48
+ BYPASS_PATTERNS = (
49
+ re.compile(r"UAP_DELIVER_BYPASS\s*=\s*['\"]?1", re.I),
50
+ re.compile(r"UAP_ENFORCE_DELIVERY\s*=\s*['\"]?(advisory|off|0|false|no)", re.I),
51
+ re.compile(r"UAP_SELF_PROTECT_OFF\s*=\s*['\"]?1", re.I),
52
+ re.compile(r"UAP_NO_WORKTREE\s*=\s*['\"]?1", re.I),
53
+ re.compile(r"UAP_WORKDIR_SCOPE_OFF\s*=\s*['\"]?1", re.I),
54
+ )
55
+ # Destructive ops against the enforcer/policy surface.
56
+ DESTRUCTIVE = re.compile(
57
+ r"\b(rm|unlink|shred|truncate|mv|chmod)\b[^\n|;&]*"
58
+ r"(\.policy-tools|src/policies|/policies/|anthropic-proxy\.env)",
59
+ re.I,
60
+ )
61
+
62
+ OVERRIDE = os.environ.get("UAP_SELF_PROTECT_OFF") == "1"
63
+
64
+
65
+ def _is_protected_path(rel_posix: str) -> bool:
66
+ low = rel_posix.lower()
67
+ return any(m in ("/" + low) for m in PROTECTED_REL)
68
+
69
+
70
+ def main() -> None:
71
+ if OVERRIDE:
72
+ emit(True, "self-protect disabled by trusted operator override")
73
+
74
+ op, args = parse_cli()
75
+
76
+ if op in EDIT_OPS:
77
+ target = args.get("file_path") or args.get("path") or args.get("target") or ""
78
+ if not target:
79
+ emit(True, "no file path in args")
80
+ try:
81
+ rel = str(Path(target).resolve().relative_to(repo_root()))
82
+ except ValueError:
83
+ # Out-of-repo writes (e.g. the proxy env in ~/.config) — match by name.
84
+ rel = target
85
+ rel_posix = rel.replace(os.sep, "/")
86
+ if _is_protected_path(rel_posix):
87
+ emit(
88
+ False,
89
+ "BLOCKED: this file controls delivery enforcement and cannot be "
90
+ f"edited by the agent ('{rel_posix}'). Do not try to disable or "
91
+ "relax the gate — route your change through the `deliver` tool "
92
+ "instead. (Operator-only override: UAP_SELF_PROTECT_OFF=1.)",
93
+ )
94
+ emit(True, "not an enforcement-control file")
95
+
96
+ if op in {"Bash", "bash"}:
97
+ cmd = args.get("command") or ""
98
+ for pat in BYPASS_PATTERNS:
99
+ if pat.search(cmd):
100
+ emit(
101
+ False,
102
+ "BLOCKED: setting a delivery-enforcement bypass/advisory flag is "
103
+ "not allowed for the agent. Route your change through the "
104
+ "`deliver` tool instead of disabling the gate. "
105
+ "(Operator-only override: UAP_SELF_PROTECT_OFF=1.)",
106
+ )
107
+ if DESTRUCTIVE.search(cmd):
108
+ emit(
109
+ False,
110
+ "BLOCKED: modifying/removing the policy enforcers or proxy env is "
111
+ "not allowed for the agent.",
112
+ )
113
+ emit(True, "no enforcement-tampering in command")
114
+
115
+ emit(True, "not a protected operation")
116
+
117
+
118
+ if __name__ == "__main__":
119
+ main()
@@ -0,0 +1,24 @@
1
+ # Enforcement Self-Protect
2
+
3
+ The delivery gate exists to route coding through `uap deliver`. A weak local
4
+ model that hits the block tends to try to ESCAPE it rather than comply
5
+ (observed: "set enforcement to advisory", "use UAP_DELIVER_BYPASS=1"). This
6
+ policy makes the gate non-disableable by the agent.
7
+
8
+ ## Rules
9
+
10
+ 1. **The control surface is read-only to the agent.** Edits to policy
11
+ definitions, enforcers (`src/policies/`, `.policy-tools/`), `.uap.json`,
12
+ `.uap/`, the proxy env, and the policy-gate hook scripts are blocked.
13
+ 2. **Bypass/relax flags cannot be set by the agent.** Bash commands that set or
14
+ persist `UAP_DELIVER_BYPASS`, relax `UAP_ENFORCE_DELIVERY`, or disable the
15
+ workdir/worktree gates are blocked. (A shell `export` alone does not persist
16
+ across tool calls; this blocks the persistence paths.)
17
+ 3. **No model-reachable bypass.** Honoring a bypass here would reopen the hole.
18
+ Operator override is out-of-band only: `UAP_SELF_PROTECT_OFF=1` in the
19
+ trusted launch environment.
20
+
21
+ ## Rationale
22
+
23
+ Compliance must be the easy path and non-compliance the blocked one. Route
24
+ substantive coding through the `deliver` tool; do not weaken the gate.
@@ -0,0 +1,71 @@
1
+ """Tests for the enforcement-self-protect enforcer (R2)."""
2
+ import importlib.util
3
+ import json
4
+ import os
5
+ import subprocess
6
+ import sys
7
+ from pathlib import Path
8
+
9
+ ENF = Path(__file__).resolve().parents[3] / "src" / "policies" / "enforcers" / "enforcement_self_protect.py"
10
+ REPO = Path(__file__).resolve().parents[3]
11
+
12
+
13
+ def run(op, args, env=None):
14
+ e = dict(os.environ)
15
+ e["UAP_REPO_ROOT"] = str(REPO)
16
+ e.pop("UAP_SELF_PROTECT_OFF", None)
17
+ if env:
18
+ e.update(env)
19
+ p = subprocess.run(
20
+ [sys.executable, str(ENF), "--operation", op, "--args", json.dumps(args)],
21
+ capture_output=True, text=True, env=e,
22
+ )
23
+ out = json.loads(p.stdout) if p.stdout.strip() else {}
24
+ return p.returncode, out
25
+
26
+
27
+ import unittest
28
+
29
+
30
+ class SelfProtectTest(unittest.TestCase):
31
+ def test_blocks_edit_to_policy_enforcer(self):
32
+ rc, out = run("Write", {"file_path": str(REPO / "src/policies/enforcers/delivery_enforcement.py")})
33
+ self.assertEqual(rc, 2)
34
+ self.assertFalse(out["allowed"])
35
+
36
+ def test_blocks_edit_to_uap_json(self):
37
+ rc, out = run("Edit", {"file_path": str(REPO / ".uap.json")})
38
+ self.assertEqual(rc, 2)
39
+
40
+ def test_blocks_edit_to_policy_tools(self):
41
+ rc, out = run("Write", {"file_path": str(REPO / ".policy-tools/abc_delivery_enforcement.py")})
42
+ self.assertEqual(rc, 2)
43
+
44
+ def test_blocks_bash_setting_bypass(self):
45
+ rc, out = run("Bash", {"command": "export UAP_DELIVER_BYPASS=1 && echo hi"})
46
+ self.assertEqual(rc, 2)
47
+
48
+ def test_blocks_bash_advisory_relax(self):
49
+ rc, out = run("Bash", {"command": "UAP_ENFORCE_DELIVERY=advisory uap deliver x"})
50
+ self.assertEqual(rc, 2)
51
+
52
+ def test_blocks_bash_rm_enforcer(self):
53
+ rc, out = run("Bash", {"command": "rm -f .policy-tools/abc_delivery_enforcement.py"})
54
+ self.assertEqual(rc, 2)
55
+
56
+ def test_allows_normal_source_edit(self):
57
+ rc, out = run("Write", {"file_path": str(REPO / "src/app/index.ts")})
58
+ self.assertEqual(rc, 0)
59
+ self.assertTrue(out["allowed"])
60
+
61
+ def test_allows_normal_bash(self):
62
+ rc, out = run("Bash", {"command": "npm test"})
63
+ self.assertEqual(rc, 0)
64
+
65
+ def test_operator_override_allows(self):
66
+ rc, out = run("Bash", {"command": "export UAP_DELIVER_BYPASS=1"}, env={"UAP_SELF_PROTECT_OFF": "1"})
67
+ self.assertEqual(rc, 0)
68
+
69
+
70
+ if __name__ == "__main__":
71
+ unittest.main()