Crossing
Detect silent information loss at system boundaries in Python codebases.
Two Tools
1. Semantic Scanner — Exception Pattern Analysis
Find where the same exception type carries different meanings depending on the code path, but handlers can't distinguish them.
# Basic scan
crossing-semantic /path/to/project
# With implicit raises (dict access, getattr, etc.)
crossing-semantic --implicit /path/to/project
# JSON output for tooling
crossing-semantic --format json /path/to/project
# CI mode: fail if elevated/high risk crossings found
crossing-semantic --ci --min-risk elevated /path/to/project
Example: a KeyError that means "config key missing" and a KeyError that means "factor-filtered to empty" arrive at the same except KeyError handler. The handler assumes one meaning. The bug is silent.
2. Data Loss Fuzzer — Round-Trip Testing
Test whether information survives boundary crossings: serialization, API calls, database writes, format conversions.
from crossing import Crossing, cross
c = Crossing(
encode=lambda d: json.dumps(d),
decode=lambda s: json.loads(s),
)
report = cross(c, samples=1000)
report.print() # shows what was lost, where, and how
This isn't fuzzing for crashes. It's fuzzing for silent data loss — the operation succeeds but the output is missing something the input had.
Semantic Scanner
What It Finds
- Polymorphic exceptions: Multiple
raisesites for the same exception type, caught by handlers that don't distinguish between them - Cross-function crossings: Exceptions raised in called functions, caught by handlers in the caller
- Cross-file crossings: Same pattern across module boundaries via import resolution
- Implicit raises:
dict[key]->KeyError,getattr(obj, name)->AttributeError,int(x)->ValueError - Inheritance crossings:
except ValueErrorcatching subclass raises likeValidationError - Scope analysis: Whether handlers catch exceptions from direct raises or from called functions
- Message differentiation: Risk downgraded when all raise sites pass distinct string messages
Risk Levels
| Level | Meaning |
|---|---|
| low | Single raise site, or polymorphic with matching handler strategies |
| medium | Multiple raise sites with uniform handler treatment |
| elevated | Scope mismatches or cross-function reachability |
| high | Many raise sites, few handlers, mixed implicit/explicit |
CLI Options
crossing-semantic [OPTIONS] PATH
Options:
--implicit Detect implicit raises (dict access, getattr, etc.)
--format FORMAT Output format: text (default), json, markdown
--min-risk LEVEL Minimum risk to report: low, medium, elevated, high
--exclude PATTERN Exclude directories (repeatable)
--ci Exit code 1 if elevated/high risk crossings found
Example Output
============================================================
Semantic Crossing Scan: /path/to/tox
============================================================
Files scanned: 42
Exception raises: 87 (58 explicit, 29 implicit)
Exception handlers: 34
Semantic crossings: 12
Polymorphic (multi-raise): 8
Elevated risk: 3
--- KeyError: 3 raise sites, 14 handlers --- high risk ---
3 raise sites across different loaders (API, TOML, INI),
14 handlers catching without distinguishing source
============================================================
Information-Theoretic Scoring
Each crossing reports quantitative metrics based on Shannon entropy:
| Metric | What it measures |
|---|---|
| Semantic entropy | Bits of information carried by the exception type at raise sites (log2 of distinct origins) |
| Handler discrimination | Bits preserved by handlers (re-raise = full, return/pass = zero) |
| Information loss | Bits destroyed: entropy minus discrimination |
| Collapse ratio | Normalized loss: 0% (no collapse) to 100% (total meaning erasure) |
--- AttributeError: 4 raise sites, 3 handlers — high risk ---
Information: 2.0 bits entropy, 0.3 bits lost, 83% collapse
In JSON output, each crossing includes an information_theory object, and the summary includes total_information_loss_bits and mean_collapse_ratio across all crossings.
Real Bugs Found
The semantic scanner has identified real bugs in production codebases:
- tox #3809:
KeyErrormeaning "factor-filtered to empty" caught by handler expecting "key doesn't exist" - Rich #3960: Exception
__notes__leaking across chained exceptions - pytest #14214: Verbosity config not propagated across internal call boundary
Data Loss Fuzzer
Built-in Crossings
| Crossing | What it tests | Typical loss rate |
|---|---|---|
json_crossing() |
JSON with default=str |
~24% lossy, 34% crashes |
json_crossing_strict() |
JSON without fallback | ~6% lossy, 52% crashes |
pickle_crossing() |
Python pickle | 0% (lossless baseline) |
yaml_crossing() |
YAML safe_load | ~0% lossy, 49% crashes |
toml_crossing() |
TOML via tomllib/tomli_w | varies |
csv_crossing() |
CSV (everything becomes strings) | ~82% lossy |
env_file_crossing() |
.env files (KEY=VALUE) | ~83% lossy |
url_query_crossing() |
URL query string encoding | ~80% lossy |
Custom Crossings
from crossing import Crossing, cross
# Test your API serialization
c = Crossing(
encode=lambda d: my_api_serialize(d),
decode=lambda s: my_api_deserialize(s),
name="My API boundary",
)
report = cross(c, samples=1000)
report.print()
CLI
# Test a single format
crossing test json -n 500 --seed 42
# Test all built-in formats
crossing test -n 200
# Compare how two formats compose
crossing compose json csv -n 300
# Measure how loss scales with repeated crossings
crossing scale json --max-n 5
# List all available crossings
crossing list
Compose Pipelines
from crossing import compose, json_crossing, string_truncation_crossing, cross
# Simulate: serialize -> store in VARCHAR(100) -> deserialize
pipeline = compose(
json_crossing(),
string_truncation_crossing(100),
)
report = cross(pipeline, samples=500)
Diff
Compare how two boundaries handle the same data:
from crossing import diff, json_crossing, pickle_crossing
report = diff(json_crossing(), pickle_crossing(), samples=500)
print(f"{report.divergent_count} samples differ between JSON and pickle")
Scaling Analysis
Measure how loss rate changes when data passes through N copies of a boundary:
from crossing import scaling, json_crossing
sr = scaling(json_crossing(), max_n=5, samples=200)
# JSON is idempotent: loss happens on first pass, then saturates (exponent ≈ 0)
# Non-idempotent crossings show positive scaling exponents
Triangulation
Compare 3+ formats simultaneously to distinguish inherent data limitations from format-specific losses:
from crossing import triangulate, json_crossing, csv_crossing, env_file_crossing
report = triangulate(
json_crossing(), csv_crossing(), env_file_crossing(),
samples=200, seed=42,
)
report.print()
# Shared losses: inherent to the data (all formats lose it)
# Unique losses: format-specific (only one format loses it)
CLI: crossing triangulate json csv env
Complexity Profiling
Measure how loss rate varies with input complexity:
from crossing import profile, json_crossing
report = profile(json_crossing(), max_depth=6, samples=200, seed=42)
report.print()
# JSON: type diversity drives loss, not nesting depth
# Pickle: 0% loss at all depths
# CSV: lossy even on scalars (63%+)
CLI: crossing profile json
Full Report
Comprehensive analysis combining test, complexity profile, and scaling in one call:
from crossing import full_report, json_crossing
fr = full_report(json_crossing(), samples=200, seed=42)
fr.print()
# Outputs: round-trip test, complexity profile,
# scaling analysis, idempotency check, and verdict
CLI: crossing report json
Codebase Scanning
python3 scan.py /path/to/project
Finds encode/decode pairs for: JSON, YAML, pickle, TOML, base64, URL encoding, CSV, struct, zlib, gzip.
GitHub Action
Add Crossing to your CI pipeline:
# .github/workflows/crossing.yml
name: Exception Analysis
on: [pull_request]
jobs:
crossing:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: worksbyfriday/crossing@main
with:
path: 'src/'
fail-on-risk: 'elevated'
Inputs: path, min-risk, format, implicit, exclude, fail-on-risk.
Benchmarks
Scanned 11 popular Python projects (Feb 2026):
| Project | Files | Crossings | High Risk | Info Loss |
|---|---|---|---|---|
| pydantic | 402 | 119 | 12 | 22.9 bits |
| sqlalchemy | 661 | 103 | 16 | 79.8 bits |
| django | 902 | 80 | 6 | — |
| aiohttp | 166 | 53 | 11 | 25.5 bits |
| click | 62 | 14 | 5 | 7.4 bits |
| celery | 161 | 12 | 3 | — |
| flask | 24 | 6 | 2 | — |
| requests | 18 | 5 | 2 | — |
| rich | 100 | 5 | 1 | — |
| astroid | 96 | 5 | 0 | — |
| fastapi | 47 | 0 | 0 | 0 bits |
FastAPI scoring clean validates the tool. Sample audit reports: SQLAlchemy, Django, Celery, Flask, Requests.
API
Scan any installed Python package via HTTP:
curl https://api.fridayops.xyz/crossing/package/flask
Returns JSON with full crossing analysis, information theory metrics, and risk levels.
Audit report — full markdown report with findings, recommendations, and benchmarks:
curl https://api.fridayops.xyz/crossing/report/flask
Badge — embed in your README:

All endpoints:
POST /crossing— scan raw Python sourceGET /crossing/package/{name}— JSON scan resultsGET /crossing/report/{name}— full markdown audit reportGET /crossing/badge/{name}— SVG badgeGET /crossing/benchmark— comparison data from 17 projectsGET /crossing/packages— list of example packagesGET /crossing/example— demo snippet
Install
pip install crossing
Or copy the files directly — no external dependencies. Python 3.10+.
License
MIT
Metadata
Release files for crossing 1.6.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| crossing-1.6.0.tar.gz | 41.7 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| crossing-1.6.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 85.1 kB
Release files / crossing-1.6.0.tar.gz
| Download URL | crossing-1.6.0.tar.gz |
|---|---|
| Size | 41.7 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
1da46fbdea8fbb800b932d1957a1a487a30a322011f7799b3f8ce85f47873abf
|
|
BLAKE2b-256 checksum How to use checksums |
575e7c31cdaf826a095f3b97135f827ceaf780bc553264b3883aa39405e6bd36
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|
Release files / crossing-1.6.0-py3-none-any.whl
| Download URL | crossing-1.6.0-py3-none-any.whl |
|---|---|
| Size | 43.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
93c9e8c59ba9d6c67201a6a9898946aa4180b1d69eaf3bbea7432e45d48cf829
|
|
BLAKE2b-256 checksum How to use checksums |
bafe545f2447dc07e6666ba2582687341b7bcdf949fb0727f370958935d84d72
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/6.2.0 CPython/3.12.3
|