Commit efe3918
feat!: rework tool security policy semantics (ANY-match) + developer tools to drive the settings UI (#1993)
* feat: extend developer tools to drive Tools, security policies, and backups
Extend the two existing ha_dev_* developer tools so they can drive every
toggle in the web settings UI, not just the Server Settings matrix.
ha_dev_manage_settings gains list_tools, set_tool (enable/disable/pin,
LLM-API exposure, per-tool security gate), get_policy, set_policy,
get_backup_config, and set_backup_config. ha_dev_manage_server gains
list_pending, approve, and deny for the live security-policy approval queue.
Each action reuses the same persistence and validation as the web settings
handlers -- shared apply_backup_config, the env-pin and BPS-lock guards, and
load_policy/save_policy with its optimistic-concurrency version bump -- so the
tools and the web UI stay in lockstep. Supported on the embedded, add-on, and
container deployments (full-server processes with a live tool registry and
approval queue); the stdio settings sidecar returns a clear error for the
registry/queue-dependent actions.
No new tools are added and no custom-component change is needed (server
package only).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address review findings — shared write lock, framing, list_tools, set_policy
Bundle the Codex + agent review fixes into the developer-tools PR:
- Shared config/policy write lock (utils/config_write_lock) held around every
tool_config.json / tool_policy.json read-modify-write in the web save
handlers (_save_tools, policy _put_config) AND the developer tool, so
concurrent writers can't lose each other's update.
- _atomic_write_json uses a unique temp file (mkstemp) so concurrent writers
to the same path don't collide on a shared .tmp.
- set_policy: version-CAS via the COERCED model version ("3" == 3); "won't
enforce" warning when the engine is off; runs under the shared lock.
- list_tools: report feature-gate availability (available/disabled_by) so
stubs aren't mislabeled enabled, and live-vs-configured policy enforcement
(policies_live) via approval_queue presence.
- set_tool: surface a partial commit when the gate write fails after the
state/LLM-API change already persisted.
- Correct the "sidecar" framing everywhere: _server is never None in a real
deployment; a missing approval queue means policies were off at startup.
- Share the auto-backup field/origin/editable matrix between the web handler
and the dev tool; gate toggle + _gated_tool_names key on the bare rule.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: cover new dev-tool behaviors (live registry, remember-cache, addon backup, partial commit)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: each policy condition is its own rule (ANY-match gating)
The policy editor packed every condition into one rule with predicates AND-ed
("ALL conditions match"). Change it so each condition persists as its OWN rule
under the same tool_name: the card collapses a tool s rules into one editing
view and re-expands to one rule per condition on save, and the evaluator (which
already ORs across rules) then gates if ANY condition matches. No conditions =
one bare rule (always). Copy updated to "ANY" (en/de/ru). removePolicyRule drops
all of a tool s rules; the Tools-tab gate toggle + gatedTools key on the bare
unconditional rule. Adds jsdom tests for expand + enable-direction.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* test: ANY-match policy gating (evaluator unit + e2e approval-flow)
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: accept JSON-string dict args for set_policy/set_backup_config
Live testing on an embedded server surfaced that the policy/backup dict
params rejected a JSON-encoded string ("expected a JSON object"), so
set_policy/set_backup_config were uncallable from MCP clients that stringify
object args. Add JSON_STRING_COERCION (same BeforeValidator ha_call_service
data uses) so a JSON string is parsed into the dict.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat!: migrate pre-ANY policies (AND conditions become OR rules)
BREAKING CHANGE: the pre-#1993 policy editor packed every condition into one
rule with the predicates AND-ed ("require approval when ALL conditions
match"). A one-time startup migration now splits every multi-predicate rule
of an unstamped tool_policy.json into one rule per predicate — old AND
conditions become OR rules, so gating triggers MORE than before (the
fail-safe direction) and enforcement matches the editor's new "ANY condition
matches" copy instead of silently diverging. The file is stamped with
schema_version so the migration runs once; multi-predicate rules authored
after the upgrade (a condition with AND-ed sub-parameters) are preserved.
The policy card now treats each RULE as one condition row: single-predicate
rows are editable, multi-predicate rows display "p1 AND p2" and round-trip
intact instead of being flattened. Migration runs even when policies are
disabled so the file is correct whenever the feature is enabled.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat: stamp serving-server policy state into tools/list _meta (#1990)
A client pointed at a different ha-mcp endpoint than the one the user
configured rules on previously failed SILENTLY: calls executed ungated and
nothing on the wire said "this server has zero rules" (#1990 — the add-on
log shows no MCP traffic reached the configured server at all). Every
tools/list entry now carries _meta.ha_mcp.policy = {enabled, live, rules,
deployment}: the ACTUAL gating state of the server answering this
connection, TTL-cached like the exposure stamp and best-effort so a read
failure can never break tools/list. policy_live is wired from the server so
"configured but not enforcing until restart" is also visible.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address Codex round 3 — approval-action exemption, name validation, rule order, cross-process lock
- PolicyMiddleware exempts ha_dev_manage_server list_pending/approve/deny:
gating queue management deadlocks by construction (approve would create a
second pending entry instead of deciding the first). update_source/restart
stay gateable.
- set_tool validates the tool name against the live registry (incl.
feature-gated stubs) before persisting; best-effort so a metadata failure
never bricks the tool.
- savePolicyRule replaces a tool s rules IN PLACE: rule order is
behaviorally significant (first match supplies remember_minutes).
- config_file_lock (flock/msvcrt) held inside every config/policy RMW
section, so the version CAS also holds across processes (stdio sidecar vs
MCP server). Best-effort on platforms without locking.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: harden policy editor + close remaining review gaps
Address the pr-review-toolkit findings: a failed policy-card auto-save now
toasts loudly and resyncs from the server instead of displaying a phantom
security rule (the #1990 failure shape); per-condition remember_minutes
survive unrelated edits (only touching the input rewrites them);
backup-config writes take the shared cross-process write guard like every
other config surface; the set_tool gate write version-checks before saving
so a degraded file lock fails loud instead of clobbering; docstrings/i18n
catch up (policy _meta stamp shape, lazy data_paths import note, gating
exemption + token self-approval caveat, always/AND strings in en/de/ru).
Coverage: middleware-level exemption pass-through, JSON-string coercion for
the dev-tool policy/backup params, policy-card collapse rendering, and the
migrate-before-enabled-check startup wiring.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* fix: address maintainer review — off-loop file lock, wildcard-aware rule insertion
- config_write_guard() enters/exits the cross-process file lock in a worker
thread so a held or slow flock can't stall the event loop
- a NEW tool rule / bare gate inserts before the first wildcard rule in all
three writers (savePolicyRule, syncPolicyRule, _apply_gate_to_policy)
- list_tools degrades with a warning on a corrupt tool_policy.json instead
of reporting a clean no-gates policy
- the ANY-match migration runs under config_file_lock(data_dir) and logs at
ERROR naming the AND/ANY divergence on failure
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TDz5APvM27J7ZHgRFLXQNg
* test: drain the cancelled ticker via gather to satisfy the CodeQL quality gate
CodeQL flags a bare `await task` inside contextlib.suppress as
py/ineffectual-statement (the same FP is path-allowlisted for
embedded_entry/embedded_server); gather(return_exceptions=True) drains the
cancellation without growing the allowlist.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TDz5APvM27J7ZHgRFLXQNg
* docs: correct the migration lock-safety claim for OAuth/OIDC startup
The migration's inline flock does land on a running loop in OAuth/OIDC mode
(the server is constructed inside asyncio.run), not strictly "before the
event loop" — but construction happens before anything is served and the
sync constructor cannot await, so state the actual invariant: a held lock
can only delay startup, never stall a served client.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TDz5APvM27J7ZHgRFLXQNg
---------
Co-authored-by: kingpanther13 <kingpanther13@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>1 parent e002244 commit efe3918
27 files changed
Lines changed: 3318 additions & 280 deletions
File tree
- src/ha_mcp
- policy
- settings_ui
- locales
- tools
- utils
- tests/src
- e2e/policy
- unit
- policy
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
14 | 14 | | |
15 | 15 | | |
16 | 16 | | |
17 | | - | |
18 | | - | |
19 | | - | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
20 | 21 | | |
21 | 22 | | |
22 | 23 | | |
| |||
31 | 32 | | |
32 | 33 | | |
33 | 34 | | |
34 | | - | |
| 35 | + | |
35 | 36 | | |
36 | 37 | | |
37 | 38 | | |
| |||
50 | 51 | | |
51 | 52 | | |
52 | 53 | | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
53 | 63 | | |
54 | 64 | | |
55 | 65 | | |
| |||
153 | 163 | | |
154 | 164 | | |
155 | 165 | | |
156 | | - | |
157 | | - | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
158 | 174 | | |
| 175 | + | |
| 176 | + | |
159 | 177 | | |
160 | 178 | | |
161 | 179 | | |
| |||
192 | 210 | | |
193 | 211 | | |
194 | 212 | | |
| 213 | + | |
| 214 | + | |
| 215 | + | |
| 216 | + | |
| 217 | + | |
| 218 | + | |
| 219 | + | |
| 220 | + | |
| 221 | + | |
| 222 | + | |
| 223 | + | |
| 224 | + | |
| 225 | + | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
| 246 | + | |
| 247 | + | |
| 248 | + | |
| 249 | + | |
| 250 | + | |
| 251 | + | |
| 252 | + | |
| 253 | + | |
195 | 254 | | |
196 | 255 | | |
197 | 256 | | |
| |||
200 | 259 | | |
201 | 260 | | |
202 | 261 | | |
| 262 | + | |
203 | 263 | | |
204 | 264 | | |
205 | 265 | | |
| |||
209 | 269 | | |
210 | 270 | | |
211 | 271 | | |
| 272 | + | |
212 | 273 | | |
213 | 274 | | |
214 | 275 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
11 | 11 | | |
12 | 12 | | |
13 | 13 | | |
| 14 | + | |
14 | 15 | | |
15 | 16 | | |
16 | 17 | | |
| |||
86 | 87 | | |
87 | 88 | | |
88 | 89 | | |
89 | | - | |
90 | | - | |
91 | | - | |
92 | | - | |
93 | | - | |
94 | | - | |
95 | | - | |
96 | | - | |
97 | | - | |
98 | | - | |
99 | | - | |
100 | | - | |
101 | | - | |
102 | | - | |
103 | | - | |
104 | | - | |
105 | | - | |
106 | | - | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
107 | 113 | | |
108 | 114 | | |
109 | 115 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
38 | 61 | | |
39 | 62 | | |
40 | 63 | | |
| |||
78 | 101 | | |
79 | 102 | | |
80 | 103 | | |
81 | | - | |
| 104 | + | |
82 | 105 | | |
83 | 106 | | |
84 | 107 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
16 | 16 | | |
17 | 17 | | |
18 | 18 | | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
19 | 27 | | |
20 | 28 | | |
21 | 29 | | |
| |||
103 | 111 | | |
104 | 112 | | |
105 | 113 | | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
106 | 117 | | |
107 | 118 | | |
108 | 119 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | 3 | | |
| 4 | + | |
4 | 5 | | |
5 | 6 | | |
6 | 7 | | |
7 | 8 | | |
8 | 9 | | |
9 | 10 | | |
10 | | - | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
11 | 14 | | |
12 | 15 | | |
13 | 16 | | |
| |||
26 | 29 | | |
27 | 30 | | |
28 | 31 | | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
29 | 111 | | |
30 | 112 | | |
31 | 113 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
249 | 249 | | |
250 | 250 | | |
251 | 251 | | |
252 | | - | |
| 252 | + | |
| 253 | + | |
| 254 | + | |
| 255 | + | |
| 256 | + | |
| 257 | + | |
| 258 | + | |
| 259 | + | |
253 | 260 | | |
254 | 261 | | |
255 | 262 | | |
| |||
1022 | 1029 | | |
1023 | 1030 | | |
1024 | 1031 | | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
| 1046 | + | |
| 1047 | + | |
| 1048 | + | |
| 1049 | + | |
| 1050 | + | |
1025 | 1051 | | |
1026 | 1052 | | |
1027 | 1053 | | |
| |||
0 commit comments