leftblock 0.4.0__tar.gz → 0.4.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.
- {leftblock-0.4.0 → leftblock-0.4.2}/PKG-INFO +12 -17
- {leftblock-0.4.0 → leftblock-0.4.2}/README.md +11 -16
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/cli.py +13 -5
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/data/gitleaks.toml +24 -67
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/PKG-INFO +12 -17
- {leftblock-0.4.0 → leftblock-0.4.2}/pyproject.toml +5 -5
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_false_positives.py +9 -6
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_kanbus_advice.py +10 -2
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_sensitive_rules.py +149 -162
- {leftblock-0.4.0 → leftblock-0.4.2}/LICENSE +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/__init__.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/allow.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/bypass.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/core.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/hooks.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock/merge.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/SOURCES.txt +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/dependency_links.txt +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/entry_points.txt +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/requires.txt +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/leftblock.egg-info/top_level.txt +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/setup.cfg +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_branding.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_bypasses.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_commit_gate.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_default_config.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_install.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_local_install.py +0 -0
- {leftblock-0.4.0 → leftblock-0.4.2}/tests/test_recorded_overrides.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: leftblock
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: LeftBlock: a pluggable inspection gate for Git commits
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -124,7 +124,7 @@ configures the integration; it does not install scanner binaries.
|
|
|
124
124
|
In the intended Python environment:
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
python -m pip install leftblock==0.4.
|
|
127
|
+
python -m pip install leftblock==0.4.2
|
|
128
128
|
python -m leftblock.cli --help
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -397,11 +397,11 @@ PEM private keys, and other standard secret formats.
|
|
|
397
397
|
|
|
398
398
|
| Rule | What it catches |
|
|
399
399
|
|---|---|
|
|
400
|
-
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com
|
|
400
|
+
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com`, no-reply addresses and `@example.*` placeholders are allowlisted) |
|
|
401
401
|
| `leftblock-home-path-posix` | Home-folder paths `/Users/<name>`, `/home/<name>` |
|
|
402
402
|
| `leftblock-home-path-windows` | `C:\Users\<name>` and `\Users\<name>` |
|
|
403
|
-
| `leftblock-session-uuid` | Potential session UUIDs
|
|
404
|
-
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values |
|
|
403
|
+
| `leftblock-session-uuid` | Potential session UUIDs (not flagged in Kanbus board data; example values and the `.kanbus.yml` `project_id` line are also allowed) |
|
|
404
|
+
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values (not flagged in Kanbus board data) |
|
|
405
405
|
| `leftblock-openai-key` | `sk-`, `sk-proj-`, `sk-svcacct-` OpenAI keys |
|
|
406
406
|
| `leftblock-anthropic-key` | `sk-ant-` Anthropic keys |
|
|
407
407
|
| `leftblock-eth-private-key` | Ethereum-style private keys (`0x` + 64 hex) |
|
|
@@ -417,18 +417,13 @@ since it may already be exposed. Findings in Kanbus board data
|
|
|
417
417
|
those files and that the content should be fixed through `kbs` (for example
|
|
418
418
|
`kbs update` or `kbs comment`) rather than by hand.
|
|
419
419
|
|
|
420
|
-
Kanbus
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
Provider session fields, nested actor values, prose and arbitrary arrays
|
|
428
|
-
remain scanned. A project prefix alone grants no exception, and a second
|
|
429
|
-
value on the same line cannot inherit one. These checks recognize syntax;
|
|
430
|
-
they do not validate a full JSON schema or prove an identifier is public.
|
|
431
|
-
Other secret rules still apply to values in the accepted fields.
|
|
420
|
+
Kanbus is the mandated task tracker, so identifiers in its board data (actor
|
|
421
|
+
ids, session UUIDs, and UUIDs in any field or prose) are not sensitive and never
|
|
422
|
+
block a `kbs` write. Board data means `project/issues/*.json`,
|
|
423
|
+
`project/events/*.json` and `project/.cache/index.json`; the exception is
|
|
424
|
+
limited to the session-UUID and actor-id rules, so personal emails, home paths
|
|
425
|
+
and API or wallet keys in those files are still detected, and the same content
|
|
426
|
+
in any other path is still flagged.
|
|
432
427
|
|
|
433
428
|
### Crypto wallet material: what is and is not flagged
|
|
434
429
|
|
|
@@ -91,7 +91,7 @@ configures the integration; it does not install scanner binaries.
|
|
|
91
91
|
In the intended Python environment:
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
python -m pip install leftblock==0.4.
|
|
94
|
+
python -m pip install leftblock==0.4.2
|
|
95
95
|
python -m leftblock.cli --help
|
|
96
96
|
```
|
|
97
97
|
|
|
@@ -364,11 +364,11 @@ PEM private keys, and other standard secret formats.
|
|
|
364
364
|
|
|
365
365
|
| Rule | What it catches |
|
|
366
366
|
|---|---|
|
|
367
|
-
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com
|
|
367
|
+
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com`, no-reply addresses and `@example.*` placeholders are allowlisted) |
|
|
368
368
|
| `leftblock-home-path-posix` | Home-folder paths `/Users/<name>`, `/home/<name>` |
|
|
369
369
|
| `leftblock-home-path-windows` | `C:\Users\<name>` and `\Users\<name>` |
|
|
370
|
-
| `leftblock-session-uuid` | Potential session UUIDs
|
|
371
|
-
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values |
|
|
370
|
+
| `leftblock-session-uuid` | Potential session UUIDs (not flagged in Kanbus board data; example values and the `.kanbus.yml` `project_id` line are also allowed) |
|
|
371
|
+
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values (not flagged in Kanbus board data) |
|
|
372
372
|
| `leftblock-openai-key` | `sk-`, `sk-proj-`, `sk-svcacct-` OpenAI keys |
|
|
373
373
|
| `leftblock-anthropic-key` | `sk-ant-` Anthropic keys |
|
|
374
374
|
| `leftblock-eth-private-key` | Ethereum-style private keys (`0x` + 64 hex) |
|
|
@@ -384,18 +384,13 @@ since it may already be exposed. Findings in Kanbus board data
|
|
|
384
384
|
those files and that the content should be fixed through `kbs` (for example
|
|
385
385
|
`kbs update` or `kbs comment`) rather than by hand.
|
|
386
386
|
|
|
387
|
-
Kanbus
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
Provider session fields, nested actor values, prose and arbitrary arrays
|
|
395
|
-
remain scanned. A project prefix alone grants no exception, and a second
|
|
396
|
-
value on the same line cannot inherit one. These checks recognize syntax;
|
|
397
|
-
they do not validate a full JSON schema or prove an identifier is public.
|
|
398
|
-
Other secret rules still apply to values in the accepted fields.
|
|
387
|
+
Kanbus is the mandated task tracker, so identifiers in its board data (actor
|
|
388
|
+
ids, session UUIDs, and UUIDs in any field or prose) are not sensitive and never
|
|
389
|
+
block a `kbs` write. Board data means `project/issues/*.json`,
|
|
390
|
+
`project/events/*.json` and `project/.cache/index.json`; the exception is
|
|
391
|
+
limited to the session-UUID and actor-id rules, so personal emails, home paths
|
|
392
|
+
and API or wallet keys in those files are still detected, and the same content
|
|
393
|
+
in any other path is still flagged.
|
|
399
394
|
|
|
400
395
|
### Crypto wallet material: what is and is not flagged
|
|
401
396
|
|
|
@@ -348,11 +348,18 @@ RULE_HINTS = {
|
|
|
348
348
|
bypass.MARKER_RULE: "Remove the marker and fix the content, or record a reasoned override.",
|
|
349
349
|
bypass.IGNORE_RULE: "Don't suppress findings silently; record a reasoned override per finding.",
|
|
350
350
|
}
|
|
351
|
-
|
|
352
|
-
"This file is written by Kanbus; fix the content
|
|
353
|
-
"(
|
|
351
|
+
KANBUS_ISSUE_HINT = (
|
|
352
|
+
"This issue file is written by Kanbus; fix the content with kbs update "
|
|
353
|
+
"(or kbs comment), don't hand-edit it."
|
|
354
354
|
)
|
|
355
|
-
|
|
355
|
+
# Events are append-only history: kbs update/comment add a new event and leave
|
|
356
|
+
# the flagged text in place.
|
|
357
|
+
KANBUS_EVENT_HINT = (
|
|
358
|
+
"This Kanbus event is append-only history and can't be edited. If the issue "
|
|
359
|
+
"isn't committed yet, run kbs delete <issue id> and recreate it without the "
|
|
360
|
+
"content; otherwise ask the board owner."
|
|
361
|
+
)
|
|
362
|
+
KANBUS_DATA = re.compile(r"(^|/)project/(issues|events)/[^/]+\.jsonl?$")
|
|
356
363
|
|
|
357
364
|
|
|
358
365
|
def print_failures(failures):
|
|
@@ -377,7 +384,8 @@ def print_failures(failures):
|
|
|
377
384
|
if rule in RULE_HINTS:
|
|
378
385
|
print(" Hint: " + RULE_HINTS[rule], file=sys.stderr)
|
|
379
386
|
for name in kanbus_files:
|
|
380
|
-
|
|
387
|
+
hint = KANBUS_EVENT_HINT if KANBUS_DATA.search(name).group(2) == "events" else KANBUS_ISSUE_HINT
|
|
388
|
+
print(f" {_plain(name)}: {hint}", file=sys.stderr)
|
|
381
389
|
else:
|
|
382
390
|
print_warn(str(result.findings))
|
|
383
391
|
|
|
@@ -21,9 +21,10 @@
|
|
|
21
21
|
# indistinguishable from SHA-256 and is intentionally left to the
|
|
22
22
|
# generic-api-key keyword context (see docs).
|
|
23
23
|
#
|
|
24
|
-
# Allowlists are scoped to exact
|
|
25
|
-
#
|
|
26
|
-
#
|
|
24
|
+
# Allowlists are scoped to exact values (placeholder usernames/actor ids,
|
|
25
|
+
# the 00000000-0000-4000-8000-... example UUID shape, the .kanbus.yml
|
|
26
|
+
# project_id line). The one path-wide exception is Kanbus board data, whose
|
|
27
|
+
# identifiers are not sensitive for the session-uuid and actor-id rules only.
|
|
27
28
|
|
|
28
29
|
title = "LeftBlock sensitive-data rules"
|
|
29
30
|
|
|
@@ -62,6 +63,8 @@ keywords = ["@"]
|
|
|
62
63
|
regexes = [
|
|
63
64
|
# Infrastructure and placeholder identities, not personal ones.
|
|
64
65
|
'''@users\.noreply\.github\.com''',
|
|
66
|
+
# No-reply addresses (e.g. commit Co-Authored-By trailers) are not personal.
|
|
67
|
+
'''(^|[^a-zA-Z0-9._%+\-'])no-?reply@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}''',
|
|
65
68
|
'''git@github\.com''',
|
|
66
69
|
'''@(example|github)\.(com|org|net|io|ai)''',
|
|
67
70
|
]
|
|
@@ -106,64 +109,19 @@ regexes = [
|
|
|
106
109
|
'''^11111111-1111-4111-8111-1111111111[0-9a-f]{2}$''',
|
|
107
110
|
]
|
|
108
111
|
|
|
109
|
-
# Kanbus
|
|
110
|
-
#
|
|
111
|
-
#
|
|
112
|
-
#
|
|
113
|
-
#
|
|
114
|
-
# single boundary byte, plus CRLF endings, without spanning properties.
|
|
112
|
+
# Kanbus board data (issues, events and the derived cache index) is written by
|
|
113
|
+
# Kanbus and is the mandated tracker in these repositories. Its identifiers,
|
|
114
|
+
# including actor ids and session UUIDs, are not treated as sensitive
|
|
115
|
+
# (decision 2026-10-20), so LeftBlock never blocks a kbs write on them. Emails,
|
|
116
|
+
# home paths and keys in board files are still detected by their own rules.
|
|
115
117
|
[[rules.allowlists]]
|
|
116
|
-
description = "Kanbus board
|
|
117
|
-
condition = "AND"
|
|
118
|
+
description = "Kanbus board data identifiers"
|
|
118
119
|
paths = [
|
|
119
120
|
'''(^|/)project/(issues|events)/[^/]+\.json$''',
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
regexes = [
|
|
123
|
-
'''^\n?[ \t]*"(id|comment_id|event_id|issue_id|parent_id|parent|target|target_id|from|to)"[ \t]*:[ \t]*"(?:[A-Za-z][A-Za-z0-9_]*-)*[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",?[ \t]*\r?$''',
|
|
124
|
-
]
|
|
125
|
-
|
|
126
|
-
# Event actor attribution is structural too. Keep this separate from
|
|
127
|
-
# session_id/provider_session_id/resumed_session, which remain detected.
|
|
128
|
-
[[rules.allowlists]]
|
|
129
|
-
description = "Kanbus event UUID actor attribution"
|
|
130
|
-
condition = "AND"
|
|
131
|
-
paths = [
|
|
132
|
-
'''(^|/)project/events/[^/]+\.json$''',
|
|
133
|
-
]
|
|
134
|
-
regexTarget = "line"
|
|
135
|
-
regexes = [
|
|
136
|
-
'''^\n? "actor_id"[ \t]*:[ \t]*"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",?[ \t]*\r?$''',
|
|
137
|
-
]
|
|
138
|
-
|
|
139
|
-
# Coordination events refer to a generated claim by its prefixed key.
|
|
140
|
-
# Require the canonical payload-property depth; bare session values,
|
|
141
|
-
# root/deeper properties, prose and neighboring values are not exempt.
|
|
142
|
-
[[rules.allowlists]]
|
|
143
|
-
description = "Kanbus coordination payload claim reference"
|
|
144
|
-
condition = "AND"
|
|
145
|
-
paths = [
|
|
146
|
-
'''(^|/)project/events/[^/]+\.json$''',
|
|
147
|
-
]
|
|
148
|
-
regexTarget = "line"
|
|
149
|
-
regexes = [
|
|
150
|
-
'''^\n? "claim_id"[ \t]*:[ \t]*"(?:[A-Za-z][A-Za-z0-9_]*-)+[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",?[ \t]*\r?$''',
|
|
151
|
-
]
|
|
152
|
-
|
|
153
|
-
# The legacy Kanbus cache duplicates issue identifiers and stores numeric
|
|
154
|
-
# mtimes keyed by issue filename. Do not exempt the cache file: it also
|
|
155
|
-
# contains full descriptions and comments that still require scanning.
|
|
156
|
-
[[rules.allowlists]]
|
|
157
|
-
description = "Kanbus cache issue identifiers and numeric filename mtimes"
|
|
158
|
-
condition = "AND"
|
|
159
|
-
paths = [
|
|
121
|
+
# Kanbus's LLM usage log (one JSON record per line).
|
|
122
|
+
'''(^|/)project/events/[^/]+\.jsonl$''',
|
|
160
123
|
'''(^|/)project/\.cache/index\.json$''',
|
|
161
124
|
]
|
|
162
|
-
regexTarget = "line"
|
|
163
|
-
regexes = [
|
|
164
|
-
'''^\n? "(id|parent)"[ \t]*:[ \t]*"(?:[A-Za-z][A-Za-z0-9_]*-)+[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}",?[ \t]*\r?$''',
|
|
165
|
-
'''^\n? "(?:[A-Za-z][A-Za-z0-9_]*-)+[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.json"[ \t]*:[ \t]*-?(?:0|[1-9][0-9]*)(?:\.[0-9]+)?(?:[eE][+-]?[0-9]+)?,?[ \t]*\r?$''',
|
|
166
|
-
]
|
|
167
125
|
|
|
168
126
|
# Kanbus 0.40+ records a public per-project UUID v4 in .kanbus.yml so every
|
|
169
127
|
# clone shares one realtime topic. Accept only the top-level YAML key with a
|
|
@@ -191,19 +149,18 @@ regexes = [
|
|
|
191
149
|
'''^example[-_a-z0-9]*$''',
|
|
192
150
|
]
|
|
193
151
|
|
|
194
|
-
# Kanbus
|
|
195
|
-
#
|
|
196
|
-
#
|
|
197
|
-
#
|
|
152
|
+
# Kanbus board data (issues, events and the derived cache index) is written by
|
|
153
|
+
# Kanbus and is the mandated tracker in these repositories. Its identifiers,
|
|
154
|
+
# including actor ids and session UUIDs, are not treated as sensitive
|
|
155
|
+
# (decision 2026-10-20), so LeftBlock never blocks a kbs write on them. Emails,
|
|
156
|
+
# home paths and keys in board files are still detected by their own rules.
|
|
198
157
|
[[rules.allowlists]]
|
|
199
|
-
description = "Kanbus board
|
|
200
|
-
condition = "AND"
|
|
158
|
+
description = "Kanbus board data identifiers"
|
|
201
159
|
paths = [
|
|
202
|
-
'''(^|/)project/events/[^/]+\.json$''',
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
'''^\n? "actor_id"[ \t]*:[ \t]*"[^"]+",?[ \t]*\r?$''',
|
|
160
|
+
'''(^|/)project/(issues|events)/[^/]+\.json$''',
|
|
161
|
+
# Kanbus's LLM usage log (one JSON record per line).
|
|
162
|
+
'''(^|/)project/events/[^/]+\.jsonl$''',
|
|
163
|
+
'''(^|/)project/\.cache/index\.json$''',
|
|
207
164
|
]
|
|
208
165
|
|
|
209
166
|
# --------------------------------------------------------------------
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: leftblock
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: LeftBlock: a pluggable inspection gate for Git commits
|
|
5
5
|
License: MIT License
|
|
6
6
|
|
|
@@ -124,7 +124,7 @@ configures the integration; it does not install scanner binaries.
|
|
|
124
124
|
In the intended Python environment:
|
|
125
125
|
|
|
126
126
|
```bash
|
|
127
|
-
python -m pip install leftblock==0.4.
|
|
127
|
+
python -m pip install leftblock==0.4.2
|
|
128
128
|
python -m leftblock.cli --help
|
|
129
129
|
```
|
|
130
130
|
|
|
@@ -397,11 +397,11 @@ PEM private keys, and other standard secret formats.
|
|
|
397
397
|
|
|
398
398
|
| Rule | What it catches |
|
|
399
399
|
|---|---|
|
|
400
|
-
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com
|
|
400
|
+
| `leftblock-personal-email` | Personal email addresses (infrastructure identities like `git@github.com`, `@users.noreply.github.com`, no-reply addresses and `@example.*` placeholders are allowlisted) |
|
|
401
401
|
| `leftblock-home-path-posix` | Home-folder paths `/Users/<name>`, `/home/<name>` |
|
|
402
402
|
| `leftblock-home-path-windows` | `C:\Users\<name>` and `\Users\<name>` |
|
|
403
|
-
| `leftblock-session-uuid` | Potential session UUIDs
|
|
404
|
-
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values |
|
|
403
|
+
| `leftblock-session-uuid` | Potential session UUIDs (not flagged in Kanbus board data; example values and the `.kanbus.yml` `project_id` line are also allowed) |
|
|
404
|
+
| `leftblock-actor-id` | `actor_id` / `user_id` / `author_id` JSON values (not flagged in Kanbus board data) |
|
|
405
405
|
| `leftblock-openai-key` | `sk-`, `sk-proj-`, `sk-svcacct-` OpenAI keys |
|
|
406
406
|
| `leftblock-anthropic-key` | `sk-ant-` Anthropic keys |
|
|
407
407
|
| `leftblock-eth-private-key` | Ethereum-style private keys (`0x` + 64 hex) |
|
|
@@ -417,18 +417,13 @@ since it may already be exposed. Findings in Kanbus board data
|
|
|
417
417
|
those files and that the content should be fixed through `kbs` (for example
|
|
418
418
|
`kbs update` or `kbs comment`) rather than by hand.
|
|
419
419
|
|
|
420
|
-
Kanbus
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
Provider session fields, nested actor values, prose and arbitrary arrays
|
|
428
|
-
remain scanned. A project prefix alone grants no exception, and a second
|
|
429
|
-
value on the same line cannot inherit one. These checks recognize syntax;
|
|
430
|
-
they do not validate a full JSON schema or prove an identifier is public.
|
|
431
|
-
Other secret rules still apply to values in the accepted fields.
|
|
420
|
+
Kanbus is the mandated task tracker, so identifiers in its board data (actor
|
|
421
|
+
ids, session UUIDs, and UUIDs in any field or prose) are not sensitive and never
|
|
422
|
+
block a `kbs` write. Board data means `project/issues/*.json`,
|
|
423
|
+
`project/events/*.json` and `project/.cache/index.json`; the exception is
|
|
424
|
+
limited to the session-UUID and actor-id rules, so personal emails, home paths
|
|
425
|
+
and API or wallet keys in those files are still detected, and the same content
|
|
426
|
+
in any other path is still flagged.
|
|
432
427
|
|
|
433
428
|
### Crypto wallet material: what is and is not flagged
|
|
434
429
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "leftblock"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "LeftBlock: a pluggable inspection gate for Git commits"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {file = "LICENSE"}
|
|
@@ -27,14 +27,14 @@ leftblock = "leftblock.cli:main"
|
|
|
27
27
|
version_toml = ["pyproject.toml:project.version"]
|
|
28
28
|
commit_message = "chore(release): {version}"
|
|
29
29
|
tag_format = "v{version}"
|
|
30
|
-
#
|
|
31
|
-
#
|
|
30
|
+
# scripts/release_prepare.py keeps the README pin on NEW_VERSION and drops
|
|
31
|
+
# commit trailers from the generated changelog; both files are committed.
|
|
32
32
|
build_command = """
|
|
33
|
-
|
|
33
|
+
python scripts/release_prepare.py
|
|
34
34
|
python -m pip install build
|
|
35
35
|
python -m build
|
|
36
36
|
"""
|
|
37
|
-
assets = ["README.md"]
|
|
37
|
+
assets = ["README.md", "CHANGELOG.md"]
|
|
38
38
|
|
|
39
39
|
[tool.semantic_release.branches.main]
|
|
40
40
|
match = "main"
|
|
@@ -73,12 +73,15 @@ class KanbusIdTests(unittest.TestCase):
|
|
|
73
73
|
"notes.md": "see PPY-%s; session %s\n" % (_uuid(rng), _uuid(rng)),
|
|
74
74
|
}))
|
|
75
75
|
|
|
76
|
-
def
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
76
|
+
def test_session_value_with_prefix_passes_in_board_files_only(self):
|
|
77
|
+
doc = json.dumps({"session_id": "PPY-" + _uuid(_rng())}, indent=2)
|
|
78
|
+
self.assertEqual(
|
|
79
|
+
scan_and_rule_ids({"project/events/synthetic.json": doc}), [])
|
|
80
|
+
for path in ("src/synthetic.json", "project/other.json",
|
|
81
|
+
"project/events/sub/synthetic.json"):
|
|
82
|
+
with self.subTest(path=path):
|
|
83
|
+
self.assertIn("leftblock-session-uuid",
|
|
84
|
+
scan_and_rule_ids({path: doc}))
|
|
82
85
|
|
|
83
86
|
|
|
84
87
|
def _uuid4(rng):
|
|
@@ -79,11 +79,19 @@ class KanbusBoardTests(RepositoryFixture):
|
|
|
79
79
|
self.assertIn("leftblock-home-path-posix", proc.stderr)
|
|
80
80
|
self.assertIn("Hint: Use a relative path, $HOME or ~ instead.", proc.stderr)
|
|
81
81
|
self.assertEqual(
|
|
82
|
-
proc.stderr.count("This file is written by Kanbus; fix the content
|
|
83
|
-
"(e.g. kbs update or comment), don't hand-edit it."), 1,
|
|
82
|
+
proc.stderr.count("This issue file is written by Kanbus; fix the content with kbs update"), 1,
|
|
84
83
|
)
|
|
85
84
|
self.assertNotIn(home_path, proc.stdout + proc.stderr)
|
|
86
85
|
|
|
86
|
+
def test_event_file_hint_says_delete_and_recreate_not_update(self):
|
|
87
|
+
self.write("project/events/2026-10-20T10-00-00.000Z__x.json",
|
|
88
|
+
'{\n "note": "see /' + 'Users/someone/notes"\n}\n')
|
|
89
|
+
proc = self.leftblock("check")
|
|
90
|
+
self.assertEqual(proc.returncode, 1)
|
|
91
|
+
self.assertIn("append-only history", proc.stderr)
|
|
92
|
+
self.assertIn("kbs delete <issue id>", proc.stderr)
|
|
93
|
+
self.assertNotIn("kbs update", proc.stderr)
|
|
94
|
+
|
|
87
95
|
def test_hint_is_printed_once_per_rule(self):
|
|
88
96
|
self.write("notes.md", "a /" + "Users/one/x\nb /" + "Users/two/y\n")
|
|
89
97
|
proc = self.leftblock("check")
|
|
@@ -222,6 +222,17 @@ class SensitiveFixtureTests(unittest.TestCase):
|
|
|
222
222
|
class NegativeTests(unittest.TestCase):
|
|
223
223
|
"""Public wallet addresses and hash-like hex must NOT be flagged."""
|
|
224
224
|
|
|
225
|
+
def test_no_reply_addresses_pass_but_lookalikes_still_flag(self):
|
|
226
|
+
at = "@"
|
|
227
|
+
self.assertEqual(scan_and_rule_ids({"CHANGELOG.md": "\n".join([
|
|
228
|
+
"Co-Authored-By: Bot <noreply" + at + "vendor.test>",
|
|
229
|
+
"From: no-reply" + at + "vendor.test",
|
|
230
|
+
]) + "\n"}), [])
|
|
231
|
+
self.assertEqual(scan_and_rule_ids({"notes.md": "mail jo" + at + "vendor.test\n"}),
|
|
232
|
+
["leftblock-personal-email"])
|
|
233
|
+
self.assertEqual(scan_and_rule_ids({"notes.md": "mail notnoreply" + at + "vendor.test\n"}),
|
|
234
|
+
["leftblock-personal-email"])
|
|
235
|
+
|
|
225
236
|
def test_public_wallet_addresses_and_hashes_not_flagged(self):
|
|
226
237
|
rng = _rng()
|
|
227
238
|
findings = scan_and_rule_ids({
|
|
@@ -332,172 +343,148 @@ def _uuid(rng):
|
|
|
332
343
|
|
|
333
344
|
@unittest.skipUnless(GITLEAKS, "gitleaks not installed")
|
|
334
345
|
class KanbusBoardAllowlistTests(unittest.TestCase):
|
|
335
|
-
"""Kanbus board
|
|
346
|
+
"""Kanbus board data identifiers are not sensitive; nothing else changes.
|
|
347
|
+
|
|
348
|
+
Policy (2026-10-20): in board data, actor ids, session UUIDs and UUIDs in
|
|
349
|
+
any field or prose pass. Emails, home paths and keys are still detected
|
|
350
|
+
there, and the same content outside board data is still flagged.
|
|
351
|
+
"""
|
|
352
|
+
|
|
353
|
+
BOARD_PATHS = (
|
|
354
|
+
"project/issues/PPY-x.json",
|
|
355
|
+
"project/events/2026-10-20T00-00-00.000Z__x.json",
|
|
356
|
+
"project/events/llm_usage.jsonl",
|
|
357
|
+
"project/.cache/index.json",
|
|
358
|
+
"sub/dir/project/issues/PPY-x.json",
|
|
359
|
+
"sub/dir/project/events/e.json",
|
|
360
|
+
"sub/dir/project/.cache/index.json",
|
|
361
|
+
)
|
|
362
|
+
NON_BOARD_PATHS = (
|
|
363
|
+
"notes.md",
|
|
364
|
+
"project/other.json",
|
|
365
|
+
"project/issues/sub/x.json",
|
|
366
|
+
"project/events/sub/x.json",
|
|
367
|
+
"project/events.json",
|
|
368
|
+
"project/issues/x.json.bak",
|
|
369
|
+
"project/issues/x.jsonl",
|
|
370
|
+
"src/project/issues-like/x.json",
|
|
371
|
+
"myproject/issues/x.json",
|
|
372
|
+
"project/.cache/other.json",
|
|
373
|
+
"project/.cache/sub/index.json",
|
|
374
|
+
)
|
|
336
375
|
|
|
337
376
|
def setUp(self):
|
|
338
377
|
self.rng = _rng()
|
|
339
378
|
|
|
340
|
-
def
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
"
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
json.dumps({"id": "PPY-" + value + " extra"}, indent=2),
|
|
402
|
-
json.dumps({"description": ['PPY-' + value]}, indent=2),
|
|
403
|
-
json.dumps({"actor_id": value, "session_id": _uuid(self.rng)}),
|
|
404
|
-
):
|
|
405
|
-
with self.subTest(doc_shape=len(doc)):
|
|
406
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({
|
|
407
|
-
"project/events/synthetic.json": doc,
|
|
408
|
-
}))
|
|
409
|
-
|
|
410
|
-
def test_all_structural_prefixes_still_flagged_outside_board_files(self):
|
|
411
|
-
for prefix in ("", "PPY-", "BOTSPY-", "kanbus-"):
|
|
412
|
-
with self.subTest(prefix=prefix):
|
|
413
|
-
doc = json.dumps({"id": prefix + _uuid(self.rng)}, indent=2)
|
|
414
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({
|
|
415
|
-
"src/state.json": doc,
|
|
416
|
-
}))
|
|
417
|
-
|
|
418
|
-
def test_actor_attribution_does_not_allow_other_sensitive_rules(self):
|
|
419
|
-
actor = "sk-proj-" + rand_openai_like(self.rng, 56)
|
|
420
|
-
doc = json.dumps({"actor_id": actor}, indent=2)
|
|
421
|
-
self.assertIn("leftblock-openai-key", scan_and_rule_ids({
|
|
422
|
-
"project/events/synthetic.json": doc,
|
|
423
|
-
}))
|
|
424
|
-
|
|
425
|
-
def test_nested_actor_and_split_line_actor_remain_detected(self):
|
|
426
|
-
nested = json.dumps({"payload": {"actor_id": _uuid(self.rng)}}, indent=2)
|
|
427
|
-
findings = scan_and_rule_ids({"project/events/synthetic.json": nested})
|
|
428
|
-
self.assertIn("leftblock-actor-id", findings)
|
|
429
|
-
self.assertIn("leftblock-session-uuid", findings)
|
|
430
|
-
split = '"actor_id"\n:\n"local' + 'user"'
|
|
431
|
-
self.assertIn("leftblock-actor-id", scan_and_rule_ids({"src/state.json": split}))
|
|
432
|
-
|
|
433
|
-
def test_example_or_cache_key_does_not_hide_another_uuid(self):
|
|
434
|
-
example = "-".join(("00000000", "0000", "4000", "8000", "0123456789ab"))
|
|
435
|
-
for first in (example, f"PPY-{_uuid(self.rng)}.json"):
|
|
436
|
-
line = first + " session=" + _uuid(self.rng)
|
|
437
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({
|
|
438
|
-
"project/events/synthetic.json": line,
|
|
439
|
-
}))
|
|
440
|
-
|
|
441
|
-
def test_canonical_crlf_event_passes(self):
|
|
442
|
-
event = json.dumps({
|
|
443
|
-
"event_id": _uuid(self.rng), "actor_id": _uuid(self.rng),
|
|
444
|
-
"issue_id": f"PPY-{_uuid(self.rng)}",
|
|
445
|
-
}, indent=2).replace("\n", "\r\n")
|
|
446
|
-
self.assertEqual(scan_and_rule_ids({"project/events/synthetic.json": event}), [])
|
|
447
|
-
|
|
448
|
-
def test_coordination_claim_reference_passes_only_in_event_payload(self):
|
|
449
|
-
claim = "claim-" + _uuid(self.rng)
|
|
450
|
-
event = json.dumps({"event_type": "coordination.claim",
|
|
451
|
-
"payload": {"claim_id": claim}}, indent=2)
|
|
452
|
-
self.assertEqual(scan_and_rule_ids({"project/events/synthetic.json": event}), [])
|
|
453
|
-
for path in ("project/issues/synthetic.json", "src/state.json"):
|
|
454
|
-
with self.subTest(path=path):
|
|
455
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({path: event}))
|
|
456
|
-
for doc in (
|
|
457
|
-
json.dumps({"claim_id": claim}, indent=2),
|
|
458
|
-
json.dumps({"payload": {"nested": {"claim_id": claim}}}, indent=2),
|
|
459
|
-
json.dumps({"payload": {"claim_id": claim + " copied"}}, indent=2),
|
|
460
|
-
json.dumps({"payload": {"session_id": claim}}, indent=2),
|
|
461
|
-
json.dumps({"payload": {"claim_id": claim, "session_id": _uuid(self.rng)}}),
|
|
462
|
-
json.dumps({"payload": {"claim_id": _uuid(self.rng)}}, indent=2),
|
|
463
|
-
):
|
|
464
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({
|
|
465
|
-
"project/events/synthetic.json": doc,
|
|
466
|
-
}))
|
|
467
|
-
|
|
468
|
-
def test_cache_bookkeeping_passes_without_exempting_cached_content(self):
|
|
379
|
+
def _content(self):
|
|
380
|
+
"""Identifier-bearing content in many shapes (indented and minified)."""
|
|
381
|
+
u = lambda: _uuid(self.rng) # noqa: E731
|
|
382
|
+
actor_key = "actor" + "_id"
|
|
383
|
+
return {
|
|
384
|
+
"session_fields": json.dumps({
|
|
385
|
+
"session_id": u(), "provider_session_id": u(),
|
|
386
|
+
"resumed_session": u()}, indent=2),
|
|
387
|
+
"prose": json.dumps({
|
|
388
|
+
"description": "session " + u() + " reproduced it",
|
|
389
|
+
"text": "see PPY-" + u() + " and BOTSPY-" + u()}, indent=2),
|
|
390
|
+
"nested_actor": json.dumps(
|
|
391
|
+
{"payload": {actor_key: u()}}, indent=2),
|
|
392
|
+
"split_actor": '"%s"\n:\n"%s"' % (actor_key, u()),
|
|
393
|
+
"minified_event": json.dumps({
|
|
394
|
+
"event_id": u(), actor_key: u(), "session_id": u(),
|
|
395
|
+
"payload": {"comment_id": u(), "text": "x " + u()}}),
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
def test_identifiers_pass_in_every_board_path(self):
|
|
399
|
+
for path in self.BOARD_PATHS:
|
|
400
|
+
for shape, doc in self._content().items():
|
|
401
|
+
with self.subTest(path=path, shape=shape):
|
|
402
|
+
self.assertEqual(scan_and_rule_ids({path: doc}), [])
|
|
403
|
+
|
|
404
|
+
def test_session_style_fields_pass_with_any_prefix_in_board_files(self):
|
|
405
|
+
for path in self.BOARD_PATHS:
|
|
406
|
+
for field in ("session_id", "provider_session_id", "resumed_session"):
|
|
407
|
+
for prefix in ("", "PPY-", "BOTSPY-", "kanbus-"):
|
|
408
|
+
with self.subTest(path=path, field=field, prefix=prefix):
|
|
409
|
+
doc = json.dumps({field: prefix + _uuid(self.rng)}, indent=2)
|
|
410
|
+
self.assertEqual(scan_and_rule_ids({path: doc}), [])
|
|
411
|
+
|
|
412
|
+
def test_bare_and_prefixed_uuids_in_prose_pass_in_board_files(self):
|
|
413
|
+
for path in self.BOARD_PATHS:
|
|
414
|
+
for field in ("description", "text"):
|
|
415
|
+
for prefix in ("", "PPY-", "kanbus-"):
|
|
416
|
+
with self.subTest(path=path, field=field, prefix=prefix):
|
|
417
|
+
doc = json.dumps({
|
|
418
|
+
"id": "PPY-" + _uuid(self.rng),
|
|
419
|
+
field: "seen in %s%s today" % (prefix, _uuid(self.rng)),
|
|
420
|
+
}, indent=2)
|
|
421
|
+
self.assertEqual(scan_and_rule_ids({path: doc}), [])
|
|
422
|
+
|
|
423
|
+
def test_actor_ids_pass_nested_split_and_minified_in_board_files(self):
|
|
424
|
+
key = "actor" + "_id"
|
|
425
|
+
for value in (_uuid(self.rng), "local" + "user"):
|
|
426
|
+
shapes = {
|
|
427
|
+
"root": json.dumps({key: value}, indent=2),
|
|
428
|
+
"nested": json.dumps({"payload": {key: value}}, indent=2),
|
|
429
|
+
"split": '"%s"\n:\n"%s"' % (key, value),
|
|
430
|
+
"minified": json.dumps({"event_type": "x", key: value}),
|
|
431
|
+
"same_line_session": json.dumps(
|
|
432
|
+
{key: value, "session_id": _uuid(self.rng)}),
|
|
433
|
+
}
|
|
434
|
+
for path in self.BOARD_PATHS:
|
|
435
|
+
for shape, doc in shapes.items():
|
|
436
|
+
with self.subTest(path=path, shape=shape):
|
|
437
|
+
self.assertEqual(scan_and_rule_ids({path: doc}), [])
|
|
438
|
+
|
|
439
|
+
def test_canonical_crlf_event_and_cache_pass(self):
|
|
469
440
|
iid = "PPY-" + _uuid(self.rng)
|
|
470
|
-
|
|
441
|
+
event = json.dumps({
|
|
442
|
+
"event_id": _uuid(self.rng), "actor" + "_id": _uuid(self.rng),
|
|
443
|
+
"issue_id": iid}, indent=2).replace("\n", "\r\n")
|
|
444
|
+
self.assertEqual(scan_and_rule_ids({"project/events/e.json": event}), [])
|
|
445
|
+
cache = json.dumps({
|
|
471
446
|
"file_mtimes": {iid + ".json": 1792500000.25},
|
|
472
|
-
"issues": [{"id": iid, "
|
|
447
|
+
"issues": [{"id": iid, "description": "s " + _uuid(self.rng),
|
|
448
|
+
"session_id": _uuid(self.rng)}]}, indent=2)
|
|
449
|
+
self.assertEqual(scan_and_rule_ids({"project/.cache/index.json": cache}), [])
|
|
450
|
+
|
|
451
|
+
def test_same_identifiers_still_flagged_outside_board_paths(self):
|
|
452
|
+
for path in self.NON_BOARD_PATHS:
|
|
453
|
+
for shape, doc in self._content().items():
|
|
454
|
+
with self.subTest(path=path, shape=shape):
|
|
455
|
+
findings = scan_and_rule_ids({path: doc})
|
|
456
|
+
self.assertIn("leftblock-session-uuid", findings)
|
|
457
|
+
|
|
458
|
+
def test_actor_ids_still_flagged_outside_board_paths(self):
|
|
459
|
+
key = "actor" + "_id"
|
|
460
|
+
doc = json.dumps({key: "local" + "user"}, indent=2)
|
|
461
|
+
split = '"%s"\n:\n"%s"' % (key, "local" + "user")
|
|
462
|
+
for path in self.NON_BOARD_PATHS:
|
|
463
|
+
for shape, content in (("root", doc), ("split", split)):
|
|
464
|
+
with self.subTest(path=path, shape=shape):
|
|
465
|
+
self.assertIn("leftblock-actor-id",
|
|
466
|
+
scan_and_rule_ids({path: content}))
|
|
467
|
+
|
|
468
|
+
def test_personal_email_home_path_and_key_still_flagged_in_board_files(self):
|
|
469
|
+
email = "jane." + "doe@" + "example-corp." + "t" + "est"
|
|
470
|
+
user = "jd" + "oe"
|
|
471
|
+
cases = {
|
|
472
|
+
"leftblock-personal-email": json.dumps(
|
|
473
|
+
{"assignee": email}, indent=2),
|
|
474
|
+
"leftblock-home-path-posix": json.dumps(
|
|
475
|
+
{"description": "see /Users/%s/work" % user}, indent=2),
|
|
476
|
+
"leftblock-openai-key": json.dumps(
|
|
477
|
+
{"text": "key sk-proj-" + rand_openai_like(self.rng, 56)},
|
|
478
|
+
indent=2),
|
|
473
479
|
}
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
for
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
path: json.dumps(cache, indent=2),
|
|
486
|
-
}))
|
|
487
|
-
del cache["issues"][0][field]
|
|
488
|
-
cache["issues"][0]["actor_id"] = "sk-proj-" + rand_openai_like(self.rng, 56)
|
|
489
|
-
self.assertIn("leftblock-openai-key", scan_and_rule_ids({
|
|
490
|
-
path: json.dumps(cache, indent=2),
|
|
491
|
-
}))
|
|
492
|
-
|
|
493
|
-
def test_cache_key_allowance_cannot_hide_another_uuid(self):
|
|
494
|
-
key = "PPY-" + _uuid(self.rng) + ".json"
|
|
495
|
-
value = _uuid(self.rng)
|
|
496
|
-
for line in (
|
|
497
|
-
f' "{key}": 123.5, "session_id": "{value}"',
|
|
498
|
-
f' "{key}": "{value}"',
|
|
499
|
-
f' "{key}": {{"session_id": "{value}"}}',
|
|
500
|
-
):
|
|
501
|
-
self.assertIn("leftblock-session-uuid", scan_and_rule_ids({
|
|
502
|
-
"project/.cache/index.json": line,
|
|
503
|
-
}))
|
|
480
|
+
for path in self.BOARD_PATHS:
|
|
481
|
+
for rule, doc in cases.items():
|
|
482
|
+
with self.subTest(path=path, rule=rule):
|
|
483
|
+
self.assertIn(rule, scan_and_rule_ids({path: doc}))
|
|
484
|
+
|
|
485
|
+
def test_key_in_actor_field_still_flagged_in_board_files(self):
|
|
486
|
+
doc = json.dumps({"actor" + "_id": "sk-proj-" + rand_openai_like(self.rng, 56)},
|
|
487
|
+
indent=2)
|
|
488
|
+
for path in self.BOARD_PATHS:
|
|
489
|
+
with self.subTest(path=path):
|
|
490
|
+
self.assertIn("leftblock-openai-key", scan_and_rule_ids({path: doc}))
|
|
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
|