entrygraph 0.1.8__tar.gz → 0.1.10__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.
- {entrygraph-0.1.8 → entrygraph-0.1.10}/PKG-INFO +1 -1
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/_version.py +2 -2
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/pipeline/scanner.py +13 -6
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/resolve/resolver.py +29 -8
- entrygraph-0.1.10/tests/fixtures/python/fuzzy_sink/app.py +14 -0
- entrygraph-0.1.10/tests/fixtures/python/heal_fidelity/caller.py +4 -0
- entrygraph-0.1.10/tests/fixtures/python/heal_fidelity/worker.py +3 -0
- entrygraph-0.1.10/tests/test_fuzzy_sink.py +36 -0
- entrygraph-0.1.10/tests/test_heal_fidelity.py +66 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_resolver.py +32 -2
- {entrygraph-0.1.8 → entrygraph-0.1.10}/.github/workflows/ci.yml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/.github/workflows/release.yml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/.gitignore +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/LICENSE +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/README.md +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/RELEASING.md +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/pyproject.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/__main__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/api.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/cli/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/cli/main.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/cli/render.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/csharp.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/go.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/java.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/javascript.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/lib_javascript.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/lib_python.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/php.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/python.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/ruby.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/data/sinks/rust.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/db/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/db/engine.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/db/meta.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/db/models.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/db/queries.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/base.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/configs.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/csharp.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/golang.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/java.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/javascript.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/php.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/python.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/ruby.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/entrypoints/rust.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/frameworks.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/manifests.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/detect/taint.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/errors.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/base.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/csharp.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/golang.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/ir.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/java.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/javascript.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/php.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/python.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/registry.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/ruby.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/extract/rust.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/fs/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/fs/hashing.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/fs/lang.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/fs/walker.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/graph/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/graph/adjacency.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/graph/cte.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/graph/scoring.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/kinds.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/parsing/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/parsing/parsers.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/parsing/queries.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/pipeline/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/pipeline/worker.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/pipeline/writer.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/py.typed +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/csharp/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/csharp/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/csharp/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/go/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/go/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/go/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/java/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/java/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/java/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/javascript/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/javascript/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/javascript/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/php/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/php/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/php/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/python/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/python/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/python/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/ruby/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/ruby/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/ruby/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/rust/calls.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/rust/definitions.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/queries/rust/imports.scm +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/resolve/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/resolve/externals.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/resolve/hierarchy.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/resolve/symbol_table.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/src/entrygraph/results.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/conftest.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/csharp/aspnet_app/Controllers/ReportsController.cs +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/csharp/aspnet_app/Program.cs +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/csharp/aspnet_app/app.csproj +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/go/gin_app/go.mod +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/go/gin_app/main.go +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/go/gin_app/service.go +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/java/spring_app/pom.xml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/java/spring_app/src/main/java/com/example/Application.java +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportRunner.java +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/java/spring_app/src/main/java/com/example/ReportService.java +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/java/spring_app/src/main/java/com/example/UserController.java +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/javascript/commonjs_app/server.js +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/javascript/express_app/package.json +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/javascript/express_app/src/routes.js +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/javascript/express_app/src/services.js +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/php/laravel_app/app/Http/Controllers/ReportController.php +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/php/laravel_app/artisan +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/php/laravel_app/composer.json +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/php/laravel_app/routes/web.php +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/app/__init__.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/app/db.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/app/routes.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/app/services.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/cli.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/flask_app/requirements.txt +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/sanitizer/app.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/python/taint_source/handler.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/ruby/sinatra_app/Gemfile +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/ruby/sinatra_app/app.rb +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/ruby/sinatra_app/services/runner.rb +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/rust/axum_app/Cargo.toml +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/rust/axum_app/src/handlers.rs +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/rust/axum_app/src/main.rs +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_api.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_cli.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_commonjs.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_engine_pragmas.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_entrypoint_expansion.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_entrypoints.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_csharp.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_go.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_java.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_javascript.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_php.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_python.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_ruby.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_extract_rust.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_frameworks.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_hardening.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_incremental.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_indexer.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_lang.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_manifests.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_models.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_pool.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_reachability.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_render.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_scoring.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_sink_catalog.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_taint.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_taint_sanitizers.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_taint_sources.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/tests/test_walker.py +0 -0
- {entrygraph-0.1.8 → entrygraph-0.1.10}/uv.lock +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: entrygraph
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.10
|
|
4
4
|
Summary: Language-agnostic code graph: query symbols, entrypoints, and source-to-sink call paths from a SQLite index
|
|
5
5
|
Project-URL: Repository, https://github.com/brettbergin/entrygraph
|
|
6
6
|
Author-email: Brett Bergin <brettberginbc@yahoo.com>
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.1.
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 1,
|
|
21
|
+
__version__ = version = '0.1.10'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 1, 10)
|
|
23
23
|
|
|
24
24
|
__commit_id__ = commit_id = None
|
|
@@ -432,7 +432,9 @@ def _write_edges_and_entrypoints(
|
|
|
432
432
|
entrypoint_rows: list[dict] = []
|
|
433
433
|
|
|
434
434
|
for path, x, is_package in extractions:
|
|
435
|
-
resolver = FileResolver(
|
|
435
|
+
resolver = FileResolver(
|
|
436
|
+
x, module_ids[path], table, externals, is_package, sink_registry=sink_registry
|
|
437
|
+
)
|
|
436
438
|
file_id = file_id_by_path[path]
|
|
437
439
|
for edge in resolver.resolve():
|
|
438
440
|
is_call = edge.kind is EdgeKind.CALLS
|
|
@@ -521,17 +523,22 @@ def _heal_dangling_edges(session, table: SymbolTable, new_qnames: set[str]) -> N
|
|
|
521
523
|
"""Re-bind edges left NULL (degraded on wipe, or targeting a not-yet-existing
|
|
522
524
|
symbol) whose dst_qname now names a freshly-created symbol."""
|
|
523
525
|
dangling = session.execute(
|
|
524
|
-
select(Edge.id, Edge.dst_qname).where(
|
|
526
|
+
select(Edge.id, Edge.dst_qname, Edge.confidence).where(
|
|
525
527
|
Edge.dst_symbol_id.is_(None), Edge.dst_qname.in_(new_qnames)
|
|
526
528
|
)
|
|
527
529
|
).all()
|
|
528
530
|
updates = []
|
|
529
|
-
|
|
531
|
+
unresolved = int(Confidence.UNRESOLVED)
|
|
532
|
+
for edge_id, dst_qname, confidence in dangling:
|
|
530
533
|
target = table.by_fqn.get(dst_qname)
|
|
531
534
|
if target is not None:
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
+
# Preserve the edge's original tier. Only an edge that was UNRESOLVED
|
|
536
|
+
# (a project import whose target didn't exist yet) upgrades to IMPORT
|
|
537
|
+
# on heal, matching what a full re-index produces. An EXACT/IMPORT/
|
|
538
|
+
# FUZZY/cha edge whose target was merely wiped and recreated keeps its
|
|
539
|
+
# tier — healing must not silently promote a fuzzy or CHA edge.
|
|
540
|
+
healed = int(Confidence.IMPORT) if confidence == unresolved else confidence
|
|
541
|
+
updates.append({"id": edge_id, "dst_symbol_id": target, "confidence": healed})
|
|
535
542
|
if updates:
|
|
536
543
|
session.execute(update(Edge), updates)
|
|
537
544
|
|
|
@@ -17,6 +17,7 @@ incremental re-resolution can heal them when the target appears.
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
from dataclasses import dataclass
|
|
20
|
+
from typing import TYPE_CHECKING
|
|
20
21
|
|
|
21
22
|
from entrygraph.extract.ir import FileExtraction, RawReference
|
|
22
23
|
from entrygraph.kinds import Confidence, EdgeKind, SymbolKind
|
|
@@ -29,6 +30,9 @@ from entrygraph.resolve.hierarchy import (
|
|
|
29
30
|
)
|
|
30
31
|
from entrygraph.resolve.symbol_table import SymbolTable
|
|
31
32
|
|
|
33
|
+
if TYPE_CHECKING:
|
|
34
|
+
from entrygraph.detect.taint import SinkRegistry
|
|
35
|
+
|
|
32
36
|
# expand_relative re-exported for backward compatibility with callers/tests.
|
|
33
37
|
__all__ = ["FileResolver", "ResolvedEdge", "expand_relative"]
|
|
34
38
|
|
|
@@ -66,12 +70,14 @@ class FileResolver:
|
|
|
66
70
|
table: SymbolTable,
|
|
67
71
|
externals: ExternalRegistry,
|
|
68
72
|
is_package: bool = False,
|
|
73
|
+
sink_registry: SinkRegistry | None = None,
|
|
69
74
|
) -> None:
|
|
70
75
|
self.x = extraction
|
|
71
76
|
self.module_symbol_id = module_symbol_id
|
|
72
77
|
self.table = table
|
|
73
78
|
self.externals = externals
|
|
74
79
|
self.is_package = is_package
|
|
80
|
+
self.sink_registry = sink_registry
|
|
75
81
|
self.prefix = LANG_PREFIX.get(extraction.language, extraction.language)
|
|
76
82
|
self.import_map, self.wildcard_modules = build_import_map(extraction, is_package)
|
|
77
83
|
|
|
@@ -217,6 +223,14 @@ class FileResolver:
|
|
|
217
223
|
return self.table.by_fqn[target]
|
|
218
224
|
return self.table.unique_by_name(name, (SymbolKind.FUNCTION, SymbolKind.METHOD))
|
|
219
225
|
|
|
226
|
+
def _guess_is_sink(self, ref: RawReference) -> bool:
|
|
227
|
+
"""Whether the receiver-agnostic guess for this attribute call matches a
|
|
228
|
+
registered sink — checked with the same call the index-time stamper uses."""
|
|
229
|
+
if self.sink_registry is None:
|
|
230
|
+
return False
|
|
231
|
+
guess = f"{self.prefix}:*.{ref.callee_name}"
|
|
232
|
+
return self.sink_registry.match(guess, ref.arg_preview) is not None
|
|
233
|
+
|
|
220
234
|
def _bind(self, ref: RawReference) -> tuple[int | None, str, Confidence, str | None]:
|
|
221
235
|
# 1. import-map expansion (chase re-export chains for project targets)
|
|
222
236
|
first_seg = ref.callee_text.split(".", 1)[0].split("(", 1)[0]
|
|
@@ -270,14 +284,21 @@ class FileResolver:
|
|
|
270
284
|
# where runner is a local variable) this is the only way to recover the
|
|
271
285
|
# call-graph edge without local type inference — it's the documented
|
|
272
286
|
# FUZZY tradeoff.
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
)
|
|
278
|
-
|
|
279
|
-
if
|
|
280
|
-
|
|
287
|
+
#
|
|
288
|
+
# But if the receiver-agnostic guess (`py:*.execute`) is itself a
|
|
289
|
+
# registered sink, do NOT fuzzy-bind: rewriting dst_qname to a unique
|
|
290
|
+
# project method named `execute` would erase the sink stamp and hide the
|
|
291
|
+
# vulnerability. Prefer the guess so the sink survives (step 5); CHA still
|
|
292
|
+
# adds the project method as a via="cha" candidate, so no edge is lost.
|
|
293
|
+
if not (ref.receiver_text is not None and self._guess_is_sink(ref)):
|
|
294
|
+
kinds = (
|
|
295
|
+
(SymbolKind.METHOD,)
|
|
296
|
+
if ref.receiver_text is not None
|
|
297
|
+
else (SymbolKind.FUNCTION, SymbolKind.CLASS)
|
|
298
|
+
)
|
|
299
|
+
dst_id = self.table.unique_by_name(ref.callee_name, kinds)
|
|
300
|
+
if dst_id is not None:
|
|
301
|
+
return dst_id, self.table.qname_of[dst_id], Confidence.FUZZY, None
|
|
281
302
|
|
|
282
303
|
# 5. unresolved: language-prefixed guess, still a real node for sinks
|
|
283
304
|
if ref.receiver_text is not None:
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import sqlite3
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
class Runner:
|
|
5
|
+
# the ONLY project method named "execute" — the collision that used to
|
|
6
|
+
# swallow cursor.execute via unique-name fuzzy binding
|
|
7
|
+
def execute(self):
|
|
8
|
+
return 1
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def query(db_path, user_id):
|
|
12
|
+
conn = sqlite3.connect(db_path)
|
|
13
|
+
cur = conn.cursor()
|
|
14
|
+
cur.execute("SELECT * FROM users WHERE id = " + user_id)
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
"""Sink preservation vs unique-name fuzzy binding (Phase 1.4).
|
|
2
|
+
|
|
3
|
+
Regression: an unknown-receiver call like `cursor.execute(sql)` fuzzy-bound to a
|
|
4
|
+
unique project method named `execute`, rewriting dst_qname so the `py:*.execute`
|
|
5
|
+
SQL sink never got stamped — the vulnerability silently vanished from the graph.
|
|
6
|
+
"""
|
|
7
|
+
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
import pytest
|
|
13
|
+
|
|
14
|
+
from entrygraph import CodeGraph
|
|
15
|
+
|
|
16
|
+
FUZZY_SINK_APP = Path(__file__).parent / "fixtures" / "python" / "fuzzy_sink"
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@pytest.fixture(scope="module")
|
|
20
|
+
def graph(tmp_path_factory) -> CodeGraph:
|
|
21
|
+
db = tmp_path_factory.mktemp("db") / "graph.db"
|
|
22
|
+
g = CodeGraph.index(FUZZY_SINK_APP, db=db)
|
|
23
|
+
yield g
|
|
24
|
+
g.close()
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_sql_sink_survives_unique_name_collision(graph):
|
|
28
|
+
sinks = graph.sql("SELECT dst_qname, sink_id FROM edges WHERE sink_id IS NOT NULL")
|
|
29
|
+
by_qname = {r["dst_qname"]: r["sink_id"] for r in sinks}
|
|
30
|
+
assert by_qname.get("py:*.execute") == "py.sql-execute"
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_sql_path_reachable_despite_collision(graph):
|
|
34
|
+
paths = graph.paths(source="app.query", sink_category="sql", include_unresolved=True)
|
|
35
|
+
assert paths
|
|
36
|
+
assert paths[0].symbols[-1].qname == "py:*.execute"
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
"""Incremental healing must not alter an edge's confidence tier (Phase 2.4).
|
|
2
|
+
|
|
3
|
+
Regression: `_heal_dangling_edges` re-bound every healed edge at Confidence.IMPORT
|
|
4
|
+
unconditionally. A cross-file FUZZY (or via="cha") edge whose target file was
|
|
5
|
+
merely touched came back as IMPORT, so a min_confidence>=IMPORT query returned
|
|
6
|
+
edges a full re-index would score FUZZY.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
import shutil
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
import pytest
|
|
16
|
+
from sqlalchemy import select
|
|
17
|
+
from sqlalchemy.orm import Session
|
|
18
|
+
|
|
19
|
+
from entrygraph.db.engine import make_engine
|
|
20
|
+
from entrygraph.db.models import Edge, Symbol
|
|
21
|
+
from entrygraph.pipeline.scanner import index_repository
|
|
22
|
+
|
|
23
|
+
HEAL_APP = Path(__file__).parent / "fixtures" / "python" / "heal_fidelity"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
@pytest.fixture
|
|
27
|
+
def repo(tmp_path) -> Path:
|
|
28
|
+
dst = tmp_path / "repo"
|
|
29
|
+
shutil.copytree(HEAL_APP, dst)
|
|
30
|
+
return dst
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def _edge_confidence(engine, src_qname, dst_qname) -> int | None:
|
|
34
|
+
with Session(engine) as s:
|
|
35
|
+
ids = {sym.qname: sym.id for sym in s.execute(select(Symbol)).scalars()}
|
|
36
|
+
src = ids.get(src_qname)
|
|
37
|
+
row = s.execute(
|
|
38
|
+
select(Edge.confidence).where(Edge.src_symbol_id == src, Edge.dst_qname == dst_qname)
|
|
39
|
+
).first()
|
|
40
|
+
return row[0] if row else None
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def test_healed_fuzzy_edge_keeps_its_confidence(repo, tmp_path):
|
|
44
|
+
from entrygraph.kinds import Confidence
|
|
45
|
+
|
|
46
|
+
engine = make_engine(tmp_path / "inc.db")
|
|
47
|
+
index_repository(repo, engine)
|
|
48
|
+
before = _edge_confidence(engine, "caller.go", "worker.Worker.process")
|
|
49
|
+
assert before == int(Confidence.FUZZY) # cross-file unique-name fuzzy edge
|
|
50
|
+
|
|
51
|
+
# touch worker.py: Worker.process gets a new id, the inbound edge is SET NULL
|
|
52
|
+
# then healed. Its tier must stay FUZZY, not be upgraded to IMPORT.
|
|
53
|
+
worker = repo / "worker.py"
|
|
54
|
+
worker.write_text(worker.read_text() + "\n# touch\n")
|
|
55
|
+
os.utime(worker, ns=(0, 10**18))
|
|
56
|
+
index_repository(repo, engine, incremental=True)
|
|
57
|
+
|
|
58
|
+
after = _edge_confidence(engine, "caller.go", "worker.Worker.process")
|
|
59
|
+
assert after == int(Confidence.FUZZY)
|
|
60
|
+
|
|
61
|
+
# and it matches a full re-index
|
|
62
|
+
full = make_engine(tmp_path / "full.db")
|
|
63
|
+
index_repository(repo, full)
|
|
64
|
+
assert after == _edge_confidence(full, "caller.go", "worker.Worker.process")
|
|
65
|
+
full.dispose()
|
|
66
|
+
engine.dispose()
|
|
@@ -20,7 +20,7 @@ def make_table() -> SymbolTable:
|
|
|
20
20
|
return table
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def make_resolver(table, refs=(), imports=(), module="app.routes", module_id=2):
|
|
23
|
+
def make_resolver(table, refs=(), imports=(), module="app.routes", module_id=2, sink_registry=None):
|
|
24
24
|
x = FileExtraction(
|
|
25
25
|
path="app/routes.py",
|
|
26
26
|
language="python",
|
|
@@ -31,7 +31,7 @@ def make_resolver(table, refs=(), imports=(), module="app.routes", module_id=2):
|
|
|
31
31
|
references=list(refs),
|
|
32
32
|
)
|
|
33
33
|
externals = ExternalRegistry(iter(range(100, 200)).__next__)
|
|
34
|
-
return FileResolver(x, module_id, table, externals), externals
|
|
34
|
+
return FileResolver(x, module_id, table, externals, sink_registry=sink_registry), externals
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
def ref(callee_text, callee_name=None, receiver=None, caller=None, kind="call"):
|
|
@@ -180,6 +180,36 @@ def test_unresolved_gets_prefixed_placeholder():
|
|
|
180
180
|
assert by_qname["py:eval"].confidence is Confidence.UNRESOLVED
|
|
181
181
|
|
|
182
182
|
|
|
183
|
+
def test_sink_named_attribute_call_is_not_fuzzy_bound():
|
|
184
|
+
# `cursor.execute(...)` guesses to the sink `py:*.execute`. Even though a
|
|
185
|
+
# unique project method `Runner.execute` (id 12) exists, we must NOT fuzzy-bind
|
|
186
|
+
# to it — that would rewrite dst_qname and erase the SQL sink. Keep the guess.
|
|
187
|
+
from entrygraph.detect.taint import builtin_registry
|
|
188
|
+
|
|
189
|
+
table = make_table()
|
|
190
|
+
resolver, _ = make_resolver(
|
|
191
|
+
table, [ref("cursor.execute", receiver="cursor")], sink_registry=builtin_registry()
|
|
192
|
+
)
|
|
193
|
+
call = next(e for e in resolver.resolve() if e.kind is EdgeKind.CALLS and e.via != "cha")
|
|
194
|
+
assert call.dst_qname == "py:*.execute"
|
|
195
|
+
assert call.confidence is Confidence.UNRESOLVED
|
|
196
|
+
assert call.dst_symbol_id != 12
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def test_non_sink_attribute_call_still_fuzzy_binds_with_registry():
|
|
200
|
+
# `obj.render` is not a sink, so unique-name fuzzy binding still applies even
|
|
201
|
+
# with a sink registry present — the fix is targeted to sink names only.
|
|
202
|
+
from entrygraph.detect.taint import builtin_registry
|
|
203
|
+
|
|
204
|
+
table = make_table()
|
|
205
|
+
resolver, _ = make_resolver(
|
|
206
|
+
table, [ref("obj.render", receiver="obj")], sink_registry=builtin_registry()
|
|
207
|
+
)
|
|
208
|
+
call = next(e for e in resolver.resolve() if e.kind is EdgeKind.CALLS)
|
|
209
|
+
assert call.dst_symbol_id == 13 # Runner.render
|
|
210
|
+
assert call.confidence is Confidence.FUZZY
|
|
211
|
+
|
|
212
|
+
|
|
183
213
|
def test_relative_import_expansion():
|
|
184
214
|
table = make_table()
|
|
185
215
|
table.add_symbol(40, "app.utils", 40 and "utils", SymbolKind.MODULE)
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/csharp/aspnet_app/Services/ReportService.cs
RENAMED
|
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
|
{entrygraph-0.1.8 → entrygraph-0.1.10}/tests/fixtures/javascript/express_app/src/services.js
RENAMED
|
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
|
|
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
|
|
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
|