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.
- {deadpush-0.2.2 → deadpush-0.2.3}/CHANGELOG.md +16 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/PKG-INFO +1 -1
- deadpush-0.2.3/deadpush/__init__.py +1 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/cli.py +12 -11
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/guard.py +15 -4
- deadpush-0.2.2/deadpush/__init__.py +0 -1
- {deadpush-0.2.2 → deadpush-0.2.3}/.cursorignore +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/.github/workflows/ci.yml +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/.github/workflows/python-publish.yml +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/.gitignore +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/.vscode/mcp.json +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/AGENT.md +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/CODE_OF_CONDUCT.md +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/CONTRIBUTING.md +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/Formula/deadpush.rb +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/LICENSE +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/README.md +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/SECURITY.md +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/action.yml +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/config.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/debris.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/deps_guard.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/hooks.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/intercept.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/layers.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/mcp_server.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/rules.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/session.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/types.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/ui.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush/verifier.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/deadpush_bootstrap.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/pyproject.toml +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/brew_release.sh +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/deadpush +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/dev_install.sh +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/full_e2e_test.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/hardened_qa.sh +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/soak_test.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/scripts/stress_test.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/__init__.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/conftest.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_attack_chain.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_bootstrap.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_deps_guard.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_exhaustive.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_failclosed.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_file_coverage.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_guardian_closed_loop.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hardened.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hooks_enforcement.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_hookspath_hijack.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_integration_lifecycle.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_intercept_guardrails.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_layers.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_mcp.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_move_delete_events.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_os_hardening.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_prepush_newbranch.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_real_repo_e2e.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_root_immutable.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_rules.py +0 -0
- {deadpush-0.2.2 → deadpush-0.2.3}/tests/test_tier0_hardening.py +0 -0
- {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.
|
|
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="
|
|
53
|
-
@click.option("--hardened", is_flag=True, help="
|
|
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="
|
|
78
|
-
@click.option("--hardened", is_flag=True, help="
|
|
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
|
|
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
|
|
2652
|
-
|
|
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",
|
|
2667
|
+
_sudo([pip, "install", target])
|
|
2657
2668
|
_sudo(["chown", "-R", "_deadpush:_deadpush", str(venv)])
|
|
2658
|
-
lines.append(f"Installed deadpush into hardened venv from {
|
|
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
|
|
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
|