deadpush 0.2.2__tar.gz → 0.2.3__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 (64) hide show
  1. {deadpush-0.2.2 → deadpush-0.2.3}/CHANGELOG.md +16 -0
  2. {deadpush-0.2.2 → deadpush-0.2.3}/PKG-INFO +1 -1
  3. deadpush-0.2.3/deadpush/__init__.py +1 -0
  4. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/cli.py +12 -11
  5. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/guard.py +15 -4
  6. deadpush-0.2.2/deadpush/__init__.py +0 -1
  7. {deadpush-0.2.2 → deadpush-0.2.3}/.cursorignore +0 -0
  8. {deadpush-0.2.2 → deadpush-0.2.3}/.github/workflows/ci.yml +0 -0
  9. {deadpush-0.2.2 → deadpush-0.2.3}/.github/workflows/python-publish.yml +0 -0
  10. {deadpush-0.2.2 → deadpush-0.2.3}/.gitignore +0 -0
  11. {deadpush-0.2.2 → deadpush-0.2.3}/.vscode/mcp.json +0 -0
  12. {deadpush-0.2.2 → deadpush-0.2.3}/AGENT.md +0 -0
  13. {deadpush-0.2.2 → deadpush-0.2.3}/CODE_OF_CONDUCT.md +0 -0
  14. {deadpush-0.2.2 → deadpush-0.2.3}/CONTRIBUTING.md +0 -0
  15. {deadpush-0.2.2 → deadpush-0.2.3}/Formula/deadpush.rb +0 -0
  16. {deadpush-0.2.2 → deadpush-0.2.3}/LICENSE +0 -0
  17. {deadpush-0.2.2 → deadpush-0.2.3}/README.md +0 -0
  18. {deadpush-0.2.2 → deadpush-0.2.3}/SECURITY.md +0 -0
  19. {deadpush-0.2.2 → deadpush-0.2.3}/action.yml +0 -0
  20. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/config.py +0 -0
  21. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/debris.py +0 -0
  22. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/deps_guard.py +0 -0
  23. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/hooks.py +0 -0
  24. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/intercept.py +0 -0
  25. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/layers.py +0 -0
  26. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/mcp_server.py +0 -0
  27. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/rules.py +0 -0
  28. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/session.py +0 -0
  29. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/types.py +0 -0
  30. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/ui.py +0 -0
  31. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/verifier.py +0 -0
  32. {deadpush-0.2.2 → deadpush-0.2.3}/deadpush_bootstrap.py +0 -0
  33. {deadpush-0.2.2 → deadpush-0.2.3}/pyproject.toml +0 -0
  34. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/brew_release.sh +0 -0
  35. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/deadpush +0 -0
  36. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/dev_install.sh +0 -0
  37. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/full_e2e_test.py +0 -0
  38. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/hardened_qa.sh +0 -0
  39. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/soak_test.py +0 -0
  40. {deadpush-0.2.2 → deadpush-0.2.3}/scripts/stress_test.py +0 -0
  41. {deadpush-0.2.2 → deadpush-0.2.3}/tests/__init__.py +0 -0
  42. {deadpush-0.2.2 → deadpush-0.2.3}/tests/conftest.py +0 -0
  43. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_attack_chain.py +0 -0
  44. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_bootstrap.py +0 -0
  45. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_deps_guard.py +0 -0
  46. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_exhaustive.py +0 -0
  47. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_failclosed.py +0 -0
  48. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_file_coverage.py +0 -0
  49. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_guardian_closed_loop.py +0 -0
  50. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hardened.py +0 -0
  51. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hooks_enforcement.py +0 -0
  52. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hookspath_hijack.py +0 -0
  53. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_integration_lifecycle.py +0 -0
  54. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_intercept_guardrails.py +0 -0
  55. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_layers.py +0 -0
  56. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_mcp.py +0 -0
  57. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_move_delete_events.py +0 -0
  58. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_os_hardening.py +0 -0
  59. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_prepush_newbranch.py +0 -0
  60. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_real_repo_e2e.py +0 -0
  61. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_root_immutable.py +0 -0
  62. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_rules.py +0 -0
  63. {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_tier0_hardening.py +0 -0
  64. {deadpush-0.2.2 → deadpush-0.2.3}/uv.lock +0 -0
@@ -7,6 +7,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.2.3] - 2026-07-04
11
+
12
+ ### Fixed
13
+ - **`protect`/`guard` now default to soft mode (critical first-run fix)**: the
14
+ default was hardened (`use_hardened = not soft`), so the documented one-liner
15
+ `pip install deadpush && deadpush protect --daemon` silently triggered a `sudo`
16
+ password prompt — and on a normal (non-editable) pip install it then failed
17
+ outright. Soft (same-UID) mode is now the default; hardened mode is opt-in via
18
+ `--hardened`. This matches the README and `SECURITY.md`.
19
+ - **Hardened mode works from a pip/wheel install**: `_ensure_hardened_venv`
20
+ assumed a source checkout and looked for a `pyproject.toml` above the package,
21
+ raising `deadpush source not found at .../site-packages` for every
22
+ pip-installed user who ran `--hardened`. It now installs the matching version
23
+ from PyPI (`deadpush==<version>`) when there is no local source tree, and still
24
+ installs the working tree for dev checkouts.
25
+
10
26
  ## [0.2.2] - 2026-07-04
11
27
 
12
28
  First public release on PyPI (`0.2.1` was burned by PyPI's deleted-filename
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deadpush
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Guardrails for the vibe coding era — real-time AI agent guardian with MCP write interception, git hooks, and quarantine
5
5
  Project-URL: Homepage, https://github.com/harris-ahmad/deadpush
6
6
  Project-URL: Source, https://github.com/harris-ahmad/deadpush
@@ -0,0 +1 @@
1
+ __version__ = "0.2.3"
@@ -49,8 +49,8 @@ def main():
49
49
  @click.option("--no-intervention", is_flag=True, help="Warning mode only (no blocking/quarantine)")
50
50
  @click.option("--daemon", is_flag=True, help="Run as background daemon")
51
51
  @click.option("--strict", is_flag=True, help="Enable strict intervention mode")
52
- @click.option("--soft", is_flag=True, help="Dev-only: run as your UID (agent can kill guardian)")
53
- @click.option("--hardened", is_flag=True, help="Run as _deadpush user (default unless --soft)")
52
+ @click.option("--soft", is_flag=True, help="Run as your own UID (this is the default)")
53
+ @click.option("--hardened", is_flag=True, help="Opt into hardened mode: run under a root-owned _deadpush user (requires sudo)")
54
54
  def cmd_guard(repo, no_intervention, daemon, strict, soft, hardened):
55
55
  """
56
56
  Start the AI Agent Guardian.
@@ -60,12 +60,11 @@ def cmd_guard(repo, no_intervention, daemon, strict, soft, hardened):
60
60
  from .guard import run_guardian
61
61
  import os
62
62
  intervention = not no_intervention
63
- use_hardened = not soft
64
- if hardened:
65
- use_hardened = True
66
63
  if soft and hardened:
67
64
  print_error("Cannot use --soft with --hardened")
68
65
  return
66
+ # Soft (same-UID) is the default; hardened (privilege separation, sudo) is opt-in.
67
+ use_hardened = bool(hardened)
69
68
  if repo:
70
69
  os.chdir(Path(repo).resolve())
71
70
  run_guardian(intervention=intervention, daemon=daemon, strict=strict, hardened=use_hardened)
@@ -74,8 +73,8 @@ def cmd_guard(repo, no_intervention, daemon, strict, soft, hardened):
74
73
  @main.command("protect")
75
74
  @click.option("--enable", is_flag=True, help="Enable persistent background guardian (auto-starts daemon after setup)")
76
75
  @click.option("--daemon", is_flag=True, help="Start the guardian as a persistent background daemon after performing full setup")
77
- @click.option("--soft", is_flag=True, help="Dev-only: same-UID guardian (agent can pkill it; not production-safe)")
78
- @click.option("--hardened", is_flag=True, help="Explicitly request hardened mode (default unless --soft)")
76
+ @click.option("--soft", is_flag=True, help="Same-UID guardian at your own privileges (this is the default)")
77
+ @click.option("--hardened", is_flag=True, help="Opt into hardened mode: privilege separation via a root-owned _deadpush user (requires sudo)")
79
78
  @click.option(
80
79
  "--allow-self-protect",
81
80
  is_flag=True,
@@ -109,12 +108,13 @@ def cmd_protect(enable, daemon, soft, hardened, allow_self_protect):
109
108
  return
110
109
 
111
110
  start_background = bool(enable or daemon)
112
- use_hardened = not soft
113
- if hardened:
114
- use_hardened = True
115
111
  if soft and hardened:
116
112
  print_error("Cannot use --soft with --hardened")
117
113
  raise SystemExit(2)
114
+ # Soft (same-UID) is the default so the documented `pip install deadpush &&
115
+ # deadpush protect --daemon` one-liner never silently requires sudo. Hardened
116
+ # mode (privilege separation via a root-owned _deadpush user) is opt-in.
117
+ use_hardened = bool(hardened)
118
118
 
119
119
  # Track failures that make protection incomplete. A production install must
120
120
  # exit non-zero (and tell the user) rather than print a warning and pretend
@@ -135,7 +135,8 @@ def cmd_protect(enable, daemon, soft, hardened, allow_self_protect):
135
135
  print_error("or use `deadpush protect --soft` for a same-UID (dev-only) guardian.")
136
136
  raise SystemExit(1)
137
137
  elif start_background:
138
- print_warning("Running in --soft mode: guardian uses your UID and can be killed by agents.")
138
+ print_warning("Running in soft mode (default): the guardian runs at your UID, so an "
139
+ "agent could kill it. Use `--hardened` for a root-backed boundary (requires sudo).")
139
140
 
140
141
  print_header("deadpush Protect", "One-command setup for AI Agent Guardian (persistent background protection)")
141
142
 
@@ -2647,15 +2647,26 @@ def _ensure_hardened_venv(_sudo, lines: list[str]) -> None:
2647
2647
 
2648
2648
  _sudo(["chown", "-R", "_deadpush:_deadpush", str(venv)])
2649
2649
 
2650
+ # How we populate the hardened venv depends on how deadpush itself was installed:
2651
+ # - dev/source checkout (a pyproject.toml sits above the package): install the
2652
+ # working tree so hardened mode tracks local changes.
2653
+ # - normal pip/wheel install (site-packages has no pyproject.toml): install the
2654
+ # matching version from PyPI. Previously this path raised "source not found",
2655
+ # so `deadpush protect --hardened` was broken for every pip-installed user.
2650
2656
  source = _deadpush_source_root()
2651
- if not (source / "pyproject.toml").exists():
2652
- raise RuntimeError(f"deadpush source not found at {source}")
2657
+ if (source / "pyproject.toml").exists():
2658
+ target = str(source)
2659
+ origin = f"source {source}"
2660
+ else:
2661
+ from . import __version__ as _dp_version
2662
+ target = f"deadpush=={_dp_version}"
2663
+ origin = f"PyPI ({target})"
2653
2664
 
2654
2665
  pip = str(venv / "bin" / "pip")
2655
2666
  _sudo([pip, "install", "--upgrade", "pip"], check=False)
2656
- _sudo([pip, "install", str(source)])
2667
+ _sudo([pip, "install", target])
2657
2668
  _sudo(["chown", "-R", "_deadpush:_deadpush", str(venv)])
2658
- lines.append(f"Installed deadpush into hardened venv from {source}")
2669
+ lines.append(f"Installed deadpush into hardened venv from {origin}")
2659
2670
 
2660
2671
 
2661
2672
  def _setup_hardened_policy(repo_root: Path, _sudo, lines: list[str]) -> None:
@@ -1 +0,0 @@
1
- __version__ = "0.2.2"
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
File without changes
File without changes
File without changes