conduct-cli 0.8.9__tar.gz → 0.9.0__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.
Files changed (50) hide show
  1. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/PKG-INFO +1 -1
  2. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/pyproject.toml +1 -1
  3. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/pretooluse.py +85 -2
  4. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/PKG-INFO +1 -1
  5. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/README.md +0 -0
  6. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/setup.cfg +0 -0
  7. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/setup.py +0 -0
  8. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/__init__.py +0 -0
  9. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/api.py +0 -0
  10. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/guard.py +0 -0
  11. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/guard_policy.md +0 -0
  12. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/guardmcp.py +0 -0
  13. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/__init__.py +0 -0
  14. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/base.py +0 -0
  15. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/posttooluse.py +0 -0
  16. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/precompact.py +0 -0
  17. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/session_parser.py +0 -0
  18. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/session_report_push.py +0 -0
  19. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/session_start.py +0 -0
  20. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/hooks/stop.py +0 -0
  21. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/log_util.py +0 -0
  22. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/main.py +0 -0
  23. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/mcp_server.py +0 -0
  24. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/memory.py +0 -0
  25. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/paxel.py +0 -0
  26. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli/tool_groups.py +0 -0
  27. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/SOURCES.txt +0 -0
  28. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/dependency_links.txt +0 -0
  29. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/entry_points.txt +0 -0
  30. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/requires.txt +0 -0
  31. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/src/conduct_cli.egg-info/top_level.txt +0 -0
  32. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_advisory_and_hook.py +0 -0
  33. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_api_client.py +0 -0
  34. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_bash_operator_signature.py +0 -0
  35. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_blast_radius.py +0 -0
  36. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_cmd_run.py +0 -0
  37. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_command_word_matcher.py +0 -0
  38. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_drain_daemon_health.py +0 -0
  39. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_guard_policy.py +0 -0
  40. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_guard_savings.py +0 -0
  41. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_hook_dispatch.py +0 -0
  42. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_instruction_files.py +0 -0
  43. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_journal_drain.py +0 -0
  44. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_log_util.py +0 -0
  45. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_proxy_coverage.py +0 -0
  46. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_proxy_env.py +0 -0
  47. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_report_tool_coverage.py +0 -0
  48. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_signed_policy.py +0 -0
  49. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_switch.py +0 -0
  50. {conduct_cli-0.8.9 → conduct_cli-0.9.0}/tests/test_token_lifecycle.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.8.9
3
+ Version: 0.9.0
4
4
  Summary: CLI for Conduct AI — secure, govern, install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "conduct-cli"
7
- version = "0.8.9"
7
+ version = "0.9.0"
8
8
  description = "CLI for Conduct AI — secure, govern, install agents, manage projects, run tests"
9
9
  readme = "README.md"
10
10
  license = { text = "MIT" }
@@ -309,6 +309,14 @@ DOC_SENSITIVE_RULE_PREFIXES = (
309
309
  "nist-govern-doc",
310
310
  "eu-ai-pii-",
311
311
  "no-" + "env" + "-read", # split literal to avoid triggering the pattern itself
312
+ # Rules that document what an attack looks like will fire on files that
313
+ # define, seed, or test those attacks. On dev paths (with sentinel) these
314
+ # skip so we can maintain the packs themselves. Not doc-sensitive in the
315
+ # customer sense — sensitive to *authoring* the detection rule.
316
+ "no_prompt_" + "inject",
317
+ "proxy-no-prompt-" + "inject",
318
+ "prompt-" + "inject",
319
+ "nist-govern-" + "policy",
312
320
  )
313
321
 
314
322
  # IRS regulatory pack rules also skip on dev paths since our own code names
@@ -401,6 +409,67 @@ def _rule_is_doc_sensitive(rid: str) -> bool:
401
409
  return False
402
410
 
403
411
 
412
+ _B64_CHUNK = re.compile(r"[A-Za-z0-9+/]{20,}={0,2}")
413
+ _HEX_CHUNK = re.compile(r"\b[0-9a-fA-F]{40,}\b")
414
+ _URL_ESC = re.compile(r"%[0-9a-fA-F]{2}")
415
+
416
+
417
+ def _decoded_variants(text: str) -> list[str]:
418
+ """Return likely-decoded versions of text for obfuscation-bypass matching.
419
+
420
+ Applies base64, hex, URL-decode, and ROT13. Caps output to keep the scan
421
+ cost bounded regardless of input size. Silent on decode failure — bad
422
+ input just yields no extra variants.
423
+ """
424
+ variants: list[str] = []
425
+ if not text:
426
+ return variants
427
+ import base64 as _b64
428
+ import codecs as _codecs
429
+ import urllib.parse as _urlparse
430
+
431
+ for m in _B64_CHUNK.findall(text)[:5]:
432
+ try:
433
+ padded = m + "=" * ((4 - len(m) % 4) % 4)
434
+ decoded = _b64.b64decode(padded, validate=False)
435
+ s = decoded.decode("utf-8", errors="ignore")
436
+ if s.strip():
437
+ variants.append(s)
438
+ except Exception:
439
+ pass
440
+
441
+ for m in _HEX_CHUNK.findall(text)[:3]:
442
+ try:
443
+ s = bytes.fromhex(m).decode("utf-8", errors="ignore")
444
+ if s.strip():
445
+ variants.append(s)
446
+ except Exception:
447
+ pass
448
+
449
+ if _URL_ESC.search(text):
450
+ try:
451
+ s = _urlparse.unquote(text)
452
+ if s and s != text:
453
+ variants.append(s)
454
+ except Exception:
455
+ pass
456
+
457
+ try:
458
+ variants.append(_codecs.decode(text, "rot_13"))
459
+ except Exception:
460
+ pass
461
+
462
+ try:
463
+ import unicodedata as _u
464
+ norm = _u.normalize("NFKC", text)
465
+ if norm != text:
466
+ variants.append(norm)
467
+ except Exception:
468
+ pass
469
+
470
+ return variants[:20]
471
+
472
+
404
473
  def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
405
474
  """Return (matched_rule, action, rule_id, message) or (None, 'allow', None, None)."""
406
475
  pol_path = active_policy_path()
@@ -424,8 +493,15 @@ def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
424
493
  rules = policy.get("rules", [])
425
494
  if tool_name == "Bash" and tool_input.get("command"):
426
495
  input_text = _bash_operator_signature(tool_input["command"])
496
+ raw_for_decode = tool_input["command"]
427
497
  else:
428
498
  input_text = json.dumps(tool_input)
499
+ raw_for_decode = input_text
500
+ # Obfuscation pre-decode: any variant of the input decoded from base64/
501
+ # hex/URL-encoding/ROT13/NFKC. Rules whose "scan" is "decoded" only match
502
+ # against these; all other rules match raw AND decoded so encoded payloads
503
+ # cannot slip past pattern-based detection.
504
+ decoded_variants = _decoded_variants(raw_for_decode)
429
505
  path_fields = [str(tool_input.get(f, "")) for f in ["file_path", "path", "command"]]
430
506
 
431
507
  is_dev_path = _is_developer_source_path(tool_input)
@@ -467,9 +543,16 @@ def check_policy(tool_name: str, tool_input: dict, tokens_before: int = 0):
467
543
  continue
468
544
  pattern = rule.get("match_pattern")
469
545
  if pattern:
546
+ scan_mode = rule.get("scan")
470
547
  try:
471
- if not re.search(pattern, input_text, re.IGNORECASE):
472
- continue
548
+ if scan_mode == "decoded":
549
+ if not any(re.search(pattern, v, re.IGNORECASE) for v in decoded_variants):
550
+ continue
551
+ else:
552
+ if not re.search(pattern, input_text, re.IGNORECASE) and not any(
553
+ re.search(pattern, v, re.IGNORECASE) for v in decoded_variants
554
+ ):
555
+ continue
473
556
  except re.error:
474
557
  continue
475
558
  path_pattern = rule.get("match_path_pattern")
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: conduct-cli
3
- Version: 0.8.9
3
+ Version: 0.9.0
4
4
  Summary: CLI for Conduct AI — secure, govern, install agents, manage projects, run tests
5
5
  Author-email: Conduct AI <hello@conductai.ai>
6
6
  License: MIT
File without changes
File without changes
File without changes