conduct-cli 0.6.4__tar.gz → 0.6.6__tar.gz
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.
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/PKG-INFO +1 -1
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/pyproject.toml +1 -1
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/hook_template.py +37 -5
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/PKG-INFO +1 -1
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/README.md +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/setup.cfg +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/setup.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/__init__.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/api.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/guard.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/guardmcp.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/hook_precompact_template.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/hook_session_start_template.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/hook_stop_template.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/log_util.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/main.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/mcp_server.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/memory.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli/paxel.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/entry_points.txt +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/requires.txt +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/src/conduct_cli.egg-info/top_level.txt +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_bash_operator_signature.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_command_word_matcher.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_guard_policy.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_guard_savings.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_hook_syntax.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_log_util.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_proxy_env.py +0 -0
- {conduct_cli-0.6.4 → conduct_cli-0.6.6}/tests/test_switch.py +0 -0
|
@@ -179,8 +179,29 @@ def _maybe_sync_policy():
|
|
|
179
179
|
|
|
180
180
|
remote_version = remote.get("version", "")
|
|
181
181
|
local_version = ""
|
|
182
|
+
local_policy: dict = {}
|
|
182
183
|
if POLICY_PATH.exists():
|
|
183
|
-
|
|
184
|
+
local_policy = json.loads(POLICY_PATH.read_text())
|
|
185
|
+
local_version = local_policy.get("version", "")
|
|
186
|
+
|
|
187
|
+
# Meta-rule: block silent fail_closed → fail_open downgrade.
|
|
188
|
+
# An attacker who compromises the policy server cannot silently disable governance.
|
|
189
|
+
# Legitimate downgrade requires fail_mode_downgrade_token in the new policy,
|
|
190
|
+
# matching the token stored when fail_closed was originally set.
|
|
191
|
+
old_mode = local_policy.get("fail_mode", "fail_open")
|
|
192
|
+
new_mode = remote.get("fail_mode", "fail_open")
|
|
193
|
+
if old_mode == "fail_closed" and new_mode != "fail_closed":
|
|
194
|
+
expected = local_policy.get("fail_mode_downgrade_token")
|
|
195
|
+
provided = remote.get("fail_mode_downgrade_token")
|
|
196
|
+
if not expected or expected != provided:
|
|
197
|
+
_post_signature_invalid_event(
|
|
198
|
+
expected_sig=f"fail_mode_downgrade_token:{expected}",
|
|
199
|
+
computed_sig=f"fail_mode_downgrade_token:{provided}",
|
|
200
|
+
policy_version=remote_version,
|
|
201
|
+
hostname=__import__("platform").node(),
|
|
202
|
+
)
|
|
203
|
+
return # reject downgrade — keep existing fail_closed policy
|
|
204
|
+
|
|
184
205
|
if remote_version != local_version:
|
|
185
206
|
POLICY_PATH.write_text(json.dumps(remote, indent=2))
|
|
186
207
|
VERSION_CACHE_PATH.write_text(json.dumps({"ts": time.time(), "version": remote_version}))
|
|
@@ -343,8 +364,18 @@ def _detect_repo(): # -> Optional[str]
|
|
|
343
364
|
|
|
344
365
|
def _detect_ai_tool():
|
|
345
366
|
import os
|
|
346
|
-
|
|
367
|
+
# Explicit env vars — most reliable, set by the tool itself
|
|
368
|
+
if os.environ.get("CLAUDE_CODE_ENTRYPOINT") or os.environ.get("CLAUDECODE"):
|
|
347
369
|
return "claude-code"
|
|
370
|
+
if os.environ.get("CODEX_SESSION_ID") or os.environ.get("CODEX_CLI_VERSION"):
|
|
371
|
+
return "codex"
|
|
372
|
+
# TERM_PROGRAM is set by Cursor, Windsurf, and similar IDE terminals
|
|
373
|
+
term = os.environ.get("TERM_PROGRAM", "").lower()
|
|
374
|
+
if term == "cursor":
|
|
375
|
+
return "cursor"
|
|
376
|
+
if term == "windsurf":
|
|
377
|
+
return "windsurf"
|
|
378
|
+
# PATH-based fallback
|
|
348
379
|
path = os.environ.get("PATH", "")
|
|
349
380
|
if "Codex.app" in path or "codex" in path.lower():
|
|
350
381
|
return "codex"
|
|
@@ -352,7 +383,7 @@ def _detect_ai_tool():
|
|
|
352
383
|
return "cursor"
|
|
353
384
|
if "windsurf" in path.lower():
|
|
354
385
|
return "windsurf"
|
|
355
|
-
return "
|
|
386
|
+
return "claude-code" # default — Claude Code is primary
|
|
356
387
|
|
|
357
388
|
|
|
358
389
|
def _already_warned_this_session(session_id: str, rule_id: str) -> bool:
|
|
@@ -730,11 +761,12 @@ def post_usage_main():
|
|
|
730
761
|
data = json.load(sys.stdin)
|
|
731
762
|
except Exception:
|
|
732
763
|
sys.exit(0)
|
|
733
|
-
session_id = data.get("session_id")
|
|
734
764
|
tool_name = (data.get("tool_name") or "").lower()
|
|
735
765
|
tool_use_id = data.get("tool_use_id")
|
|
736
766
|
transcript_path = data.get("transcript_path")
|
|
737
|
-
is_codex
|
|
767
|
+
is_codex = (tool_use_id or "").startswith("call_")
|
|
768
|
+
# Codex sends transcript_path but not session_id — use path as stable session key
|
|
769
|
+
session_id = data.get("session_id") or (f"transcript:{transcript_path}" if transcript_path else None)
|
|
738
770
|
|
|
739
771
|
if is_codex and transcript_path:
|
|
740
772
|
# Write pending args; spawn delayed background reader so hook exits instantly
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|