deadpush 0.2.1__tar.gz → 0.2.2__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 (106) hide show
  1. deadpush-0.2.2/.cursorignore +23 -0
  2. deadpush-0.2.2/.github/workflows/ci.yml +66 -0
  3. {deadpush-0.2.1 → deadpush-0.2.2}/.gitignore +3 -4
  4. deadpush-0.2.2/.vscode/mcp.json +10 -0
  5. {deadpush-0.2.1 → deadpush-0.2.2}/AGENT.md +15 -27
  6. deadpush-0.2.2/CHANGELOG.md +277 -0
  7. deadpush-0.2.2/CONTRIBUTING.md +59 -0
  8. deadpush-0.2.2/Formula/deadpush.rb +58 -0
  9. {deadpush-0.2.1 → deadpush-0.2.2}/PKG-INFO +51 -65
  10. deadpush-0.2.2/README.md +182 -0
  11. deadpush-0.2.2/SECURITY.md +186 -0
  12. deadpush-0.2.2/action.yml +26 -0
  13. deadpush-0.2.2/deadpush/__init__.py +1 -0
  14. deadpush-0.2.2/deadpush/cli.py +1565 -0
  15. deadpush-0.2.2/deadpush/config.py +377 -0
  16. deadpush-0.2.2/deadpush/debris.py +165 -0
  17. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/deps_guard.py +1 -1
  18. deadpush-0.2.2/deadpush/guard.py +2785 -0
  19. deadpush-0.2.2/deadpush/hooks.py +901 -0
  20. deadpush-0.2.2/deadpush/intercept.py +729 -0
  21. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/layers.py +1 -2
  22. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/mcp_server.py +131 -214
  23. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/rules.py +6 -2
  24. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/session.py +1 -10
  25. deadpush-0.2.2/deadpush/types.py +47 -0
  26. deadpush-0.2.2/deadpush/ui.py +37 -0
  27. {deadpush-0.2.1 → deadpush-0.2.2}/deadpush/verifier.py +1 -2
  28. deadpush-0.2.2/deadpush_bootstrap.py +66 -0
  29. {deadpush-0.2.1 → deadpush-0.2.2}/pyproject.toml +29 -13
  30. deadpush-0.2.2/scripts/brew_release.sh +59 -0
  31. deadpush-0.2.2/scripts/deadpush +60 -0
  32. deadpush-0.2.2/scripts/dev_install.sh +54 -0
  33. deadpush-0.2.2/scripts/full_e2e_test.py +359 -0
  34. deadpush-0.2.2/scripts/hardened_qa.sh +378 -0
  35. deadpush-0.2.2/scripts/soak_test.py +180 -0
  36. deadpush-0.2.2/scripts/stress_test.py +123 -0
  37. deadpush-0.2.2/tests/test_attack_chain.py +106 -0
  38. deadpush-0.2.2/tests/test_bootstrap.py +44 -0
  39. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_deps_guard.py +0 -7
  40. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_exhaustive.py +55 -217
  41. deadpush-0.2.2/tests/test_failclosed.py +130 -0
  42. deadpush-0.2.2/tests/test_file_coverage.py +72 -0
  43. deadpush-0.2.2/tests/test_guardian_closed_loop.py +118 -0
  44. deadpush-0.2.2/tests/test_hardened.py +406 -0
  45. deadpush-0.2.2/tests/test_hooks_enforcement.py +71 -0
  46. deadpush-0.2.2/tests/test_hookspath_hijack.py +87 -0
  47. deadpush-0.2.2/tests/test_integration_lifecycle.py +172 -0
  48. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_intercept_guardrails.py +32 -38
  49. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_mcp.py +9 -23
  50. deadpush-0.2.2/tests/test_move_delete_events.py +116 -0
  51. deadpush-0.2.2/tests/test_os_hardening.py +45 -0
  52. deadpush-0.2.2/tests/test_prepush_newbranch.py +120 -0
  53. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_real_repo_e2e.py +31 -33
  54. deadpush-0.2.2/tests/test_root_immutable.py +186 -0
  55. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_rules.py +1 -2
  56. deadpush-0.2.2/tests/test_tier0_hardening.py +229 -0
  57. deadpush-0.2.2/uv.lock +559 -0
  58. deadpush-0.2.1/.github/workflows/ci.yml +0 -36
  59. deadpush-0.2.1/CHANGELOG.md +0 -19
  60. deadpush-0.2.1/CONTRIBUTING.md +0 -38
  61. deadpush-0.2.1/README.md +0 -188
  62. deadpush-0.2.1/action.yml +0 -34
  63. deadpush-0.2.1/deadpush/__init__.py +0 -1
  64. deadpush-0.2.1/deadpush/churn.py +0 -189
  65. deadpush-0.2.1/deadpush/cli.py +0 -1584
  66. deadpush-0.2.1/deadpush/comments.py +0 -265
  67. deadpush-0.2.1/deadpush/complexity.py +0 -254
  68. deadpush-0.2.1/deadpush/config.py +0 -284
  69. deadpush-0.2.1/deadpush/crawler.py +0 -133
  70. deadpush-0.2.1/deadpush/deadness.py +0 -477
  71. deadpush-0.2.1/deadpush/debris.py +0 -729
  72. deadpush-0.2.1/deadpush/deps.py +0 -323
  73. deadpush-0.2.1/deadpush/entrypoints.py +0 -193
  74. deadpush-0.2.1/deadpush/graph.py +0 -401
  75. deadpush-0.2.1/deadpush/guard.py +0 -1386
  76. deadpush-0.2.1/deadpush/hooks.py +0 -369
  77. deadpush-0.2.1/deadpush/importgraph.py +0 -122
  78. deadpush-0.2.1/deadpush/imports.py +0 -239
  79. deadpush-0.2.1/deadpush/intercept.py +0 -995
  80. deadpush-0.2.1/deadpush/languages/__init__.py +0 -143
  81. deadpush-0.2.1/deadpush/languages/base.py +0 -70
  82. deadpush-0.2.1/deadpush/languages/cpp.py +0 -150
  83. deadpush-0.2.1/deadpush/languages/go_.py +0 -177
  84. deadpush-0.2.1/deadpush/languages/java.py +0 -185
  85. deadpush-0.2.1/deadpush/languages/javascript.py +0 -202
  86. deadpush-0.2.1/deadpush/languages/python_.py +0 -278
  87. deadpush-0.2.1/deadpush/languages/rust.py +0 -147
  88. deadpush-0.2.1/deadpush/languages/typescript.py +0 -192
  89. deadpush-0.2.1/deadpush/reachability.py +0 -183
  90. deadpush-0.2.1/deadpush/registration.py +0 -280
  91. deadpush-0.2.1/deadpush/report.py +0 -113
  92. deadpush-0.2.1/deadpush/sarif.py +0 -123
  93. deadpush-0.2.1/deadpush/scorer.py +0 -151
  94. deadpush-0.2.1/deadpush/security.py +0 -187
  95. deadpush-0.2.1/deadpush/tests.py +0 -333
  96. deadpush-0.2.1/deadpush/ui.py +0 -156
  97. deadpush-0.2.1/deadpush/watch.py +0 -103
  98. deadpush-0.2.1/scripts/full_e2e_test.py +0 -510
  99. deadpush-0.2.1/tests/test_comments.py +0 -169
  100. deadpush-0.2.1/tests/test_tests_analyzer.py +0 -114
  101. {deadpush-0.2.1 → deadpush-0.2.2}/.github/workflows/python-publish.yml +0 -0
  102. {deadpush-0.2.1 → deadpush-0.2.2}/CODE_OF_CONDUCT.md +0 -0
  103. {deadpush-0.2.1 → deadpush-0.2.2}/LICENSE +0 -0
  104. {deadpush-0.2.1 → deadpush-0.2.2}/tests/__init__.py +0 -0
  105. {deadpush-0.2.1 → deadpush-0.2.2}/tests/conftest.py +0 -0
  106. {deadpush-0.2.1 → deadpush-0.2.2}/tests/test_layers.py +0 -0
@@ -0,0 +1,23 @@
1
+
2
+ # Added by deadpush
3
+ **/playground.*
4
+ **/scratch*.md
5
+ **/temp*.py
6
+ **/tmp*.go
7
+ .claude_instructions
8
+ .copilot-instructions.md
9
+ .cursorrules
10
+ .deadpush-archive/
11
+ .deadpush-autoignore
12
+ .deadpush-quarantine/
13
+ .venv/
14
+ __pycache__/
15
+ agents.md
16
+ ai_prompt.md
17
+ claude.md
18
+ dist/
19
+ llm_context.txt
20
+ node_modules/
21
+ target/
22
+ venv/
23
+ windsurf_rules.md
@@ -0,0 +1,66 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ env:
10
+ PYTHONUTF8: "1"
11
+
12
+ jobs:
13
+ lint:
14
+ runs-on: ubuntu-latest
15
+ steps:
16
+ - uses: actions/checkout@v4
17
+
18
+ - name: Install uv
19
+ uses: astral-sh/setup-uv@v5
20
+ with:
21
+ python-version: "3.12"
22
+ enable-cache: true
23
+
24
+ - name: Verify lockfile is in sync
25
+ run: uv lock --check
26
+
27
+ - name: Install dependencies
28
+ run: uv sync --extra dev --frozen
29
+
30
+ - name: Lint with ruff (blocking)
31
+ run: uv run ruff check deadpush/ tests/ scripts/
32
+
33
+ test:
34
+ # Unit + integration suite. Runs on Linux across supported Python versions
35
+ # and on macOS (the primary hardened-mode target). The integration test in
36
+ # tests/test_integration_lifecycle.py drives the installed CLI and executes
37
+ # the real generated git hooks — the closest deterministic proxy to a user's
38
+ # first run, without the flakiness of a background daemon in CI.
39
+ needs: [lint]
40
+ strategy:
41
+ fail-fast: false
42
+ matrix:
43
+ include:
44
+ - os: ubuntu-latest
45
+ python-version: "3.11"
46
+ - os: ubuntu-latest
47
+ python-version: "3.12"
48
+ - os: ubuntu-latest
49
+ python-version: "3.13"
50
+ - os: macos-latest
51
+ python-version: "3.12"
52
+ runs-on: ${{ matrix.os }}
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Install uv
57
+ uses: astral-sh/setup-uv@v5
58
+ with:
59
+ python-version: ${{ matrix.python-version }}
60
+ enable-cache: true
61
+
62
+ - name: Install dependencies
63
+ run: uv sync --extra dev --frozen
64
+
65
+ - name: Run tests
66
+ run: uv run pytest -v
@@ -95,10 +95,9 @@ ipython_config.py
95
95
  # Pipfile.lock
96
96
 
97
97
  # UV
98
- # Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control.
99
- # This is especially recommended for binary packages to ensure reproducibility, and is more
100
- # commonly ignored for libraries.
101
- uv.lock
98
+ # deadpush is a distributed application/security tool, so uv.lock IS committed
99
+ # for reproducible builds. CI enforces it (uv lock --check, uv sync --frozen).
100
+ # uv.lock
102
101
 
103
102
  # poetry
104
103
  # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
@@ -0,0 +1,10 @@
1
+ {
2
+ "servers": {
3
+ "deadpush": {
4
+ "command": "/Users/harris/Documents/personal/deadpush/.venv/bin/deadpush",
5
+ "args": [
6
+ "mcp"
7
+ ]
8
+ }
9
+ }
10
+ }
@@ -6,9 +6,9 @@ This project uses **deadpush**: an agent-native guardrail system that intercepts
6
6
 
7
7
  - Write files normally via MCP (`write_file` tool). Guardrails run automatically.
8
8
  - If blocked → read the feedback file → fix the issue → retry.
9
- - If you hit a false positive → `add_allowed_pattern` to whitelist the code.
9
+ - If you hit a false positive → `add_allowed_pattern` to whitelist the code (requires `deadpush mcp --danger`).
10
10
 
11
- ## MCP Tools (24 available)
11
+ ## MCP Tools
12
12
 
13
13
  Connect via `deadpush mcp` (stdio JSON-RPC on `2024-11-05` protocol).
14
14
 
@@ -17,34 +17,27 @@ Connect via `deadpush mcp` (stdio JSON-RPC on `2024-11-05` protocol).
17
17
  |---|---|
18
18
  | `write_file(path, content)` | Write through guardrails. Blocked files go to quarantine + feedback. |
19
19
  | `check_file(path, content)` | Preview: would the file pass? Returns violations without writing. |
20
+ | `verify_write(path, content)` | Write + run related tests. File only persists if tests pass. |
21
+ | `get_write_diff(path, content)` | Preview diff + guardrail violations before writing. |
22
+ | `retry_write(path, content)` | Submit corrected content for a previously blocked file. |
20
23
 
21
- ### Scan
24
+ ### Quarantine
22
25
  | Tool | What it does |
23
26
  |---|---|
24
- | `scan` | Full analysis summary (dead symbols, debris, test issues, etc.) |
25
- | `get_dead_symbols` | Unreachable code |
26
- | `get_debris` | AI artifacts, stale files |
27
- | `get_test_issues` | No-assertion / tautology / empty tests |
28
- | `get_stale_docs` | Docstring-param mismatches |
29
- | `get_layer_violations` | Architectural import violations |
30
- | `get_security_boundaries` | Untested security-sensitive ops |
31
- | `get_complexity_alerts` | Complexity spikes |
32
-
33
- ### Clean / Quarantine
34
- | Tool | What it does |
35
- |---|---|
36
- | `clean(mode)` | Remove dead code / debris (safe, dry_run, force) |
37
27
  | `quarantine_list(limit)` | List quarantined files |
38
- | `quarantine_restore(name)` | Restore from quarantine |
28
+ | `quarantine_restore(name)` | Restore from quarantine (danger mode) |
39
29
 
40
30
  ### Feedback
41
31
  | Tool | What it does |
42
32
  |---|---|
43
33
  | `get_feedback(limit)` | Read recent guardrail feedback |
34
+ | `get_recent_feedback(limit)` | Unacknowledged feedback only |
35
+ | `acknowledge_feedback(name)` | Mark feedback as read/addressed |
44
36
  | `get_status` | Current paths + available tools |
45
37
  | `get_safety_score` | Latest guardian score |
38
+ | `get_test_results(limit)` | Recent verify_write test output |
46
39
 
47
- ### Config (agent self-service)
40
+ ### Config (agent self-service — danger mode for softening actions)
48
41
  | Tool | What it does |
49
42
  |---|---|
50
43
  | `get_runtime_config` | View all current rules |
@@ -53,15 +46,12 @@ Connect via `deadpush mcp` (stdio JSON-RPC on `2024-11-05` protocol).
53
46
  | `ignore_path(path)` | Skip a file entirely |
54
47
  | `set_guardrail_level(category, level)` | Set severity: `off`, `warn`, or `block` |
55
48
  | `reset_runtime_config` | Clear all overrides to defaults |
49
+ | `allow_sensitive_write(path)` | Opt in to writing a sensitive config file |
50
+ | `learn_false_positive(category, pattern, reason)` | Teach deadpush to suppress a false positive |
51
+ | `adjudicate_finding(...)` | Structured adjudication rubric for a finding |
56
52
 
57
53
  All tools return `{"success": bool, "data": ..., "summary": "..."}`.
58
54
 
59
- ### Diff / Preview
60
- | Tool | What it does |
61
- |---|---|
62
- | `get_write_diff(path, content)` | Preview diff + guardrail violations before writing |
63
- | `allow_sensitive_write(path)` | Opt in to writing a sensitive config file (CI/CD, Docker, etc.) |
64
-
65
55
  ## Guardrail Categories
66
56
 
67
57
  | Category | Default level | What it catches |
@@ -72,7 +62,7 @@ All tools return `{"success": bool, "data": ..., "summary": "..."}`.
72
62
  | `layer` | `block` | Imports violating architecture layer rules |
73
63
  | `sensitive` | `block` | Writes to CI/CD, deployment, Docker, and other sensitive config files |
74
64
  | `destructive` | `warn` | Near-empty rewrites of substantial files, >50% line reduction |
75
- | `debris` | `warn` | TODO stubs, FIXME markers, bare `pass` statements |
65
+ | `debris` | `warn` | LLM context files, scratchpads, hardcoded secrets in debris scan |
76
66
  | `dependency` | `warn` | Typosquat packages, suspicious package names in dep files |
77
67
 
78
68
  ## Self-Correction Flow
@@ -95,8 +85,6 @@ Then retry — it will pass.
95
85
  Persisted in `.deadpush/rules.json`. Survives server restarts.
96
86
  Modify via MCP tools above. Do NOT edit the file directly.
97
87
 
98
- Use `set_guardrail_level("prompt_injection", "warn")` to downgrade a category to warning-only (doesn't block, just reports).
99
-
100
88
  ## What NOT To Do
101
89
 
102
90
  - Do NOT bypass guardrails by writing directly to the filesystem.
@@ -0,0 +1,277 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [Unreleased]
9
+
10
+ ## [0.2.2] - 2026-07-04
11
+
12
+ First public release on PyPI (`0.2.1` was burned by PyPI's deleted-filename
13
+ reuse rule and skipped). The project stays on a 0.2.x beta line until the
14
+ production-readiness gates (blocking lint, lockfiled reproducible builds, and
15
+ green macOS + Linux CI including the lifecycle integration test) are met.
16
+
17
+ ### Security (hardened mode — Tier 0)
18
+
19
+ A threat-model audit (adversarial agent running as the same UID) found five ways
20
+ a same-UID agent could defeat *hardened* mode even though the daemon and hook
21
+ files were protected. These close them so hardened mode is a real boundary, not
22
+ just defense-in-depth. Covered by `tests/test_tier0_hardening.py` and validated
23
+ end-to-end by `scripts/hardened_qa.sh`.
24
+
25
+ - **Hardened hooks now execute the root-owned interpreter (H2)**: git hooks used
26
+ the *user's* interpreter and user-writable `deadpush` package, so a same-UID
27
+ agent could no-op the guardrails by editing the package — defeating even a
28
+ root-immutable hook file. Hardened hooks now run `/var/db/deadpush/venv/bin/
29
+ python` (owned by `_deadpush`, not user-writable). The guardian's auto-repair
30
+ re-pins this interpreter and re-locks `schg` instead of downgrading.
31
+ - **Guardrail policy is now read from a root-owned dir (H1)**: enforcement read
32
+ `rules.json` / `learned_patterns.json` from the in-repo `.deadpush/` directory,
33
+ which the agent can write (it could set every category to `off` or allowlist
34
+ `.*`). Hardened installs now read/write policy from a root/`_deadpush`-owned
35
+ tree (`/var/db/deadpush/policy/<repo-id>/`) that the user can traverse and read
36
+ but not modify. Soft mode is unchanged (in-repo, deterrence only).
37
+ - **Control server enforces a bearer token on mutations (H3)**: the localhost
38
+ control server shipped with auth wired up but disabled (no token), so any
39
+ local process — including the agent — could reset the allowlist or restore
40
+ quarantined files. A random token is now generated per guardian and stored
41
+ `0600` (root-owned in hardened mode, so the agent can't read it); state-changing
42
+ `POST`s require it (with a constant-time compare), while read-only `GET`s stay
43
+ open so the human dashboard still works.
44
+ - **Quarantine restore is confined to the repo tree (H4)**: `restore()` moved a
45
+ file to the absolute "Original path" read from an agent-writable `.reason`
46
+ file, letting a crafted `.reason` make the guardian (running as `_deadpush`)
47
+ write anywhere — a confused-deputy write. The destination is now resolved and
48
+ required to stay within the repo, else the restore is refused.
49
+ - **Fail-closed can no longer be cleared by the agent (H5)**: the hooks' fail-
50
+ closed decision keyed only off the agent-deletable in-repo `.deadpush/installed`
51
+ marker. Hardened setup now also writes a root-owned marker under
52
+ `/var/db/deadpush/policy/<repo-id>/`, and the hook prelude honors it, so
53
+ fail-closed holds even if the in-repo marker is deleted. (Combined with H2 the
54
+ root-owned interpreter is itself un-deletable by the agent.)
55
+ - **Hardened state dir is now `0711` (was `0700`)**: required so the user's git
56
+ hooks can *traverse* to the root-owned interpreter and policy. The directory is
57
+ not listable and secret files (the control token) remain `0600`.
58
+
59
+ ### Added
60
+ - **`SECURITY.md` threat model**: documents the adversary (a same-UID AI agent),
61
+ the soft-vs-hardened guarantees, the inherent limits of heuristic detection, and
62
+ private vulnerability reporting.
63
+ - **Real-time move/delete handling**: the watchdog guardian only handled
64
+ `on_created`/`on_modified`, so an agent could evade the real-time layer by
65
+ moving un-scanned content into place (stage a payload in a skipped dir like
66
+ `node_modules/` then `mv` it in, or rename a benign file onto a dangerous
67
+ path/name). `on_moved` now evaluates the destination exactly like a fresh
68
+ write (dangerous content is quarantined). `on_deleted` records forensic
69
+ telemetry (log + session incident) but is deliberately non-punitive — it does
70
+ not auto-restore or dock the safety score, since the score never recovers and
71
+ a legitimate multi-file `git rm`/refactor would otherwise false-trigger
72
+ lockdown; tracked files remain recoverable from git history. Covered by
73
+ `tests/test_move_delete_events.py`.
74
+ - **Fail-closed git hooks**: once a repo is protected (a `.deadpush/installed`
75
+ marker is written), the installed hooks refuse to run when the deadpush
76
+ interpreter goes missing, instead of silently allowing the push/commit.
77
+ `DEADPUSH_STRICT=1` forces fail-closed everywhere.
78
+ - **`protect` verifies and reports failure**: `deadpush protect` now polls that
79
+ the guardian actually started, and exits non-zero with actionable guidance if
80
+ hooks, the marker, or the daemon did not come up (no more false "success").
81
+ - **`doctor` protection checks**: reports the protection marker (and whether the
82
+ pinned interpreter still exists) plus git-hook integrity.
83
+ - **Lifecycle integration test** (`tests/test_integration_lifecycle.py`): drives
84
+ the installed CLI and executes the real generated hooks; runs on macOS + Linux
85
+ in CI.
86
+ - **Hardened-mode QA harness** (`scripts/hardened_qa.sh`): a manual, root-gated
87
+ end-to-end validator for the hardened guarantees CI cannot cover — privilege
88
+ separation, an agent-unkillable daemon (and launchd/systemd respawn),
89
+ root-immutable `schg`/`+i` hooks, repo + `$HOME`-traverse ACLs, real-time
90
+ quarantine, `core.hooksPath` self-heal, and a verified clean teardown. It runs
91
+ in a throwaway repo, escalates only via `sudo`, and refuses to run when a
92
+ `_deadpush` account already exists so it can't disturb a real install.
93
+ - **`core.hooksPath` hijack detection + self-heal**: `git config core.hooksPath
94
+ /dev/null` (or any dir other than `.git/hooks`) silently disabled every hook
95
+ without tripping the checksum/immutability checks. `verify_hooks_installed` now
96
+ flags it, `repair_deadpush_hooks` restores it (repo-local only, overriding a
97
+ global hijack if needed), the running guardian auto-repairs it every cycle, and
98
+ `doctor` reports it. Covered by `tests/test_hookspath_hijack.py`.
99
+ - **Root-immutable hooks in hardened mode**: hardened installs now lock the git
100
+ hooks ROOT-immutable (`sudo chflags schg` on macOS, `sudo chattr +i` on Linux)
101
+ instead of only user-immutable (`uchg`). A same-UID agent can no longer clear
102
+ the flag to delete or rewrite a hook — only root can. Soft mode keeps the
103
+ owner-clearable `uchg`. `_is_immutable` now recognises `schg` (so the guardian
104
+ never loops trying to "repair" a locked hook it cannot rewrite), `uninstall`
105
+ clears it via sudo (with an automatic escalation fallback), and if `schg` can't
106
+ be set it fails safe to `uchg` with a loud warning. Covered by
107
+ `tests/test_root_immutable.py`.
108
+
109
+ ### Fixed
110
+ - **Pre-push new-branch range can no longer be poisoned (D4)**: for a brand-new
111
+ branch (remote side all-zeros) the hook derived the scan boundary from
112
+ `git rev-list … --not --remotes`, trusting local `refs/remotes/*`. Those refs are
113
+ writable by a same-UID agent, so forging `refs/remotes/x/y` at the payload commit
114
+ made git treat the dangerous content as "already on the remote" and shrank the
115
+ scanned diff to exclude it — while `git push` still shipped those commits. New
116
+ refs now always scan the entire pushed tree (`empty-tree..local_sha`), which
117
+ cannot be poisoned by local refs; existing-branch updates keep using the
118
+ trustworthy `remote_sha` git supplies from the real push negotiation. Covered by
119
+ `tests/test_prepush_newbranch.py`
120
+ (`test_new_branch_push_cannot_be_poisoned_by_fake_remote_ref`).
121
+ - **Uninstall leaves the repo pristine**: cleanup only removed an empty
122
+ `.guardian`, leaving behind empty `.deadpush` (e.g. `.deadpush/feedback/`) and
123
+ `.deadpush-quarantine` directories. It now prunes deadpush's own now-empty
124
+ bookkeeping dirs — but strictly non-destructively: a directory is removed only
125
+ when it has no remaining contents, so real feedback data, config, and
126
+ quarantined files are always preserved. Covered by
127
+ `tests/test_integration_lifecycle.py`
128
+ (`test_uninstall_leaves_repo_pristine`, `test_uninstall_preserves_user_data`).
129
+ - **Cross-platform uninstall**: `deadpush uninstall` invoked `launchctl`
130
+ unconditionally, which raised `FileNotFoundError` on Linux and made the command
131
+ exit non-zero. It now unloads/removes the correct service per platform (launchd
132
+ plist on macOS, systemd unit on Linux — the systemd unit was previously never
133
+ removed) and treats a missing/inactive service manager as a no-op.
134
+ - **Hardened-mode teardown parity on Linux**: hardened setup already supported
135
+ Linux (`useradd`/`groupadd`/`setfacl`), but uninstall only ran macOS
136
+ `dscl`/`chmod -N`, so it crashed on Linux and orphaned the `_deadpush`
137
+ account/ACLs. Added `teardown_hardened_environment`, a platform-aware,
138
+ best-effort reversal that revokes every `_deadpush` ACL (repo tree,
139
+ `.guardian`, and parent traverse dirs — a shared walk with setup so the two
140
+ can't drift) and deletes the account (macOS `dscl`, Linux `userdel`/`groupdel`),
141
+ revoking ACLs before removing the account. Covered by
142
+ `tests/test_hardened.py::TestTeardownHardenedEnvironment`.
143
+
144
+ ### Changed
145
+ - **Wider git-hook file coverage**: the pre-commit/pre-push/post-commit hooks
146
+ gated content scanning on a small extension allowlist (~18 types), so an agent
147
+ could commit dangerous content in files the hooks never read — extensionless
148
+ and config files such as `Dockerfile`, `Makefile`, `.env`, `.npmrc`, shell
149
+ rc-files, and LLM-context files like `.cursorrules` / `.claude_instructions`
150
+ (the watchdog daemon already scanned these; the hooks did not). A shared
151
+ `is_enforceable_path()` now mirrors the daemon's coverage: a much wider set of
152
+ code/config/infra/text extensions plus sensitive-by-name and `.env*` /
153
+ `Dockerfile*` files, while binary formats (images, archives, compiled objects,
154
+ keystores) are always skipped. Covered by `tests/test_file_coverage.py`.
155
+ - **Robust hook/daemon entrypoint**: generated hooks, the launchd/systemd units,
156
+ and the shadow respawn command now launch via `deadpush_bootstrap` instead of
157
+ `-m deadpush.cli`, so they work in editable installs where macOS hides the
158
+ `.pth` file (previously the guardian and hooks failed to import deadpush from a
159
+ non-source working directory).
160
+ - **Single source of truth for version**: `pyproject.toml` derives the version
161
+ from `deadpush/__init__.py` (hatchling dynamic version); metadata and
162
+ `deadpush.__version__` can no longer drift.
163
+
164
+ ### CI / tooling
165
+ - Ruff is now a **blocking** gate; the codebase is lint-clean.
166
+ - `uv.lock` is committed and CI runs `uv lock --check` + `uv sync --frozen` for
167
+ reproducible builds.
168
+ - CI test matrix runs on **macOS and Linux** across supported Python versions.
169
+
170
+ ### Fixed
171
+ - **launchctl bootstrap bug**: `protect` no longer marks the guardian as
172
+ bootstrapped when `launchctl bootstrap` fails; it now falls back to a direct
173
+ daemon launch and verifies the result.
174
+ - `uninstall` now also removes the deadpush git hooks and the protection marker,
175
+ so a full uninstall truly leaves nothing behind.
176
+ - **Pre-push scanning of new branches**: pushing a brand-new branch (remote side
177
+ all-zeros) previously diffed the working tree against the tip and scanned
178
+ nothing, letting a first push bypass content enforcement. New commits are now
179
+ resolved against the boundary already on the remote (or the whole tree when
180
+ there is no shared history) and scanned. Covered by `tests/test_prepush_newbranch.py`.
181
+ - **Test isolation / macOS notification spam**: the autostart unit tests wrote
182
+ real plists into `~/Library/LaunchAgents` (path derived from `$HOME`, not the
183
+ temp repo), triggering a macOS "app can run in the background" notification per
184
+ run and littering hundreds of stale plists. They now use the isolated
185
+ `hardened_env` fixture.
186
+
187
+ ### Packaging
188
+ - **Homebrew formula rewrite** (`Formula/deadpush.rb`): switched from a fragile
189
+ `pip3 install --prefix` to `virtualenv_install_with_resources` with runtime
190
+ dependencies pinned to the `uv.lock` versions (`click`, `pathspec`, `watchdog`),
191
+ a Homebrew-audit-clean `desc`, and a `--version` test. Passes `brew style`.
192
+ A release helper (`scripts/brew_release.sh`) fills the source `sha256` and runs
193
+ audit/install/test. Note: `brew install` requires the repo to be **public** with
194
+ the release tag pushed; the `sha256` is a placeholder until then.
195
+
196
+ ---
197
+
198
+ ## [1.0.0] - unreleased
199
+
200
+ _Historical notes previously drafted under a 1.0.0 heading; not yet released._
201
+
202
+ ### Added
203
+ - **Log rotation**: RotatingFileHandler (10MB × 5 files) prevents unbounded log growth
204
+ - **Safety score JSON persistence**: Structured `safety_score.json` in state dir, auto-saved on every incident, read by MCP server
205
+ - **MCP Bearer token authentication**: Optional token auth for GuardianControlServer endpoints
206
+ - **Health endpoint**: `GET /health` on GuardianControlServer for launchd/systemd health checks
207
+ - **CLI: doctor**: Comprehensive health check (`deadpush doctor`)
208
+ - **CLI: uninstall**: Complete removal including hardened mode cleanup (`deadpush uninstall`)
209
+ - **CLI: init**: Guided first-time setup (`deadpush init --mode default|hardened --daemon`)
210
+ - **Hardened mode**: Full privilege separation via dedicated `_deadpush` user/group
211
+ - Dedicated `_deadpush` group (not wheel) — eliminates root escalation risk
212
+ - ACLs on repo (read) + `.guardian/` (write), hooks write access removed
213
+ - LaunchDaemon with `UserName=_deadpush`, state in `/var/db/deadpush/`
214
+ - `setup_hardened_environment()` one-time sudo setup
215
+ - **Lock/PID improvements**: Holder PID stored in lock file, start-time verification, `--force` cleanup
216
+ - **Daemon fork safety**: Handler and control server created post-fork, FD closure
217
+ - **Shadow process hardening**: Health checks via `ps`, exponential backoff, PID coordination
218
+ - **Lock manager**: Holder PID in lock file, `force_cleanup()`, `--force` CLI flag
219
+ - **Test scripts**: `scripts/soak_test.py` (multi-repo), `scripts/stress_test.py` (high-concurrency)
220
+ - **CI/CD**: GitHub Actions workflow for building wheels, publishing to PyPI, GitHub Releases
221
+ - **Homebrew formula**: Template formula for `brew install deadpush`
222
+
223
+ ### Changed
224
+ - **Per-repo hardened flag**: Replaced global `_hardened_enabled` with explicit `hardened` parameter on all functions
225
+ - **Safety score storage**: Migrated from log parsing to structured JSON (`safety_score.json`)
226
+ - **MCP safety score tool**: Now reads structured JSON instead of log parsing
227
+ - **Lock manager**: Added holder PID tracking, force cleanup, `get_holder_pid()`
228
+ - **Daemon fork safety**: `GuardianHandler` and `GuardianControlServer` created post-fork, all inherited FDs closed
229
+ - **Shadow process**: Health checks via `ps`, exponential backoff (3s→60s), max 10 failures, PID file coordination
230
+ - **PID reuse protection**: Start time verification via `ps -o etimes=` + command line check
231
+ - **TOCTOU lock fix**: Holder PID stored, `--force` cleanup flag
232
+
233
+ ### Security
234
+ - Removed `_deadpush` from wheel group — eliminated root escalation via sudoers
235
+ - Removed hook write ACL for `_deadpush` — guardian no longer modifies `.git/hooks/`
236
+ - Dedicated `_deadpush` group with GID 499
237
+
238
+ ### Fixed
239
+ - PID reuse false positive in `DaemonManager.is_running()`
240
+ - TOCTOU race in lock acquisition
241
+ - Fork crash from thread-before-fork in daemon mode
242
+ - Duplicate `_require_auth` method in control handler
243
+
244
+ ---
245
+
246
+ ## [0.2.1] - 2026-06-20
247
+
248
+ ### Added
249
+ - Multi-agent Safety Score with burst detection
250
+ - Session tracking (events, recent files, duration)
251
+ - Per-repo scoped state files (PID, lock, port, suspend, plist)
252
+ - Launchd/systemd autostart config generation
253
+ - Shadow process for guardian respawn
254
+ - MCP server with stdio transport
255
+ - Agent-as-Adjudicator feedback loop
256
+ - Debris detection (LLM context files, vibe scratchpads, secrets, duplicates)
257
+ - Layer violation detection (architecture import rules)
258
+ - Smart ignore file auto-merge
259
+
260
+ ### Changed
261
+ - Safety score now tracks multi-agent bursts
262
+ - Path-aware guardrail lowering for test/mock files
263
+ - Learned false positive patterns persisted to `.deadpush/learned_patterns.json`
264
+
265
+ ---
266
+
267
+ ## [0.1.0] - 2026-06-10
268
+
269
+ ### Added
270
+ - Initial release
271
+ - Real-time filesystem guardian with watchdog
272
+ - 7-category guardrail pipeline (security, secrets, prompt injection, debris, layers, destructive, sensitive config)
273
+ - Call graph dead code analysis with 8-factor scoring
274
+ - Git pre-push hook installation
275
+ - Quarantine system (safe restore via git)
276
+ - Static call graph resolution
277
+ - Basic MCP server with stdio transport
@@ -0,0 +1,59 @@
1
+ # Contributing
2
+
3
+ Thanks for your interest in deadpush.
4
+
5
+ ## Getting Started
6
+
7
+ ```bash
8
+ git clone https://github.com/harris-ahmad/deadpush
9
+ cd deadpush
10
+ ./scripts/dev_install.sh
11
+ source .venv/bin/activate
12
+ ```
13
+
14
+ Or manually:
15
+
16
+ ```bash
17
+ python -m venv .venv
18
+ source .venv/bin/activate
19
+ pip install -e ".[dev,rich]"
20
+ ```
21
+
22
+ ### macOS: editable install silently broken?
23
+
24
+ On macOS, Python 3.12+ **skips `.pth` files marked hidden** (`UF_HIDDEN`). Files under `~/Documents` (and other iCloud-synced folders) often get this flag, so `pip install -e .` succeeds but `deadpush` only works when your cwd is the repo — from any other directory you get `ModuleNotFoundError`.
25
+
26
+ Fix:
27
+
28
+ ```bash
29
+ ./scripts/dev_install.sh
30
+ # or, if already installed:
31
+ chflags -R nohidden .venv
32
+ ```
33
+
34
+ Do **not** run plain `pip install deadpush` in the dev venv; use `pip install -e .` so the editable hook points at your source tree.
35
+
36
+ ## Running Tests
37
+
38
+ ```bash
39
+ pytest
40
+ ```
41
+
42
+ ## Code Style
43
+
44
+ We use ruff. Run before committing:
45
+
46
+ ```bash
47
+ ruff check .
48
+ ```
49
+
50
+ ## Pull Requests
51
+
52
+ - Keep changes focused. One PR = one concern.
53
+ - Add tests for new functionality.
54
+ - Run the full test suite before submitting.
55
+ - Update CHANGELOG.md if the change is user-facing.
56
+
57
+ ## Questions?
58
+
59
+ Open an issue at https://github.com/harris-ahmad/deadpush/issues
@@ -0,0 +1,58 @@
1
+ class Deadpush < Formula
2
+ include Language::Python::Virtualenv
3
+
4
+ desc "Real-time AI agent guardian with git hooks and MCP write interception"
5
+ homepage "https://github.com/harris-ahmad/deadpush"
6
+ url "https://github.com/harris-ahmad/deadpush/archive/refs/tags/v0.2.1.tar.gz"
7
+ # RELEASE STEP: placeholder sha256 (all-zeros). It can only be filled once the
8
+ # repo is PUBLIC and the tag is pushed. `brew install` will fail loudly with a
9
+ # checksum mismatch until then. Run scripts/brew_release.sh to fetch the tarball,
10
+ # compute the real sha256, write it here, and verify with brew audit/install/test.
11
+ sha256 "0000000000000000000000000000000000000000000000000000000000000000"
12
+ license "MIT"
13
+
14
+ depends_on "python@3.14"
15
+
16
+ # Runtime dependencies, pinned to the exact versions in uv.lock (the versions
17
+ # the test suite runs against). Homebrew installs these with --no-deps, so every
18
+ # runtime dependency must be listed explicitly. The PEP 517 build backend
19
+ # (hatchling) is resolved automatically via pip build isolation and is not vendored.
20
+ resource "click" do
21
+ url "https://files.pythonhosted.org/packages/9b/98/518d8e5081007684232226f475082b30087d0f585e8457db087298259f49/click-8.4.1.tar.gz"
22
+ sha256 "918b5633eddf6b41c32d4f454bf0de810065c74e3f7dbf8ee5452f8be88d3e96"
23
+ end
24
+
25
+ resource "pathspec" do
26
+ url "https://files.pythonhosted.org/packages/5a/82/42f767fc1c1143d6fd36efb827202a2d997a375e160a71eb2888a925aac1/pathspec-1.1.1.tar.gz"
27
+ sha256 "17db5ecd524104a120e173814c90367a96a98d07c45b2e10c2f3919fff91bf5a"
28
+ end
29
+
30
+ resource "watchdog" do
31
+ url "https://files.pythonhosted.org/packages/db/7d/7f3d619e951c88ed75c6037b246ddcf2d322812ee8ea189be89511721d54/watchdog-6.0.0.tar.gz"
32
+ sha256 "9ddf7c82fda3ae8e24decda1338ede66e1c99883db93711d8fb941eaa2d8c282"
33
+ end
34
+
35
+ def install
36
+ virtualenv_install_with_resources
37
+ end
38
+
39
+ def caveats
40
+ <<~EOS
41
+ Get started by protecting a repository:
42
+ cd your-repo && deadpush init
43
+
44
+ For hardened mode (privilege separation via a dedicated _deadpush user):
45
+ sudo deadpush init --mode hardened
46
+
47
+ Run the guardian as a background daemon:
48
+ deadpush protect --daemon
49
+
50
+ Check status:
51
+ deadpush status
52
+ EOS
53
+ end
54
+
55
+ test do
56
+ assert_match version.to_s, shell_output("#{bin}/deadpush --version")
57
+ end
58
+ end