leftblock 0.3.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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anthus AI Solutions
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,390 @@
1
+ Metadata-Version: 2.4
2
+ Name: leftblock
3
+ Version: 0.3.2
4
+ Summary: LeftBlock: a pluggable inspection gate for Git commits
5
+ License: MIT License
6
+
7
+ Copyright (c) 2026 Anthus AI Solutions
8
+
9
+ Permission is hereby granted, free of charge, to any person obtaining a copy
10
+ of this software and associated documentation files (the "Software"), to deal
11
+ in the Software without restriction, including without limitation the rights
12
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13
+ copies of the Software, and to permit persons to whom the Software is
14
+ furnished to do so, subject to the following conditions:
15
+
16
+ The above copyright notice and this permission notice shall be included in all
17
+ copies or substantial portions of the Software.
18
+
19
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25
+ SOFTWARE.
26
+
27
+ Requires-Python: >=3.8
28
+ Description-Content-Type: text/markdown
29
+ License-File: LICENSE
30
+ Requires-Dist: PyYAML
31
+ Requires-Dist: behave
32
+ Dynamic: license-file
33
+
34
+ # LeftBlock
35
+
36
+ **LeftBlock** is a pluggable inspection gate for Git commits. It runs configured scanners, including Gitleaks, on staged changes. Local check-only mode blocks findings before a commit; optional receipt mode also records the inspection result in an HMAC receipt.
37
+
38
+ LeftBlock **shifts privacy checks left**: detected sensitive material can be blocked before it enters a commit and becomes part of local Git history. Pushing distributes that history to a remote; subsequent clones and pulls distribute it further. A later check can stop the next step, but cannot undo copies already made.
39
+
40
+ ```mermaid
41
+ flowchart LR
42
+ Work["Working tree / staged changes"] --> Check{"Pre-commit check<br/>LeftBlock + Gitleaks"}
43
+ Check -->|Detected sensitive material| Block["Commit blocked<br/>Fix staged changes"]
44
+ Block --> Work
45
+ Check -->|Checks pass| History["Local Git history<br/>Commit and provenance"]
46
+ History -->|Push| Remote["Remote repository"]
47
+ Remote -->|Clone / pull| Copies["Downstream copies"]
48
+ Work -.->|Hook bypass| History
49
+ Check -.->|Receipt mode approved override| History
50
+ History -.-> Later["Later checks<br/>Pre-push / CI / receipt verification"]
51
+ Remote -.-> Later
52
+ Later --> Limit["May stop further distribution or deployment<br/>Material is already in history"]
53
+
54
+ style Block fill:#ffe4e4,stroke:#b42318
55
+ style Check fill:#e5f3ff,stroke:#175cd3
56
+ ```
57
+
58
+ Local check-only mode installs a Git **`pre-commit` hook**. Receipt mode installs a **`commit-msg` hook**, which runs later but still before Git creates the commit object. The local hook never offers an override or adds a receipt. The default Gitleaks command scans the staged diff, not every working-tree file or all existing history. Staging may already write blobs to the local object database, so blocking a commit does not erase every local copy.
59
+
60
+ This is a layered defense, not a guarantee that a repository contains no sensitive information. Scanners can miss formats or context, allowlists and configuration affect coverage, and hooks can be bypassed with `--no-verify` or disabled. In receipt mode, interactive approval can intentionally override findings. Missing scanners, invalid configuration, or other checker errors block the commit without an override. Use review, narrowly scoped exceptions, and independent checks before pushing or deploying as additional layers.
61
+
62
+ `leftblock verify` validates receipts; it does not rescan content, and a valid receipt can record an approved override. The HMAC binds the recorded result to a tree hash, but anyone who can read the shared secret can mint a receipt. LeftBlock does not itself isolate that secret from an agent running as the same user.
63
+
64
+ ---
65
+
66
+ ## Project and release status
67
+
68
+ LeftBlock has been donated to **Data Parade (`DataParade-io`)**, alongside
69
+ BotSpy. Its repository is
70
+ [DataParade-io/LeftBlock](https://github.com/DataParade-io/LeftBlock).
71
+
72
+ **LeftBlock 0.3.2 is unreleased.** The current source uses `leftblock` as its
73
+ distribution, Python package, and CLI name. It includes no earlier package
74
+ aliases or command shims. The CLI is also available as
75
+ `python -m leftblock.cli`.
76
+
77
+ Current receipt-mode configuration uses `.leftblock.yml`, `.leftblock/`
78
+ rule paths, `LEFTBLOCK_SECRET_PATH`, and `leftblock-*` rule IDs. New receipts
79
+ use the `leftblock-v1` validator for configuration version 1. Earlier
80
+ configuration filenames, environment-variable names, and hook ownership
81
+ records are not migrated automatically.
82
+
83
+ ### Migrating an earlier installation
84
+
85
+ This release does not remove or replace a separately installed earlier
86
+ distribution, and it does not rewrite published releases or Git history.
87
+ That does **not** make an existing installation automatically compatible:
88
+ an editable install or hook may still load code from the checkout being
89
+ updated. Before pulling these changes into such a checkout:
90
+
91
+ 1. Inventory each protected repository's common Git directory, hook manager,
92
+ interpreter, installed package, checker policy, rules, and secret location.
93
+ Linked worktrees share their common Git metadata; coordinate them together.
94
+ 2. Prepare and test the new runtime independently. Retain the working earlier
95
+ runtime and protection until the replacement has passed validation.
96
+ 3. Review and migrate policy paths and rule IDs to the current names. Preserve
97
+ custom rules, narrow exceptions, owner edits, and existing hook-manager
98
+ behavior. Do not replace an edited or foreign hook to force installation.
99
+ 4. For receipt mode, retain the **same secret bytes**. Securely migrate the
100
+ existing file to the new default location, or set `LEFTBLOCK_SECRET_PATH`
101
+ to its existing absolute path in the hook and verifier environments.
102
+ Do not print the secret, commit it, or run an installer that generates a
103
+ replacement secret as a shortcut. New receipt installation is not a
104
+ credential migration tool.
105
+ 5. Coordinate the runtime, policy, hook command and ownership-state transition.
106
+ Keep a rollback path and existing protection until the new setup accepts
107
+ clean fixtures, blocks synthetic sensitive fixtures without advancing
108
+ `HEAD`, and verifies the required historical receipt range.
109
+
110
+ Historical receipts must remain byte-for-byte unchanged. Verification reads
111
+ and authenticates the validator string stored in each receipt, so a naming
112
+ change does not require resigning history when the original secret is
113
+ available. This is a migration checklist, not an automatic migration or a
114
+ claim that existing repositories have already been upgraded.
115
+
116
+ ## Quick Start
117
+
118
+ LeftBlock is built in Python and requires `git`. Install Gitleaks separately
119
+ from a trusted source and confirm `gitleaks version` works. LeftBlock
120
+ configures the integration; it does not install scanner binaries.
121
+
122
+ **1. Install the CLI:**
123
+
124
+ While 0.3.2 is unreleased, install only a reviewed local checkout containing
125
+ these changes. From that checkout, in the intended Python environment:
126
+
127
+ ```bash
128
+ python -m pip install .
129
+ python -m leftblock.cli --help
130
+ ```
131
+
132
+ The installation is from local source, not a published release. Confirm the
133
+ checkout's commit and validation results before installing; do not assume
134
+ `main` or an earlier feature-branch commit contains this implementation.
135
+ Follow the migration checklist above before replacing an earlier setup.
136
+
137
+ After the release is published and its distribution identity is verified,
138
+ the versioned package installation will be:
139
+
140
+ ```bash
141
+ python -m pip install leftblock==0.3.2
142
+ ```
143
+
144
+ **2. Enable local staged protection:**
145
+
146
+ ```bash
147
+ cd my-repo
148
+ leftblock install --local --check-only
149
+ ```
150
+
151
+ This mode installs a managed `pre-commit` hook and keeps its policy in the
152
+ repository's common Git metadata directory. It creates no signing secret,
153
+ adds no commit receipts, and writes no tracked repository files. Linked
154
+ worktrees share that metadata and hook, so coordinate setup across them.
155
+ To select a specific existing Gitleaks executable, pass its absolute path:
156
+
157
+ ```bash
158
+ leftblock install --local --check-only --gitleaks "$(command -v gitleaks)"
159
+ ```
160
+
161
+ The installer preserves existing hooks and refuses to overwrite a foreign
162
+ or edited hook. An unchanged hook managed by LeftBlock can be refreshed.
163
+ A configured `core.hooksPath` is left untouched; compose the check explicitly
164
+ with the existing hook manager instead of replacing it. An earlier hook
165
+ without the current ownership record requires a reviewed migration even
166
+ if it is still functional.
167
+
168
+ To run a noninteractive staged check with an explicit policy:
169
+
170
+ ```bash
171
+ leftblock check --config /absolute/path/to/policy.yml
172
+ ```
173
+
174
+ Use the actual policy path for the repository. The command returns a
175
+ nonzero status for findings, unavailable scanners, or an invalid setup;
176
+ it never offers an override. Before adopting a hook in another project,
177
+ verify that a synthetic sensitive fixture is blocked and a clean fixture
178
+ passes in a throwaway repository. Keep private policy files in local Git
179
+ metadata or another private location.
180
+
181
+ During an in-progress merge, command checkers skip only regular files whose
182
+ complete blob and mode already exist at the same path in a parent. Changed
183
+ resolutions and renamed destinations retain their full staged diff, so
184
+ inherited material in a modified file can still block. If merge scoping
185
+ fails, the full staged check runs. Tactus uses the original index.
186
+ This does not rescan parent history or eliminate staged-diff limitations,
187
+ including sensitive context assembled only by deleting lines.
188
+
189
+ **3. Optionally enable inspection receipts:**
190
+
191
+ ```bash
192
+ leftblock install
193
+ ```
194
+
195
+ Receipt mode installs a `commit-msg` hook, creates a shared secret if one
196
+ is absent, and writes defaults when they do not already exist:
197
+
198
+ - `.leftblock.yml` — checker configuration
199
+ - `.leftblock/gitleaks.toml` — the shipped sensitive-data ruleset
200
+
201
+ Existing files at the current configuration and ruleset paths are not
202
+ overwritten. Other hooks are preserved. Earlier path names are not discovered
203
+ or migrated automatically. The default receipt-mode configuration uses
204
+ Gitleaks with redacted output:
205
+
206
+ ```yaml
207
+ version: 1
208
+ checkers:
209
+ - name: gitleaks
210
+ type: command
211
+ command: gitleaks protect --staged --config .leftblock/gitleaks.toml --redact --no-banner --report-format json --report-path {report_file}
212
+ success_codes: [0]
213
+ finding_codes: [1]
214
+ ```
215
+
216
+ The installed hook pins its Python environment and preserves the installation
217
+ PATH so it can find configured scanners. Review the hook after moving its
218
+ Python environment or Gitleaks executable. For manual composition, run
219
+ `leftblock check --config PATH` in the existing `pre-commit` flow and
220
+ `leftblock hook "$1"` in the `commit-msg` flow when receipts are needed.
221
+ Preserve existing behavior and propagate any failure's exit status.
222
+
223
+ Receipts use HMAC trailers, not Git GPG/SSH signatures; they do not appear
224
+ in `git log --show-signature`. Every machine running `leftblock verify`
225
+ needs the same secret, stored by default at `~/.config/leftblock/secret`
226
+ or the path in `LEFTBLOCK_SECRET_PATH`. Preserve the existing secret during
227
+ migration so historical receipts remain verifiable. Local check-only mode
228
+ does not need this secret.
229
+
230
+ **4. Add independent checks before distribution or deployment:**
231
+
232
+ For projects using receipts, verify the appropriate incoming commit range:
233
+
234
+ ```bash
235
+ leftblock verify HEAD~5..HEAD
236
+ ```
237
+
238
+ Choose the range for the pipeline rather than assuming every push contains
239
+ five commits. Receipt verification needs the shared secret and neither
240
+ rescans content nor rejects valid overrides. Use an independent secret scan
241
+ as another layer. A project using only local check-only mode does not mint
242
+ the receipts required by this gate.
243
+
244
+ ---
245
+
246
+ ## Out-of-the-Box Detection
247
+
248
+ LeftBlock ships a Gitleaks ruleset (copied to `.leftblock/gitleaks.toml` in receipt mode) that
249
+ extends gitleaks' default secret rules with personal-data and crypto-wallet
250
+ rules. These patterns provide a starting point for detection; they do not
251
+ cover every kind of sensitive information.
252
+
253
+ **Covered by gitleaks defaults:** AWS credential pairs, Google/Gemini API keys
254
+ (`AIza...`, default `gcp-api-key` rule), generic API keys in keyword context,
255
+ PEM private keys, and other standard secret formats.
256
+
257
+ **Added by the LeftBlock ruleset:**
258
+
259
+ | Rule | What it catches |
260
+ |---|---|
261
+ | `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com` and `@example.*` placeholders are allowlisted) |
262
+ | `leftblock-home-path-posix` | Home-folder paths `/Users/<name>`, `/home/<name>` |
263
+ | `leftblock-home-path-windows` | `C:\Users\<name>` and `\Users\<name>` |
264
+ | `leftblock-session-uuid` | Potential session UUIDs, with narrow exceptions for canonical Kanbus structural fields and example values |
265
+ | `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values |
266
+ | `leftblock-openai-key` | `sk-`, `sk-proj-`, `sk-svcacct-` OpenAI keys |
267
+ | `leftblock-anthropic-key` | `sk-ant-` Anthropic keys |
268
+ | `leftblock-eth-private-key` | Ethereum-style private keys (`0x` + 64 hex) |
269
+ | `leftblock-wif-key` | Bitcoin WIF private keys (compressed `K/L/M…`, uncompressed `5…`) |
270
+ | `leftblock-bip39-mnemonic` | BIP-39 seed phrases (12–24 words) in seed/mnemonic/recovery context |
271
+
272
+ Kanbus exceptions apply to complete identifier property lines in
273
+ `project/issues/*.json` and `project/events/*.json`. Event actor attribution
274
+ must occupy a root property line in the canonical two-space format.
275
+ Coordination claim references must occupy the canonical payload depth.
276
+ The legacy `project/.cache/index.json` allows only canonical issue ID/parent
277
+ lines and issue-filename keys with numeric modification times; cached
278
+ descriptions and comments still receive the normal checks.
279
+ Provider session fields, nested actor values, prose and arbitrary arrays
280
+ remain scanned. A project prefix alone grants no exception, and a second
281
+ value on the same line cannot inherit one. These checks recognize syntax;
282
+ they do not validate a full JSON schema or prove an identifier is public.
283
+ Other secret rules still apply to values in the accepted fields.
284
+
285
+ ### Crypto wallet material: what is and is not flagged
286
+
287
+ - **Flagged:** `0x`-prefixed 64-hex private keys, WIF-format Bitcoin keys,
288
+ and 12/15/18/21/24-word BIP-39 seed phrases when they appear in
289
+ seed/mnemonic context (`seed_phrase = "..."`, `recovery mnemonic: ...`).
290
+ - **Not flagged on their own:** public wallet addresses (Ethereum `0x` + 40
291
+ hex, Bitcoin base58 addresses) and hash-like hex — SHA-256 digests, git
292
+ SHAs, SHA-512. A bare 64-hex string with no `0x` prefix and no key-like
293
+ keyword is shape-identical to a SHA-256 digest and git SHA, so it is
294
+ deliberately only reported when keyword context (e.g. `private_key = …`)
295
+ indicates it is a key. This trades a small residual gap (a bare,
296
+ unlabeled 64-hex private key stored without any keyword) for not
297
+ drowning developers in digest false positives. BIP-39 checksum
298
+ validation is future validator work; phrase detection is shape- and
299
+ context-based, so unlabeled bare word sequences that read like prose
300
+ are not flagged.
301
+
302
+ ### Why Gitleaks is the default checker
303
+
304
+ The earlier scanner evaluation used a synthetic fixture corpus, clean
305
+ content, and a ~2 MB repository tree. Gitleaks results below include the
306
+ LeftBlock ruleset; these measurements describe that corpus, not a guarantee
307
+ about other repositories or scanner versions:
308
+
309
+ | Scanner | Verdict |
310
+ |---|---|
311
+ | **Gitleaks 8.30.1** (default checker) | Catches API keys, crypto keys (with the LeftBlock ruleset), and personal data via custom rules; deterministic exit codes (0 clean / 1 findings); zero false positives on clean content and on the real tree with the shipped ruleset; fast (~35 ms on 2 MB). |
312
+ | detect-secrets | No OpenAI/AWS-type detector hits for these formats — findings appear only as generic "High Entropy String"/"Secret Keyword"; misses mnemonics and all personal data; exit code 0 even with findings, so not gate-suitable without a wrapper. |
313
+ | TruffleHog (offline) | Its AWS detector caught the credential pair, but nothing else offline (OpenAI/Anthropic/Gemini/crypto/personal data all missed); scans git objects including gitignored files, which produces a different, noisier finding set; no allowlist mechanism for custom detectors. |
314
+ | Presidio | Built for PII text analytics; requires a 500 MB model, flags benign URLs/IPs/timestamps while missing every target category here (including `.test`-TLD emails and keys); unsuitable for commit gating. |
315
+
316
+ False-positive measurements with the shipped ruleset: **0 findings** on a
317
+ clean corpus (benign source/config files, placeholder emails and paths that
318
+ must stay allowed) and **0 findings** on a ~2 MB real repository tree
319
+ (LEFTBLOCK_DOGFOOD_TREE dogfood test). The unit-test suite
320
+ (`tests/test_sensitive_rules.py`) regenerates all fixtures synthetically at
321
+ test time from a seeded RNG — no real credential ever appears in the repo.
322
+
323
+ ---
324
+
325
+ ## Custom Agent-Based Scanning (Tactus)
326
+
327
+ While standard tools like Gitleaks are great for finding AWS IAM keys, they struggle with subtle, context-dependent leaks—like mentioning a confidential client's name or proprietary business logic.
328
+
329
+ LeftBlock supports **Tactus procedures** for agent-based code review.
330
+
331
+ ### Example: Protecting Confidential Client Names
332
+ Imagine you have a list of highly confidential clients that should never be mentioned in your repository's source code.
333
+
334
+ 1. Create a `.gitignored` file named `confidential_clients.txt`.
335
+ 2. Write a Tactus procedure (e.g., `check_clients`) that reads `confidential_clients.txt` and scans the staged files to ensure none of those names appear.
336
+ 3. Add the procedure to your `.leftblock.yml`:
337
+
338
+ ```yaml
339
+ version: 1
340
+ checkers:
341
+ - name: tactus-confidentiality-scan
342
+ type: tactus
343
+ procedure: check_clients
344
+ ```
345
+
346
+ In receipt mode, LeftBlock invokes a configured Tactus checker when a commit is made. If Tactus finds a leaked client name, it exits with an error, LeftBlock blocks the commit, and prompts a human for an override password. If the code is clean, the signature is minted and the commit proceeds.
347
+
348
+ ---
349
+
350
+ ## Deep Dive: How it works
351
+
352
+ ### 1. The Commit Hook
353
+ Receipt mode uses a shared HMAC secret.
354
+
355
+ In receipt mode, before Git creates the commit, LeftBlock:
356
+
357
+ 1. Computes the hash of the staged Git tree.
358
+ 2. Runs the configured scanners. The default Gitleaks command checks the staged diff; custom checkers define their own scope.
359
+ 3. If checks pass, records the result and tree hash in an HMAC receipt and appends it to the commit message as Git trailers. An approved interactive override is recorded as an override, not as a clean result.
360
+
361
+ The tree hash identifies the staged snapshot. It does not imply that the
362
+ scanner examined every file in that snapshot or the repository's history.
363
+
364
+ ```text
365
+ Inspected-by: leftblock-v1
366
+ Inspection-tree: f18588e0c5f5b0a5ba281b5a78242127919d2388
367
+ Inspection-result: clean
368
+ Inspection-at: 2026-08-31T17:01:34Z
369
+ Inspection-sig: hmac-sha256:8cce6e3714782d025eb4ec4aec755...
370
+ ```
371
+
372
+ ### 2. Retroactive Approval (Signature Pooling)
373
+ Because Git commit hashes change if you modify a commit message, you cannot simply add signatures to old commits without rewriting history (e.g., `git rebase`).
374
+
375
+ To support later receipts, LeftBlock ties the signature to the **Tree Hash** (the codebase state) rather than the **Commit Hash**.
376
+
377
+ If an agent bypasses the hook with `--no-verify`, or if you are onboarding an older project, you can run `leftblock approve`. This creates an empty "paperwork" commit at the tip of your branch that holds the signatures for the older commits.
378
+
379
+ ```mermaid
380
+ flowchart LR
381
+ C1["Commit 1<br/>Tree: A<br/>Signed: Yes"] --> C2["Commit 2<br/>Tree: B<br/>Signed: No"]
382
+ C2 --> C3["Commit 3<br/>Tree: C<br/>Signed: No"]
383
+ C3 --> AC["Approval Commit<br/>Signs Trees: B, C"]
384
+
385
+ AC --> Gate{"Deploy Gate<br/>leftblock verify"}
386
+ Gate -->|Pools valid signatures| Check["Are Trees A, B, and C in the pool?"]
387
+ Check -->|Yes| Deploy["Deploy Success"]
388
+ ```
389
+
390
+ Retroactive receipts preserve commit history, but do not remove sensitive material from existing commits or copies. They document a later inspection or approval; they cannot provide the same early interception as a check before the original commit.