codesuture 0.7.0__tar.gz → 0.7.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.
- codesuture-0.7.2/.gitignore +53 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/CHANGELOG.md +68 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/PKG-INFO +41 -58
- {codesuture-0.7.0 → codesuture-0.7.2}/README.md +39 -57
- codesuture-0.7.2/codesuture/__init__.py +1 -0
- codesuture-0.7.2/codesuture/_eval_fix.py +5 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/audit.py +7 -10
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/cli.py +4 -1
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/explain.py +10 -10
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/fingerprint.py +26 -21
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/guard_synthesizer.py +164 -156
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/middleware.py +10 -5
- codesuture-0.7.2/codesuture/opcodes.py +146 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/pattern_matcher.py +100 -91
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/persistence.py +52 -21
- codesuture-0.7.2/codesuture/rewind.py +78 -0
- codesuture-0.7.2/codesuture/rollback.py +167 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/tracer.py +359 -315
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/PKG-INFO +41 -58
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/SOURCES.txt +6 -6
- {codesuture-0.7.0 → codesuture-0.7.2}/pyproject.toml +2 -1
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_new_guards.py +48 -48
- codesuture-0.7.2/tests/test_opcodes.py +222 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_pattern_matcher.py +15 -2
- codesuture-0.7.2/tests/test_persistence.py +201 -0
- codesuture-0.7.2/tests/test_rollback.py +210 -0
- codesuture-0.7.2/tests/test_thread_safety.py +138 -0
- codesuture-0.7.2/tests/test_transparency.py +135 -0
- codesuture-0.7.0/.gitignore +0 -14
- codesuture-0.7.0/codesuture/__init__.py +0 -2
- codesuture-0.7.0/codesuture/_eval_fix.py +0 -5
- codesuture-0.7.0/codesuture/rewind.py +0 -49
- codesuture-0.7.0/codesuture/rollback.py +0 -85
- codesuture-0.7.0/tests/closure_test.py +0 -16
- codesuture-0.7.0/tests/debug_gc.py +0 -23
- codesuture-0.7.0/tests/harness3.py +0 -46
- codesuture-0.7.0/tests/test_codesuture_debuggee.py +0 -17
- codesuture-0.7.0/tests/test_tracer_active_shield.py +0 -70
- codesuture-0.7.0/tests/test_unknown_bug.py +0 -9
- {codesuture-0.7.0 → codesuture-0.7.2}/LICENSE +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/MANIFEST.in +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/__main__.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/code_replacer.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/codesuture_fix.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/debuggee.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/diff_guard.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/knowledge.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/plugins/__init__.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/plugins/autonomous.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/sandbox.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/shadow.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture/watcher.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/dependency_links.txt +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/entry_points.txt +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/requires.txt +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/codesuture.egg-info/top_level.txt +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/setup.cfg +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/setup.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/__init__.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_e2e.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_guard_synthesizer.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_harness.py +0 -0
- {codesuture-0.7.0 → codesuture-0.7.2}/tests/test_harness2.py +0 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# ---------------------------
|
|
2
|
+
# Python
|
|
3
|
+
# ---------------------------
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.pyc
|
|
6
|
+
*.pyo
|
|
7
|
+
*.egg-info/
|
|
8
|
+
dist/
|
|
9
|
+
build/
|
|
10
|
+
|
|
11
|
+
# ---------------------------
|
|
12
|
+
# Virtual environments
|
|
13
|
+
# ---------------------------
|
|
14
|
+
.venv/
|
|
15
|
+
env/
|
|
16
|
+
venv/
|
|
17
|
+
|
|
18
|
+
# ---------------------------
|
|
19
|
+
# IDE / editor
|
|
20
|
+
# ---------------------------
|
|
21
|
+
.vscode/
|
|
22
|
+
.idea/
|
|
23
|
+
*.code-workspace
|
|
24
|
+
|
|
25
|
+
# ---------------------------
|
|
26
|
+
# CodeSuture runtime artifacts
|
|
27
|
+
# ---------------------------
|
|
28
|
+
.codesuture_store/
|
|
29
|
+
.codesuture_cache/
|
|
30
|
+
.livepatch_cache/
|
|
31
|
+
.codesuture_fingerprints
|
|
32
|
+
.models/
|
|
33
|
+
|
|
34
|
+
# ---------------------------
|
|
35
|
+
# Test / CI artifacts
|
|
36
|
+
# ---------------------------
|
|
37
|
+
.pytest_cache/
|
|
38
|
+
htmlcov/
|
|
39
|
+
.coverage
|
|
40
|
+
*.log
|
|
41
|
+
|
|
42
|
+
# ---------------------------
|
|
43
|
+
# Planning & roadmap (local only)
|
|
44
|
+
# ---------------------------
|
|
45
|
+
ROADMAP.md
|
|
46
|
+
implementation_plan.md
|
|
47
|
+
task.md
|
|
48
|
+
walkthrough.md
|
|
49
|
+
|
|
50
|
+
# ---------------------------
|
|
51
|
+
# Old verification suites (deleted)
|
|
52
|
+
# ---------------------------
|
|
53
|
+
v4test/
|
|
@@ -5,6 +5,74 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.7.1] - 2026-05-25
|
|
9
|
+
|
|
10
|
+
### Fixed — Safety Hardening
|
|
11
|
+
- **Index guard no longer returns wrong item:** `index_guard` now returns a
|
|
12
|
+
type-appropriate default value instead of clamping the index to 0 (which
|
|
13
|
+
silently returned `list[0]` — a data corruption bug)
|
|
14
|
+
- **Callable guard no longer replaces unknown functions with `lambda: 0`:**
|
|
15
|
+
Unknown callables are skipped with a warning instead of being silently
|
|
16
|
+
replaced with a stub that could bypass validation logic
|
|
17
|
+
- **Exception tracebacks shown by default:** `_codesuture_excepthook` now
|
|
18
|
+
prints a structured summary of self-healed exceptions instead of
|
|
19
|
+
suppressing all output; use `--silent` to restore old behavior
|
|
20
|
+
- **Rollback restores runtime code:** `codesuture rollback` now restores
|
|
21
|
+
original bytecode in memory (not just deletes files); original code
|
|
22
|
+
objects are backed up before patching and saved as `.orig.code` files
|
|
23
|
+
- **Marshal integrity checks:** Persisted `.code` files now include SHA-256
|
|
24
|
+
checksums in metadata; tampered files are refused on load
|
|
25
|
+
- **Thread safety:** Added locking on `HEALED_FUNCTIONS`,
|
|
26
|
+
`ANNOUNCED_HEALED_FUNCTIONS`, `_retried_exc_types`, fingerprint registry,
|
|
27
|
+
and `CodeSutureMetaFinder` recursion guard (now uses `threading.local()`)
|
|
28
|
+
- **`_INLINE_STRATEGIES` set populated:** Try-block detection for inline
|
|
29
|
+
strategies (`subscript_guard`, `key_guard`, `division_guard`,
|
|
30
|
+
`chain_subscript_guard`) is now active — previously disabled by empty set
|
|
31
|
+
- **Safe tuple propagation:** Replaced dangerous `ctypes` memory write into
|
|
32
|
+
tuple `ob_item` array with safe `code.replace(co_consts=...)` approach
|
|
33
|
+
|
|
34
|
+
### Added — CPython 3.12+ Compatibility
|
|
35
|
+
- **`codesuture/opcodes.py`:** Version-aware opcode abstraction layer
|
|
36
|
+
providing correct opcode names, instruction builders, and opcode name
|
|
37
|
+
sets for Python 3.11, 3.12, and 3.13+
|
|
38
|
+
- All guard builders now use `opcodes.py` instead of hardcoded 3.11-only
|
|
39
|
+
opcode names (`PRECALL`, `POP_JUMP_FORWARD_IF_FALSE`, `LOAD_METHOD`)
|
|
40
|
+
- Pattern matcher uses opcode sets for resilient bytecode analysis across
|
|
41
|
+
Python versions
|
|
42
|
+
- Frame rewind offset detection is now runtime-detected instead of
|
|
43
|
+
hardcoded to CPython 3.11 struct layout (`id(frame) + 40`)
|
|
44
|
+
- CLI: added `--silent` flag to `codesuture run`
|
|
45
|
+
|
|
46
|
+
### Changed
|
|
47
|
+
- Removed old test files, v4test/ verification suite, and heavily-mocked
|
|
48
|
+
tracer test; replaced with comprehensive new test suite
|
|
49
|
+
|
|
50
|
+
### Fixed — Audit Round (Post-Release)
|
|
51
|
+
- **`_eval_fix.py`:** Fixed `ImportError` — was importing `apply_fix_with_info`
|
|
52
|
+
which doesn't exist; changed to `apply_fix`
|
|
53
|
+
- **`_build_subscript_guarded_code`:** No longer calls `.get()` on lists/tuples;
|
|
54
|
+
now checks `isinstance(container, dict)` before using `.get()`, falls back to
|
|
55
|
+
direct subscript for non-dict containers
|
|
56
|
+
- **`rollback_runtime`:** Removed dead `gc.get_referrers` loop that had only `pass`
|
|
57
|
+
in its body
|
|
58
|
+
- **`middleware.py`:** Retry tracking now uses `(exc_type, filename, func_name)`
|
|
59
|
+
tuple instead of bare exception type — prevents second crash of same type in
|
|
60
|
+
different function from being silently ignored
|
|
61
|
+
- **`persistence.py`:** Thread name now uses `threading.current_thread().name`
|
|
62
|
+
instead of hardcoded `"MainThread"`; `datetime.utcnow()` replaced with
|
|
63
|
+
`datetime.now(timezone.utc)` (Python 3.12 deprecation fix);
|
|
64
|
+
`_iter_cached_function_names` now excludes `.orig.code` files
|
|
65
|
+
- **`audit.py`:** Unicode box-drawing characters now actually used when terminal
|
|
66
|
+
supports them (was identical ASCII `|` on both branches);
|
|
67
|
+
`datetime.utcnow()` deprecation fixed
|
|
68
|
+
- **`rollback.py`:** `datetime.utcnow()` replaced with timezone-aware datetime
|
|
69
|
+
(was causing `TypeError` when comparing with timezone-aware `patched_at` values)
|
|
70
|
+
- **`tracer.py`:** `--silent` flag now gates ALL 20+ informational print statements
|
|
71
|
+
(fingerprint hits, patch applied, already healed, etc.); errors/warnings still
|
|
72
|
+
always print
|
|
73
|
+
- **`opcodes.py`:** Added Python 3.13 compatibility documentation; expanded
|
|
74
|
+
`SUBSCRIPT_OPCODES` to include `BINARY_SLICE` for 3.13 slice operations
|
|
75
|
+
|
|
8
76
|
## [0.7.0] - 2026-05-17
|
|
9
77
|
|
|
10
78
|
### Added
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: codesuture
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: Runtime Python bytecode patcher with guard knowledge base, persistence, and self-healing re-execution
|
|
5
5
|
License-Expression: MIT
|
|
6
6
|
Project-URL: Source, https://github.com/codesuture/codesuture
|
|
@@ -11,6 +11,7 @@ Classifier: Topic :: Software Development :: Debuggers
|
|
|
11
11
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
15
|
Requires-Python: >=3.11
|
|
15
16
|
Description-Content-Type: text/markdown
|
|
16
17
|
License-File: LICENSE
|
|
@@ -21,7 +22,14 @@ Dynamic: license-file
|
|
|
21
22
|
|
|
22
23
|
# CodeSuture
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+

|
|
26
|
+
|
|
27
|
+
**Runtime guard synthesis for CPython 3.11+. Catches structural crashes, patches live bytecode, keeps your server running.**
|
|
28
|
+
|
|
29
|
+
[]()
|
|
30
|
+
[]()
|
|
31
|
+
[](LICENSE)
|
|
32
|
+
[]()
|
|
25
33
|
|
|
26
34
|
```
|
|
27
35
|
pip install codesuture
|
|
@@ -97,11 +105,11 @@ No 500. No traceback. Server process intact.
|
|
|
97
105
|
|
|
98
106
|
## How it works
|
|
99
107
|
|
|
100
|
-
1. **Catch** — `sys.settrace` intercepts exceptions at the exact frame and bytecode offset.
|
|
108
|
+
1. **Catch** — `sys.settrace` (3.11) or `sys.monitoring` (3.12+) intercepts exceptions at the exact frame and bytecode offset.
|
|
101
109
|
2. **Analyze** — The pattern matcher walks the instruction chain and identifies the failing variable or operation.
|
|
102
110
|
3. **Patch** — The guard synthesizer injects new bytecode into the function's code object. A semantic diff gate rejects patches that change too much logic.
|
|
103
|
-
4. **Rewind** — Execution restarts from the patched function. The guard prevents recurrence.
|
|
104
|
-
5. **Persist** — The patched code object is serialized to `.codesuture_store/` with JSON metadata. Subsequent runs load it before the first call.
|
|
111
|
+
4. **Rewind** — Execution restarts from the patched function via `f_lineno` setter. The guard prevents recurrence.
|
|
112
|
+
5. **Persist** — The patched code object is serialized to `.codesuture_store/` with SHA-256 integrity checks and JSON metadata. Subsequent runs load it before the first call.
|
|
105
113
|
|
|
106
114
|
No source files are modified.
|
|
107
115
|
|
|
@@ -121,6 +129,7 @@ No source files are modified.
|
|
|
121
129
|
| `str_coerce_guard` | `TypeError` on string concat | `"age: " + 25` |
|
|
122
130
|
| `file_guard` | `FileNotFoundError` | `open(path)` when file is missing |
|
|
123
131
|
| `callable_guard` | `TypeError` calling `None` | `func()` when `func` is `None` |
|
|
132
|
+
| `return_guard` | `TypeError` on `None` return | Downstream use of a `None` return value |
|
|
124
133
|
|
|
125
134
|
---
|
|
126
135
|
|
|
@@ -129,16 +138,17 @@ No source files are modified.
|
|
|
129
138
|
| Command | Flags | What it does |
|
|
130
139
|
|---|---|---|
|
|
131
140
|
| `codesuture run <script>` | | Run with live patching |
|
|
132
|
-
|
|
|
133
|
-
|
|
|
134
|
-
|
|
|
135
|
-
|
|
|
136
|
-
|
|
|
141
|
+
| | `--verbose` | Show patch diffs and instruction deltas |
|
|
142
|
+
| | `--shadow` | Warn when patched functions return sentinel values |
|
|
143
|
+
| | `--dry-run` | Preview patches without applying |
|
|
144
|
+
| | `--silent` | Suppress all informational output |
|
|
145
|
+
| | `--ttl DAYS` | Set patch expiry (default: 7 days) |
|
|
146
|
+
| | `--retries N` | Max re-execution attempts (default: 3) |
|
|
137
147
|
| `codesuture watch <script>` | `--max-restarts N` | Run continuously, restart after each patch |
|
|
138
148
|
| `codesuture audit` | | Show all active patches in a table |
|
|
139
149
|
| `codesuture explain` | | Plain-language breakdown of every patch |
|
|
140
150
|
| `codesuture explain <name>` | | Explain one function's patch |
|
|
141
|
-
| `codesuture rollback <name>` | | Remove one persisted patch |
|
|
151
|
+
| `codesuture rollback <name>` | | Remove one persisted patch and restore runtime code |
|
|
142
152
|
| `codesuture rollback` | `--all` | Remove all patches and fingerprint registry |
|
|
143
153
|
| `codesuture rollback` | `--dry-run` | Preview what would be removed |
|
|
144
154
|
|
|
@@ -156,15 +166,6 @@ Every patched response carries:
|
|
|
156
166
|
X-CodeSuture: patched=1; guard=<type>; target=<variable>
|
|
157
167
|
```
|
|
158
168
|
|
|
159
|
-
Four crash types, one server, all returning 200:
|
|
160
|
-
|
|
161
|
-
```
|
|
162
|
-
"GET /user-data HTTP/1.1" 200 ← null_guard on None object
|
|
163
|
-
"GET /config HTTP/1.1" 200 ← key_guard on missing key
|
|
164
|
-
"GET /process-payment HTTP/1.1" 200 ← type_coercion_guard on bad input
|
|
165
|
-
"GET /latest-user HTTP/1.1" 200 ← chain_subscript_guard on out-of-bounds
|
|
166
|
-
```
|
|
167
|
-
|
|
168
169
|
### WSGI middleware
|
|
169
170
|
|
|
170
171
|
```python
|
|
@@ -175,19 +176,15 @@ app = CodeSutureMiddleware(wsgi_app)
|
|
|
175
176
|
|
|
176
177
|
---
|
|
177
178
|
|
|
178
|
-
##
|
|
179
|
-
|
|
180
|
-
**Semantic diff gate** — Patches that modify too many instructions for the guard type are automatically rejected. The engine never corrupts a complex function to patch a simple crash.
|
|
181
|
-
|
|
182
|
-
**Caller-aware propagation** — After patching, CodeSuture uses `gc.get_referrers` to update every live reference to the original code object: closures, bound methods, partials. No stale copy survives.
|
|
183
|
-
|
|
184
|
-
**Shadow execution mode** — `--shadow` monitors return values of patched functions. If a sentinel default leaks into downstream logic, a warning fires before it causes a second failure.
|
|
185
|
-
|
|
186
|
-
**Patch expiry** — Every persisted patch carries a TTL. When it ages past the limit, CodeSuture logs a reminder to fix the root cause in source. Patches are scaffolding, not permanent fixes.
|
|
187
|
-
|
|
188
|
-
**Bytecode fingerprint registry** — Crash sites are hashed by their surrounding instruction window. Repeat patterns get instant cached guard application without re-analysis.
|
|
179
|
+
## Safety features
|
|
189
180
|
|
|
190
|
-
**
|
|
181
|
+
- **Semantic diff gate** — Patches that modify too many instructions are rejected. The engine never corrupts a complex function to fix a simple crash.
|
|
182
|
+
- **SHA-256 integrity** — Persisted patches are checksummed. Tampered `.code` files are refused on load.
|
|
183
|
+
- **Caller-aware propagation** — After patching, `gc.get_referrers` updates every live reference: closures, bound methods, partials. No stale copy survives.
|
|
184
|
+
- **Patch validation** — Synthesized bytecode is checked for `LOAD_FAST` references to variables not in `co_varnames`. Invalid patches are rejected before application.
|
|
185
|
+
- **Patch expiry (TTL)** — Every patch carries a time-to-live. Aged patches trigger a warning to fix the root cause in source.
|
|
186
|
+
- **Thread safety** — All shared state (fingerprint registry, persistence store, healed function sets) is protected by locks.
|
|
187
|
+
- **Rollback** — `codesuture rollback` removes persisted files AND restores original code in the running process.
|
|
191
188
|
|
|
192
189
|
---
|
|
193
190
|
|
|
@@ -197,38 +194,24 @@ app = CodeSutureMiddleware(wsgi_app)
|
|
|
197
194
|
|
|
198
195
|
**Not a static analyzer.** It operates at runtime on live bytecode, not on source.
|
|
199
196
|
|
|
200
|
-
**Not autonomous.**
|
|
197
|
+
**Not autonomous by default.** All patches are deterministic rule-based guards. An opt-in `--autonomous` flag exists for experimental LLM-powered suggestions via local models, but it is off by default and never auto-applies fixes.
|
|
201
198
|
|
|
202
199
|
---
|
|
203
200
|
|
|
204
|
-
##
|
|
201
|
+
## Known limitations
|
|
205
202
|
|
|
206
|
-
**Python 3.11+ only.**
|
|
207
|
-
|
|
208
|
-
**Comprehensions are not patchable.** List
|
|
209
|
-
|
|
210
|
-
**
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
**Async support is experimental.** Standard `async def` functions are patched. Async generators and deeply nested `await` chains may not be handled correctly in all cases.
|
|
215
|
-
|
|
216
|
-
**HTTP recovery covers simple server paths.** Validated against `http.server` and `socketserver`. Full ASGI framework support is in progress.
|
|
217
|
-
|
|
218
|
-
---
|
|
219
|
-
|
|
220
|
-
## Roadmap
|
|
221
|
-
|
|
222
|
-
Tracked in `ROADMAP.md`. v1.0 themes:
|
|
223
|
-
|
|
224
|
-
- `sys.monitoring` as the default engine on Python 3.12+ (zero line-tracing overhead on hot paths)
|
|
225
|
-
- Stronger transaction recovery boundaries across web frameworks
|
|
226
|
-
- Verified source-level repair proposals via local LLM
|
|
227
|
-
- Fleet governance, audit lifecycle, and incident export
|
|
228
|
-
- Language-neutral incident protocol for future polyglot adapters
|
|
203
|
+
- **Python 3.11+ only.** Depends on CPython bytecode structures introduced in 3.11.
|
|
204
|
+
- **3.12+ frame rewind.** The Python-level `f_lineno` setter is used on 3.12+. The ctypes fallback is disabled on 3.12+ to prevent memory corruption from struct layout changes.
|
|
205
|
+
- **Comprehensions are not patchable.** List/dict/set/generator comprehensions are anonymous nested code objects. CodeSuture logs a warning and skips them.
|
|
206
|
+
- **Semantic bugs are out of scope.** CodeSuture fixes structural crashes — null access, missing keys, type mismatches, bounds errors. Logic errors that produce wrong results without crashing cannot be detected.
|
|
207
|
+
- **Single-process scope.** Patches apply per-process. `.codesuture_store/` is shared on disk, so patches persist across restarts.
|
|
208
|
+
- **Async support is experimental.** Standard `async def` functions are patched. Async generators and deeply nested `await` chains may not be handled correctly.
|
|
209
|
+
- **HTTP recovery is validated against `http.server`.** Full ASGI framework support is not yet implemented.
|
|
229
210
|
|
|
230
211
|
---
|
|
231
212
|
|
|
232
213
|
## License
|
|
233
214
|
|
|
234
|
-
MIT. See
|
|
215
|
+
MIT. See [LICENSE](LICENSE) for details.
|
|
216
|
+
|
|
217
|
+
For a detailed history of changes, see the [Changelog](CHANGELOG.md).
|
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
# CodeSuture
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+

|
|
4
|
+
|
|
5
|
+
**Runtime guard synthesis for CPython 3.11+. Catches structural crashes, patches live bytecode, keeps your server running.**
|
|
6
|
+
|
|
7
|
+
[]()
|
|
8
|
+
[]()
|
|
9
|
+
[](LICENSE)
|
|
10
|
+
[]()
|
|
4
11
|
|
|
5
12
|
```
|
|
6
13
|
pip install codesuture
|
|
@@ -76,11 +83,11 @@ No 500. No traceback. Server process intact.
|
|
|
76
83
|
|
|
77
84
|
## How it works
|
|
78
85
|
|
|
79
|
-
1. **Catch** — `sys.settrace` intercepts exceptions at the exact frame and bytecode offset.
|
|
86
|
+
1. **Catch** — `sys.settrace` (3.11) or `sys.monitoring` (3.12+) intercepts exceptions at the exact frame and bytecode offset.
|
|
80
87
|
2. **Analyze** — The pattern matcher walks the instruction chain and identifies the failing variable or operation.
|
|
81
88
|
3. **Patch** — The guard synthesizer injects new bytecode into the function's code object. A semantic diff gate rejects patches that change too much logic.
|
|
82
|
-
4. **Rewind** — Execution restarts from the patched function. The guard prevents recurrence.
|
|
83
|
-
5. **Persist** — The patched code object is serialized to `.codesuture_store/` with JSON metadata. Subsequent runs load it before the first call.
|
|
89
|
+
4. **Rewind** — Execution restarts from the patched function via `f_lineno` setter. The guard prevents recurrence.
|
|
90
|
+
5. **Persist** — The patched code object is serialized to `.codesuture_store/` with SHA-256 integrity checks and JSON metadata. Subsequent runs load it before the first call.
|
|
84
91
|
|
|
85
92
|
No source files are modified.
|
|
86
93
|
|
|
@@ -100,6 +107,7 @@ No source files are modified.
|
|
|
100
107
|
| `str_coerce_guard` | `TypeError` on string concat | `"age: " + 25` |
|
|
101
108
|
| `file_guard` | `FileNotFoundError` | `open(path)` when file is missing |
|
|
102
109
|
| `callable_guard` | `TypeError` calling `None` | `func()` when `func` is `None` |
|
|
110
|
+
| `return_guard` | `TypeError` on `None` return | Downstream use of a `None` return value |
|
|
103
111
|
|
|
104
112
|
---
|
|
105
113
|
|
|
@@ -108,16 +116,17 @@ No source files are modified.
|
|
|
108
116
|
| Command | Flags | What it does |
|
|
109
117
|
|---|---|---|
|
|
110
118
|
| `codesuture run <script>` | | Run with live patching |
|
|
111
|
-
|
|
|
112
|
-
|
|
|
113
|
-
|
|
|
114
|
-
|
|
|
115
|
-
|
|
|
119
|
+
| | `--verbose` | Show patch diffs and instruction deltas |
|
|
120
|
+
| | `--shadow` | Warn when patched functions return sentinel values |
|
|
121
|
+
| | `--dry-run` | Preview patches without applying |
|
|
122
|
+
| | `--silent` | Suppress all informational output |
|
|
123
|
+
| | `--ttl DAYS` | Set patch expiry (default: 7 days) |
|
|
124
|
+
| | `--retries N` | Max re-execution attempts (default: 3) |
|
|
116
125
|
| `codesuture watch <script>` | `--max-restarts N` | Run continuously, restart after each patch |
|
|
117
126
|
| `codesuture audit` | | Show all active patches in a table |
|
|
118
127
|
| `codesuture explain` | | Plain-language breakdown of every patch |
|
|
119
128
|
| `codesuture explain <name>` | | Explain one function's patch |
|
|
120
|
-
| `codesuture rollback <name>` | | Remove one persisted patch |
|
|
129
|
+
| `codesuture rollback <name>` | | Remove one persisted patch and restore runtime code |
|
|
121
130
|
| `codesuture rollback` | `--all` | Remove all patches and fingerprint registry |
|
|
122
131
|
| `codesuture rollback` | `--dry-run` | Preview what would be removed |
|
|
123
132
|
|
|
@@ -135,15 +144,6 @@ Every patched response carries:
|
|
|
135
144
|
X-CodeSuture: patched=1; guard=<type>; target=<variable>
|
|
136
145
|
```
|
|
137
146
|
|
|
138
|
-
Four crash types, one server, all returning 200:
|
|
139
|
-
|
|
140
|
-
```
|
|
141
|
-
"GET /user-data HTTP/1.1" 200 ← null_guard on None object
|
|
142
|
-
"GET /config HTTP/1.1" 200 ← key_guard on missing key
|
|
143
|
-
"GET /process-payment HTTP/1.1" 200 ← type_coercion_guard on bad input
|
|
144
|
-
"GET /latest-user HTTP/1.1" 200 ← chain_subscript_guard on out-of-bounds
|
|
145
|
-
```
|
|
146
|
-
|
|
147
147
|
### WSGI middleware
|
|
148
148
|
|
|
149
149
|
```python
|
|
@@ -154,19 +154,15 @@ app = CodeSutureMiddleware(wsgi_app)
|
|
|
154
154
|
|
|
155
155
|
---
|
|
156
156
|
|
|
157
|
-
##
|
|
158
|
-
|
|
159
|
-
**Semantic diff gate** — Patches that modify too many instructions for the guard type are automatically rejected. The engine never corrupts a complex function to patch a simple crash.
|
|
160
|
-
|
|
161
|
-
**Caller-aware propagation** — After patching, CodeSuture uses `gc.get_referrers` to update every live reference to the original code object: closures, bound methods, partials. No stale copy survives.
|
|
162
|
-
|
|
163
|
-
**Shadow execution mode** — `--shadow` monitors return values of patched functions. If a sentinel default leaks into downstream logic, a warning fires before it causes a second failure.
|
|
164
|
-
|
|
165
|
-
**Patch expiry** — Every persisted patch carries a TTL. When it ages past the limit, CodeSuture logs a reminder to fix the root cause in source. Patches are scaffolding, not permanent fixes.
|
|
166
|
-
|
|
167
|
-
**Bytecode fingerprint registry** — Crash sites are hashed by their surrounding instruction window. Repeat patterns get instant cached guard application without re-analysis.
|
|
157
|
+
## Safety features
|
|
168
158
|
|
|
169
|
-
**
|
|
159
|
+
- **Semantic diff gate** — Patches that modify too many instructions are rejected. The engine never corrupts a complex function to fix a simple crash.
|
|
160
|
+
- **SHA-256 integrity** — Persisted patches are checksummed. Tampered `.code` files are refused on load.
|
|
161
|
+
- **Caller-aware propagation** — After patching, `gc.get_referrers` updates every live reference: closures, bound methods, partials. No stale copy survives.
|
|
162
|
+
- **Patch validation** — Synthesized bytecode is checked for `LOAD_FAST` references to variables not in `co_varnames`. Invalid patches are rejected before application.
|
|
163
|
+
- **Patch expiry (TTL)** — Every patch carries a time-to-live. Aged patches trigger a warning to fix the root cause in source.
|
|
164
|
+
- **Thread safety** — All shared state (fingerprint registry, persistence store, healed function sets) is protected by locks.
|
|
165
|
+
- **Rollback** — `codesuture rollback` removes persisted files AND restores original code in the running process.
|
|
170
166
|
|
|
171
167
|
---
|
|
172
168
|
|
|
@@ -176,38 +172,24 @@ app = CodeSutureMiddleware(wsgi_app)
|
|
|
176
172
|
|
|
177
173
|
**Not a static analyzer.** It operates at runtime on live bytecode, not on source.
|
|
178
174
|
|
|
179
|
-
**Not autonomous.**
|
|
175
|
+
**Not autonomous by default.** All patches are deterministic rule-based guards. An opt-in `--autonomous` flag exists for experimental LLM-powered suggestions via local models, but it is off by default and never auto-applies fixes.
|
|
180
176
|
|
|
181
177
|
---
|
|
182
178
|
|
|
183
|
-
##
|
|
179
|
+
## Known limitations
|
|
184
180
|
|
|
185
|
-
**Python 3.11+ only.**
|
|
186
|
-
|
|
187
|
-
**Comprehensions are not patchable.** List
|
|
188
|
-
|
|
189
|
-
**
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
**Async support is experimental.** Standard `async def` functions are patched. Async generators and deeply nested `await` chains may not be handled correctly in all cases.
|
|
194
|
-
|
|
195
|
-
**HTTP recovery covers simple server paths.** Validated against `http.server` and `socketserver`. Full ASGI framework support is in progress.
|
|
196
|
-
|
|
197
|
-
---
|
|
198
|
-
|
|
199
|
-
## Roadmap
|
|
200
|
-
|
|
201
|
-
Tracked in `ROADMAP.md`. v1.0 themes:
|
|
202
|
-
|
|
203
|
-
- `sys.monitoring` as the default engine on Python 3.12+ (zero line-tracing overhead on hot paths)
|
|
204
|
-
- Stronger transaction recovery boundaries across web frameworks
|
|
205
|
-
- Verified source-level repair proposals via local LLM
|
|
206
|
-
- Fleet governance, audit lifecycle, and incident export
|
|
207
|
-
- Language-neutral incident protocol for future polyglot adapters
|
|
181
|
+
- **Python 3.11+ only.** Depends on CPython bytecode structures introduced in 3.11.
|
|
182
|
+
- **3.12+ frame rewind.** The Python-level `f_lineno` setter is used on 3.12+. The ctypes fallback is disabled on 3.12+ to prevent memory corruption from struct layout changes.
|
|
183
|
+
- **Comprehensions are not patchable.** List/dict/set/generator comprehensions are anonymous nested code objects. CodeSuture logs a warning and skips them.
|
|
184
|
+
- **Semantic bugs are out of scope.** CodeSuture fixes structural crashes — null access, missing keys, type mismatches, bounds errors. Logic errors that produce wrong results without crashing cannot be detected.
|
|
185
|
+
- **Single-process scope.** Patches apply per-process. `.codesuture_store/` is shared on disk, so patches persist across restarts.
|
|
186
|
+
- **Async support is experimental.** Standard `async def` functions are patched. Async generators and deeply nested `await` chains may not be handled correctly.
|
|
187
|
+
- **HTTP recovery is validated against `http.server`.** Full ASGI framework support is not yet implemented.
|
|
208
188
|
|
|
209
189
|
---
|
|
210
190
|
|
|
211
191
|
## License
|
|
212
192
|
|
|
213
|
-
MIT. See
|
|
193
|
+
MIT. See [LICENSE](LICENSE) for details.
|
|
194
|
+
|
|
195
|
+
For a detailed history of changes, see the [Changelog](CHANGELOG.md).
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.7.2"
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import json
|
|
3
3
|
import sys
|
|
4
|
-
from datetime import datetime
|
|
4
|
+
from datetime import datetime, timezone
|
|
5
5
|
|
|
6
6
|
def run_audit(patch_store_path: str = None):
|
|
7
7
|
|
|
@@ -26,7 +26,7 @@ def run_audit(patch_store_path: str = None):
|
|
|
26
26
|
print("[CodeSuture] Patch store exists but is empty.")
|
|
27
27
|
return
|
|
28
28
|
|
|
29
|
-
now = datetime.
|
|
29
|
+
now = datetime.now(timezone.utc)
|
|
30
30
|
|
|
31
31
|
col_func = max(18, max(len(p.get("func_name","?")) for p in patches) + 2)
|
|
32
32
|
col_guard = 12
|
|
@@ -35,23 +35,20 @@ def run_audit(patch_store_path: str = None):
|
|
|
35
35
|
col_age = 7
|
|
36
36
|
|
|
37
37
|
try:
|
|
38
|
-
"
|
|
38
|
+
"│".encode(sys.stdout.encoding or 'ascii')
|
|
39
39
|
HAS_UNICODE = True
|
|
40
40
|
except Exception:
|
|
41
41
|
HAS_UNICODE = False
|
|
42
42
|
|
|
43
43
|
def row(f, g, t, d, a):
|
|
44
|
-
v = "
|
|
44
|
+
v = "│" if HAS_UNICODE else "|"
|
|
45
45
|
return (f"{v} {f:<{col_func}} {v} {g:<{col_guard}} {v} "
|
|
46
46
|
f"{t:<{col_target}} {v} {d:<{col_default}} {v} {a:<{col_age}} {v}")
|
|
47
47
|
|
|
48
48
|
if HAS_UNICODE:
|
|
49
|
-
sep =
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+++")
|
|
53
|
-
bot = (f"+-{'-'*col_func}-+-{'-'*col_guard}-+-"
|
|
54
|
-
f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+")
|
|
49
|
+
sep = f"├─{'─'*col_func}─┼─{'─'*col_guard}─┼─{'─'*col_target}─┼─{'─'*col_default}─┼─{'─'*col_age}─┤"
|
|
50
|
+
top = f"┌─{'─'*col_func}─┬─{'─'*col_guard}─┬─{'─'*col_target}─┬─{'─'*col_default}─┬─{'─'*col_age}─┐"
|
|
51
|
+
bot = f"└─{'─'*col_func}─┴─{'─'*col_guard}─┴─{'─'*col_target}─┴─{'─'*col_default}─┴─{'─'*col_age}─┘"
|
|
55
52
|
else:
|
|
56
53
|
sep = (f"|-{'*'*col_func}-+-{'*'*col_guard}-+-"
|
|
57
54
|
f"{'*'*col_target}-+-{'*'*col_default}-+-{'*'*col_age}-|")
|
|
@@ -18,6 +18,8 @@ def main():
|
|
|
18
18
|
run_parser.add_argument('--verbose', action='store_true', help='Show detailed debug output')
|
|
19
19
|
run_parser.add_argument('--shadow', action='store_true', help='Warn if patched functions return sentinel values')
|
|
20
20
|
run_parser.add_argument('--ttl', type=int, default=7, metavar='DAYS', help='Patch TTL in days (default: 7)')
|
|
21
|
+
run_parser.add_argument('--silent', action='store_true',
|
|
22
|
+
help='Suppress exception output for healed crashes (default: show summary)')
|
|
21
23
|
|
|
22
24
|
sub.add_parser('audit', help='Show all active patches')
|
|
23
25
|
|
|
@@ -96,7 +98,8 @@ def main():
|
|
|
96
98
|
max_retries=args.retries,
|
|
97
99
|
autonomous=getattr(args, 'autonomous', False),
|
|
98
100
|
script_path=args.script, verbose=args.verbose,
|
|
99
|
-
shadow=args.shadow, ttl=args.ttl
|
|
101
|
+
shadow=args.shadow, ttl=args.ttl,
|
|
102
|
+
silent=args.silent)
|
|
100
103
|
max_runs = args.retries + 1
|
|
101
104
|
for run in range(max_runs):
|
|
102
105
|
patched_before = tracer.stats['patched']
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
import os
|
|
3
3
|
import json
|
|
4
4
|
import sys
|
|
5
|
-
from datetime import datetime
|
|
5
|
+
from datetime import datetime, timezone
|
|
6
6
|
|
|
7
7
|
def run_explain(func_name=None):
|
|
8
8
|
|
|
@@ -32,10 +32,10 @@ def run_explain(func_name=None):
|
|
|
32
32
|
print(f"[CodeSuture] No patches found for '{func_name}'.")
|
|
33
33
|
return
|
|
34
34
|
|
|
35
|
-
now = datetime.
|
|
35
|
+
now = datetime.now(timezone.utc)
|
|
36
36
|
|
|
37
37
|
try:
|
|
38
|
-
"
|
|
38
|
+
"│".encode(sys.stdout.encoding or "ascii")
|
|
39
39
|
HAS_UNICODE = True
|
|
40
40
|
except Exception:
|
|
41
41
|
HAS_UNICODE = False
|
|
@@ -47,7 +47,7 @@ def run_explain(func_name=None):
|
|
|
47
47
|
col_age = 12
|
|
48
48
|
col_safe = 9
|
|
49
49
|
|
|
50
|
-
v = "
|
|
50
|
+
v = "│" if HAS_UNICODE else "|"
|
|
51
51
|
|
|
52
52
|
def row(f, g, t, d, a, s):
|
|
53
53
|
return (f"{v} {f:<{col_func}} {v} {g:<{col_guard}} {v} "
|
|
@@ -55,12 +55,12 @@ def run_explain(func_name=None):
|
|
|
55
55
|
f"{a:<{col_age}} {v} {s:<{col_safe}} {v}")
|
|
56
56
|
|
|
57
57
|
if HAS_UNICODE:
|
|
58
|
-
sep = (f"
|
|
59
|
-
f"{'
|
|
60
|
-
top = (f"
|
|
61
|
-
f"{'
|
|
62
|
-
bot = (f"
|
|
63
|
-
f"{'
|
|
58
|
+
sep = (f"├─{'─'*col_func}─┼─{'─'*col_guard}─┼─"
|
|
59
|
+
f"{'─'*col_target}─┼─{'─'*col_default}─┼─{'─'*col_age}─┼─{'─'*col_safe}─┤")
|
|
60
|
+
top = (f"┌─{'─'*col_func}─┬─{'─'*col_guard}─┬─"
|
|
61
|
+
f"{'─'*col_target}─┬─{'─'*col_default}─┬─{'─'*col_age}─┬─{'─'*col_safe}─┐")
|
|
62
|
+
bot = (f"└─{'─'*col_func}─┴─{'─'*col_guard}─┴─"
|
|
63
|
+
f"{'─'*col_target}─┴─{'─'*col_default}─┴─{'─'*col_age}─┴─{'─'*col_safe}─┘")
|
|
64
64
|
else:
|
|
65
65
|
sep = (f"+-{'-'*col_func}-+-{'-'*col_guard}-+-"
|
|
66
66
|
f"{'-'*col_target}-+-{'-'*col_default}-+-{'-'*col_age}-+-{'-'*col_safe}-+")
|