apisec-code-bolt 0.1.7__tar.gz → 0.1.9__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.
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/PKG-INFO +45 -3
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/README.md +44 -2
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/pyproject.toml +1 -1
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/analyzer.py +96 -35
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/data_flow.py +12 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/dependency_extractor.py +86 -18
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cli/main.py +43 -5
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cloud/apisec_client.py +9 -2
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/telemetry.py +7 -3
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/taint_scanner.py +218 -18
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/jvm/parser.py +0 -1
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/query/executor.py +1 -1
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_cli.py +45 -1
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_dependency_parsing.py +125 -4
- apisec_code_bolt-0.1.9/tests/unit/test_entry_point_interface_resolution.py +149 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_rails_plugin.py +597 -0
- apisec_code_bolt-0.1.9/tests/unit/test_upload_verify_timeout.py +69 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/.gitignore +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/__main__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/binding_tracker.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/call_graph.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/call_graph_types.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/call_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/capability_tagger.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/config_scanner.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/flow_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/hof_catalog.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/integration_detector.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/literal_scanner.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/path_normalizer.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/read_site_detector.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/request_patterns.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/sensitivity_classifier.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/sink_evidence.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/surface_enrichment.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/url_prefix_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cli/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cli/exit_codes.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cloud/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cloud/client.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/config.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/credentials.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/discovery.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/log_format.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/manifest.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/repo.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/state.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/core/types.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/fingerprinting/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/_graphql_common.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/_jwt_common.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/auth_helpers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/base.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/_path_helpers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/aspnet_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/grpc_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/jwt_config_extractor.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/legacy_aspnet_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/refit_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/wcf_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/dotnet/webforms_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/_annotations.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/_constraints.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/jaxrs_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/jwt_config_extractor.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/micronaut_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/java/spring_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/_route_helpers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/express_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/fastify_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/js/nestjs_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/celery_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/click_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/django_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/fastapi/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/fastapi/plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/flask_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/prefect_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/python/webhook_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/_ts_helpers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/grape_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/rails_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/frameworks/ruby/sinatra_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/base.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/csharp/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/csharp/language_services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/csharp/literals.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/csharp/parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/csharp/type_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/js/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/js/language_services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/js/parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/jvm/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/jvm/language_services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/jvm/type_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/cbv_extractor.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/constant_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/cross_file_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/dynamic_route_detector.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/expression_utils.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/extraction_types.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/language_services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/parameter_analyzer.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/path_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/router_registry.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/type_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/python/visitors.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/ruby/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/ruby/language_services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/ruby/parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/parsing/services.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/query/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/query/ast_cache.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/query/handlers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/query/manifest_context.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/conftest.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/Cargo.toml +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/Gemfile.lock +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/Pipfile +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/build.gradle +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/build.gradle.kts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/go.mod +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/package.json +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/pom.xml +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/pyproject_pep621.toml +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/pyproject_poetry.toml +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/requirements-dev.txt +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/requirements.txt +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/setup.cfg +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/dep_files/setup.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_django/urls.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_django/views.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_express/app.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_express/middleware/authenticate.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_express/routes/auth.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_express/routes/users.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_fastapi/main.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_fastapi/routes/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_fastapi/routes/users.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_fastapi/services/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_fastapi/services/user_service.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_flask/app.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_flask_pydantic/app.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_grape/api.rb +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_nestjs/auth.controller.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_nestjs/roles.decorator.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_nestjs/roles.guard.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_nestjs/users.controller.ts +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_sinatra/app.rb +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_spring_boot/src/main/java/com/example/demo/config/SecurityConfig.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_spring_boot/src/main/java/com/example/demo/controller/UserController.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_spring_boot/src/main/java/com/example/demo/model/User.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_spring_boot/src/main/java/com/example/demo/service/UserService.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/sample_spring_boot/src/main/resources/application.properties +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/VULNERABILITIES.md +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/config/SecurityConfig.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/controller/AdminController.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/controller/OrderController.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/controller/UserController.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/model/Order.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/model/User.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/service/OrderService.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/java/com/example/vulnapi/service/UserService.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vuln_spring_boot/src/main/resources/application.properties +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vulnerable_app/pom.xml +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/fixtures/vulnerable_app/src/main/java/com/demo/DemoController.java +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_django_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_express_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_fastapi_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_flask_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_nestjs_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_spring_boot_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/integration/test_vulnerable_app_reachability_guard.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/fixtures/sample_app.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/test_auth_indicator_matching.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/test_executor.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/test_handlers.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/test_query/test_read_site_origin_types.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/__init__.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_apisec_client.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_aspnet_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_benchmark_audit.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_benchmark_run.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_call_graph.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_cbv_extractor.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_cli_console_url.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_config.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_config_scanner_framework.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_constant_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_cross_file_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_csharp_literals.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_csharp_type_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_data_flow.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_discovery.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_django_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_dynamic_routes.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_express_nestjs_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_fastapi_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_fastify_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_flow_analysis.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_grape_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_grpc_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_instrumentation.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_integration_detection.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_java_parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_jaxrs_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_js_graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_jwt_config_extraction.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_legacy_aspnet_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_manifest_upload_gzip.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_micronaut_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_parameter_analyzer.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_path_resolver.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_pydantic_body_inference.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_python_parser.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_refit_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_repo_canonical.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_router_registry.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_ruby_graphql_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_sensitivity_capability.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_sinatra_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_sink_evidence.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_spring_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_static_route_catchup.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_surface_enrichment.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_surface_extraction_client.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_telemetry.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_transformation_tracking.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_types.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_wcf_plugin.py +0 -0
- {apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/tests/unit/test_webforms_plugin.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: apisec-code-bolt
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.9
|
|
4
4
|
Summary: Static analysis probe for extracting architectural metadata from codebases
|
|
5
5
|
Project-URL: Homepage, https://apisec.ai
|
|
6
6
|
Project-URL: Documentation, https://docs.apisec.ai/code-bolt
|
|
@@ -291,8 +291,8 @@ Options:
|
|
|
291
291
|
|
|
292
292
|
### telemetry
|
|
293
293
|
|
|
294
|
-
Manage anonymous
|
|
295
|
-
code, paths, or credentials).
|
|
294
|
+
Manage anonymous usage telemetry (opt-out; on by default, disable any time with
|
|
295
|
+
`telemetry off`; never includes code, paths, or credentials).
|
|
296
296
|
|
|
297
297
|
```bash
|
|
298
298
|
apisec-code-bolt telemetry on|off|status
|
|
@@ -349,6 +349,48 @@ ruff check .
|
|
|
349
349
|
ruff format --check .
|
|
350
350
|
```
|
|
351
351
|
|
|
352
|
+
### Publishing a release (maintainers)
|
|
353
|
+
|
|
354
|
+
The CLI is published to [PyPI](https://pypi.org/project/apisec-code-bolt/) by the
|
|
355
|
+
**Publish to PyPI** GitHub Action. **Merging to `build` does NOT publish** — the
|
|
356
|
+
workflow only runs on `workflow_dispatch` or a published GitHub Release, and it
|
|
357
|
+
builds from whatever ref it runs on.
|
|
358
|
+
|
|
359
|
+
Prerequisites (one-time): the `PYPI_API_TOKEN` repository secret must be set
|
|
360
|
+
(Settings → Secrets and variables → Actions).
|
|
361
|
+
|
|
362
|
+
1. **Bump the version.** Edit `version` in `pyproject.toml` (single source of
|
|
363
|
+
truth; `--version` reads it via package metadata). PyPI **rejects re-uploads**
|
|
364
|
+
of an existing version, so the number must be higher than the current PyPI
|
|
365
|
+
release. Open a PR and merge it to `build`.
|
|
366
|
+
|
|
367
|
+
2. **Publish** — dispatch the workflow (the standard path we use):
|
|
368
|
+
Actions → *Publish to PyPI* → **Run workflow** → select branch **`build`**.
|
|
369
|
+
|
|
370
|
+
```bash
|
|
371
|
+
gh workflow run "Publish to PyPI" --ref build
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
<details><summary>Alternative: publish via a GitHub Release</summary>
|
|
375
|
+
|
|
376
|
+
New release → create tag `vX.Y.Z` → set **Target: `build`** (it defaults to
|
|
377
|
+
the default branch; only `build` has the bumped version) → Publish. This
|
|
378
|
+
fires the same workflow via `release: [published]`.
|
|
379
|
+
|
|
380
|
+
```bash
|
|
381
|
+
gh release create vX.Y.Z --target build --title "vX.Y.Z" --notes "…"
|
|
382
|
+
```
|
|
383
|
+
</details>
|
|
384
|
+
|
|
385
|
+
3. **Verify.** Confirm the new version appears on
|
|
386
|
+
[PyPI](https://pypi.org/project/apisec-code-bolt/), then upgrade an install:
|
|
387
|
+
```bash
|
|
388
|
+
uv tool upgrade apisec-code-bolt # or: pipx upgrade apisec-code-bolt
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
> Always release from `build` after the version bump has merged there — releasing
|
|
392
|
+
> from a ref that still carries the old version will fail the PyPI upload.
|
|
393
|
+
|
|
352
394
|
## Privacy
|
|
353
395
|
|
|
354
396
|
apisec-code-bolt is designed with privacy as a core principle:
|
|
@@ -244,8 +244,8 @@ Options:
|
|
|
244
244
|
|
|
245
245
|
### telemetry
|
|
246
246
|
|
|
247
|
-
Manage anonymous
|
|
248
|
-
code, paths, or credentials).
|
|
247
|
+
Manage anonymous usage telemetry (opt-out; on by default, disable any time with
|
|
248
|
+
`telemetry off`; never includes code, paths, or credentials).
|
|
249
249
|
|
|
250
250
|
```bash
|
|
251
251
|
apisec-code-bolt telemetry on|off|status
|
|
@@ -302,6 +302,48 @@ ruff check .
|
|
|
302
302
|
ruff format --check .
|
|
303
303
|
```
|
|
304
304
|
|
|
305
|
+
### Publishing a release (maintainers)
|
|
306
|
+
|
|
307
|
+
The CLI is published to [PyPI](https://pypi.org/project/apisec-code-bolt/) by the
|
|
308
|
+
**Publish to PyPI** GitHub Action. **Merging to `build` does NOT publish** — the
|
|
309
|
+
workflow only runs on `workflow_dispatch` or a published GitHub Release, and it
|
|
310
|
+
builds from whatever ref it runs on.
|
|
311
|
+
|
|
312
|
+
Prerequisites (one-time): the `PYPI_API_TOKEN` repository secret must be set
|
|
313
|
+
(Settings → Secrets and variables → Actions).
|
|
314
|
+
|
|
315
|
+
1. **Bump the version.** Edit `version` in `pyproject.toml` (single source of
|
|
316
|
+
truth; `--version` reads it via package metadata). PyPI **rejects re-uploads**
|
|
317
|
+
of an existing version, so the number must be higher than the current PyPI
|
|
318
|
+
release. Open a PR and merge it to `build`.
|
|
319
|
+
|
|
320
|
+
2. **Publish** — dispatch the workflow (the standard path we use):
|
|
321
|
+
Actions → *Publish to PyPI* → **Run workflow** → select branch **`build`**.
|
|
322
|
+
|
|
323
|
+
```bash
|
|
324
|
+
gh workflow run "Publish to PyPI" --ref build
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
<details><summary>Alternative: publish via a GitHub Release</summary>
|
|
328
|
+
|
|
329
|
+
New release → create tag `vX.Y.Z` → set **Target: `build`** (it defaults to
|
|
330
|
+
the default branch; only `build` has the bumped version) → Publish. This
|
|
331
|
+
fires the same workflow via `release: [published]`.
|
|
332
|
+
|
|
333
|
+
```bash
|
|
334
|
+
gh release create vX.Y.Z --target build --title "vX.Y.Z" --notes "…"
|
|
335
|
+
```
|
|
336
|
+
</details>
|
|
337
|
+
|
|
338
|
+
3. **Verify.** Confirm the new version appears on
|
|
339
|
+
[PyPI](https://pypi.org/project/apisec-code-bolt/), then upgrade an install:
|
|
340
|
+
```bash
|
|
341
|
+
uv tool upgrade apisec-code-bolt # or: pipx upgrade apisec-code-bolt
|
|
342
|
+
```
|
|
343
|
+
|
|
344
|
+
> Always release from `build` after the version bump has merged there — releasing
|
|
345
|
+
> from a ref that still carries the old version will fail the PyPI upload.
|
|
346
|
+
|
|
305
347
|
## Privacy
|
|
306
348
|
|
|
307
349
|
apisec-code-bolt is designed with privacy as a core principle:
|
|
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "apisec-code-bolt"
|
|
7
|
-
version = "0.1.
|
|
7
|
+
version = "0.1.9"
|
|
8
8
|
description = "Static analysis probe for extracting architectural metadata from codebases"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Proprietary"}
|
|
@@ -790,16 +790,16 @@ class ProjectAnalyzer:
|
|
|
790
790
|
changed = True
|
|
791
791
|
|
|
792
792
|
# Group-level auth: RequireAuthorization chained on the group
|
|
793
|
-
has_group_auth =
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
)()
|
|
801
|
-
|
|
802
|
-
|
|
793
|
+
has_group_auth = False
|
|
794
|
+
for call in parsed_file.call_sites:
|
|
795
|
+
if call.callee_name.split(".")[-1] != "RequireAuthorization":
|
|
796
|
+
continue
|
|
797
|
+
recv = call.receiver_expression or ""
|
|
798
|
+
if not recv.startswith("line:"):
|
|
799
|
+
continue
|
|
800
|
+
if recv[5:].isdigit() and int(recv[5:]) in chain_lines:
|
|
801
|
+
has_group_auth = True
|
|
802
|
+
break
|
|
803
803
|
|
|
804
804
|
# Find calls to custom Map* extension methods (non-standard, non-chained).
|
|
805
805
|
# These are calls like ``group.MapCreateBrandEndpoint()`` where the method
|
|
@@ -2234,46 +2234,107 @@ class ProjectAnalyzer:
|
|
|
2234
2234
|
Resolve handler qualified names to call graph symbols.
|
|
2235
2235
|
|
|
2236
2236
|
Handles mismatches between route extraction and call graph naming
|
|
2237
|
-
(e.g., module path variations)
|
|
2237
|
+
(e.g., module path variations), and the interface-handler pattern where
|
|
2238
|
+
the route annotations live on an interface method but the body lives in
|
|
2239
|
+
an implementing class (e.g. Micronaut operation interfaces: routes are
|
|
2240
|
+
declared on ``*Operations`` interfaces and the ``@Controller`` class
|
|
2241
|
+
implements them). Taint must seed from the concrete implementation —
|
|
2242
|
+
the interface method has no body, so a manifest handler pointing at it
|
|
2243
|
+
yields zero data flows.
|
|
2238
2244
|
"""
|
|
2239
2245
|
if not self._call_graph:
|
|
2240
2246
|
return entry_points
|
|
2241
2247
|
|
|
2242
|
-
|
|
2243
|
-
|
|
2248
|
+
cg = self._call_graph
|
|
2249
|
+
symbols = set(cg._symbols.keys())
|
|
2244
2250
|
|
|
2251
|
+
# interface simple-name -> {implementing class qualified names}. Sourced
|
|
2252
|
+
# from parsed classes (base_classes includes Java `implements`); the
|
|
2253
|
+
# built CallGraph does not expose the builder's class-base map.
|
|
2254
|
+
iface_impls: dict[str, set[str]] = {}
|
|
2255
|
+
for pf in self._parsed_files.values():
|
|
2256
|
+
if not getattr(pf, "success", False):
|
|
2257
|
+
continue
|
|
2258
|
+
for cls in getattr(pf, "classes", None) or []:
|
|
2259
|
+
try:
|
|
2260
|
+
cls_qname = cls.qualified_name.full
|
|
2261
|
+
except AttributeError:
|
|
2262
|
+
continue
|
|
2263
|
+
for base in getattr(cls, "base_classes", None) or []:
|
|
2264
|
+
iface_impls.setdefault(base.rsplit(".", 1)[-1], set()).add(cls_qname)
|
|
2265
|
+
|
|
2266
|
+
def _redirect_to_impl(handler_qn: str) -> str:
|
|
2267
|
+
"""If handler is declared on an interface a concrete class implements,
|
|
2268
|
+
redirect to that class's method (where the body/params live)."""
|
|
2269
|
+
if "." not in handler_qn:
|
|
2270
|
+
return handler_qn
|
|
2271
|
+
owner, method = handler_qn.rsplit(".", 1)
|
|
2272
|
+
impls = iface_impls.get(owner.rsplit(".", 1)[-1])
|
|
2273
|
+
if not impls:
|
|
2274
|
+
return handler_qn
|
|
2275
|
+
fallback = None
|
|
2276
|
+
for impl_cls in impls:
|
|
2277
|
+
cand = f"{impl_cls}.{method}"
|
|
2278
|
+
if cand == handler_qn or cand not in symbols:
|
|
2279
|
+
continue
|
|
2280
|
+
node = cg.get_node(cand)
|
|
2281
|
+
# Prefer a concrete implementation with a body (call edges) or one
|
|
2282
|
+
# not flagged abstract; keep the interface handler otherwise.
|
|
2283
|
+
if cg.get_callees(cand) or not getattr(node, "is_abstract", False):
|
|
2284
|
+
return cand
|
|
2285
|
+
fallback = fallback or cand
|
|
2286
|
+
return fallback or handler_qn
|
|
2287
|
+
|
|
2288
|
+
resolved = []
|
|
2245
2289
|
for ep in entry_points:
|
|
2246
2290
|
handler = ep.get("handler_qualified_name")
|
|
2247
2291
|
if not handler:
|
|
2248
2292
|
resolved.append(ep)
|
|
2249
2293
|
continue
|
|
2250
2294
|
|
|
2251
|
-
if handler in symbols
|
|
2252
|
-
|
|
2253
|
-
|
|
2254
|
-
|
|
2255
|
-
|
|
2256
|
-
|
|
2257
|
-
|
|
2258
|
-
|
|
2259
|
-
|
|
2260
|
-
|
|
2261
|
-
|
|
2262
|
-
|
|
2263
|
-
|
|
2264
|
-
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2295
|
+
chosen = handler if handler in symbols else None
|
|
2296
|
+
if chosen is None:
|
|
2297
|
+
# Try common variations (module-path prefix stripping, __init__).
|
|
2298
|
+
candidates = [handler, handler.replace(".__init__", "")]
|
|
2299
|
+
if "." in handler:
|
|
2300
|
+
parts = handler.split(".")
|
|
2301
|
+
candidates += [".".join(parts[i:]) for i in range(1, len(parts))]
|
|
2302
|
+
for cand in candidates:
|
|
2303
|
+
if cand in symbols:
|
|
2304
|
+
chosen = cand
|
|
2305
|
+
break
|
|
2306
|
+
# Suffix match on the handler tail. Some plugins record the
|
|
2307
|
+
# handler module as the *routing* file rather than the view's
|
|
2308
|
+
# module (e.g. Django: `urls.UserSessionLoginApi.post` or the
|
|
2309
|
+
# function-based `api_router.favorite_post`, while the symbols
|
|
2310
|
+
# are `app.authentication.apis.UserSessionLoginApi.post` and
|
|
2311
|
+
# `app.api.views.favorite_post`). Prefix stripping can't bridge
|
|
2312
|
+
# that. Try the `Class.method` tail first, then the bare name,
|
|
2313
|
+
# accepting a match only when it is unambiguous.
|
|
2314
|
+
if chosen is None and "." in handler:
|
|
2315
|
+
parts = handler.split(".")
|
|
2316
|
+
for depth in (2, 1):
|
|
2317
|
+
if depth > len(parts):
|
|
2318
|
+
continue
|
|
2319
|
+
tail = "." + ".".join(parts[-depth:])
|
|
2320
|
+
tail_matches = [s for s in symbols if s.endswith(tail)]
|
|
2321
|
+
if len(tail_matches) == 1:
|
|
2322
|
+
chosen = tail_matches[0]
|
|
2323
|
+
break
|
|
2324
|
+
chosen = chosen or handler
|
|
2325
|
+
|
|
2326
|
+
# Interface-handler → concrete implementation (Micronaut operation
|
|
2327
|
+
# interfaces, and any handler declared on an implemented interface).
|
|
2328
|
+
redirected = _redirect_to_impl(chosen)
|
|
2329
|
+
if redirected != handler:
|
|
2330
|
+
ep = dict(ep)
|
|
2331
|
+
ep["handler_qualified_name"] = redirected
|
|
2273
2332
|
resolved.append(ep)
|
|
2274
2333
|
|
|
2275
2334
|
return resolved
|
|
2276
2335
|
|
|
2336
|
+
return resolved
|
|
2337
|
+
|
|
2277
2338
|
# =========================================================================
|
|
2278
2339
|
# Integration Detection
|
|
2279
2340
|
# =========================================================================
|
{apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/analysis/data_flow.py
RENAMED
|
@@ -1646,6 +1646,18 @@ class DataFlowAnalyzer:
|
|
|
1646
1646
|
if pname and ploc:
|
|
1647
1647
|
param_locations[pname] = ploc
|
|
1648
1648
|
|
|
1649
|
+
# Django/DRF views receive all user input via the `request` object
|
|
1650
|
+
# (request.data / .GET / .POST / .query_params) rather than typed
|
|
1651
|
+
# per-field route params, so there is nothing in param_locations to
|
|
1652
|
+
# seed. Treat the handler's `request` parameter itself as an HTTP
|
|
1653
|
+
# origin and let propagation carry it into request.* reads, serializer
|
|
1654
|
+
# construction, and downstream sinks.
|
|
1655
|
+
if framework.startswith("django"):
|
|
1656
|
+
for pobj in getattr(func, "parameters", None) or []:
|
|
1657
|
+
pname = getattr(pobj, "name", None)
|
|
1658
|
+
if pname == "request" and pname not in param_locations:
|
|
1659
|
+
param_locations[pname] = "HTTP_BODY"
|
|
1660
|
+
|
|
1649
1661
|
route_path = ep.get("path", "")
|
|
1650
1662
|
origins = source_id.identify_from_function(
|
|
1651
1663
|
func,
|
|
@@ -26,6 +26,25 @@ from ..parsing.base import ParsedFile
|
|
|
26
26
|
logger = logging.getLogger(__name__)
|
|
27
27
|
|
|
28
28
|
|
|
29
|
+
def _maven_bom_resolution_enabled() -> bool:
|
|
30
|
+
"""Whether to resolve parent/imported Maven BOM versions from Maven Central.
|
|
31
|
+
|
|
32
|
+
Defaults ON: without it, deps whose versions are managed by a parent POM or
|
|
33
|
+
imported BOM (Spring Boot's jackson-databind, hsqldb, …) come back
|
|
34
|
+
version=None and are invisible to CVE matching. Resolution fetches those POMs
|
|
35
|
+
(network egress) and degrades cleanly to None on any failure.
|
|
36
|
+
|
|
37
|
+
Opt out for air-gapped / no-egress runs with ``--offline`` (sets
|
|
38
|
+
``APISEC_OFFLINE``) or ``APISEC_RESOLVE_MAVEN_BOMS=0``.
|
|
39
|
+
"""
|
|
40
|
+
if os.environ.get("APISEC_OFFLINE", "").strip().lower() in ("1", "true", "yes"):
|
|
41
|
+
return False
|
|
42
|
+
flag = os.environ.get("APISEC_RESOLVE_MAVEN_BOMS")
|
|
43
|
+
if flag is not None:
|
|
44
|
+
return flag.strip().lower() not in ("0", "false", "no", "")
|
|
45
|
+
return True
|
|
46
|
+
|
|
47
|
+
|
|
29
48
|
class DependencyExtractor:
|
|
30
49
|
"""Extracts package dependencies and internal imports from a project."""
|
|
31
50
|
|
|
@@ -854,27 +873,75 @@ class DependencyExtractor:
|
|
|
854
873
|
|
|
855
874
|
_MAVEN_CENTRAL = "https://repo1.maven.org/maven2"
|
|
856
875
|
_bom_pom_cache: dict[str, bytes | None] = {}
|
|
876
|
+
_BOM_FETCH_ATTEMPTS = 3
|
|
877
|
+
|
|
878
|
+
def _bom_cache_dir(self) -> Path:
|
|
879
|
+
base = os.environ.get("APISEC_BOM_CACHE_DIR") or os.path.join(
|
|
880
|
+
os.path.expanduser("~"), ".cache", "apisec-code-bolt", "maven-boms"
|
|
881
|
+
)
|
|
882
|
+
return Path(base)
|
|
857
883
|
|
|
858
884
|
def _fetch_pom(self, group: str, artifact: str, version: str) -> bytes | None:
|
|
859
|
-
"""Fetch a POM from Maven Central
|
|
885
|
+
"""Fetch a POM from Maven Central, resiliently. None on definitive miss
|
|
886
|
+
(404) or offline; a transient network failure is retried and NOT cached,
|
|
887
|
+
so a blip can't silently null out a managed version for the whole run.
|
|
888
|
+
|
|
889
|
+
Two cache tiers: an in-process dict and an on-disk cache. Version-pinned
|
|
890
|
+
POMs are immutable, so a cached copy is always valid — and it makes
|
|
891
|
+
resolution resilient to a flaky network (once fetched, reused forever).
|
|
892
|
+
"""
|
|
860
893
|
key = f"{group}:{artifact}:{version}"
|
|
861
894
|
if key in self._bom_pom_cache:
|
|
862
895
|
return self._bom_pom_cache[key]
|
|
896
|
+
|
|
897
|
+
cache_file = self._bom_cache_dir() / group.replace(".", "/") / artifact / f"{version}.pom"
|
|
898
|
+
try:
|
|
899
|
+
if cache_file.is_file():
|
|
900
|
+
data = cache_file.read_bytes()
|
|
901
|
+
self._bom_pom_cache[key] = data
|
|
902
|
+
return data
|
|
903
|
+
except Exception: # unreadable cache file → fall through to fetch
|
|
904
|
+
pass
|
|
905
|
+
|
|
863
906
|
url = (
|
|
864
907
|
f"{self._MAVEN_CENTRAL}/{group.replace('.', '/')}/"
|
|
865
908
|
f"{artifact}/{version}/{artifact}-{version}.pom"
|
|
866
909
|
)
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
910
|
+
import time
|
|
911
|
+
|
|
912
|
+
for attempt in range(1, self._BOM_FETCH_ATTEMPTS + 1):
|
|
913
|
+
try:
|
|
914
|
+
import httpx
|
|
915
|
+
|
|
916
|
+
resp = httpx.get(url, timeout=8.0, follow_redirects=True)
|
|
917
|
+
if resp.status_code == 200:
|
|
918
|
+
data = resp.content
|
|
919
|
+
self._bom_pom_cache[key] = data
|
|
920
|
+
try: # best-effort persist; immutable so safe to keep
|
|
921
|
+
cache_file.parent.mkdir(parents=True, exist_ok=True)
|
|
922
|
+
cache_file.write_bytes(data)
|
|
923
|
+
except Exception:
|
|
924
|
+
pass
|
|
925
|
+
return data
|
|
926
|
+
if resp.status_code == 404:
|
|
927
|
+
self._bom_pom_cache[key] = None # definitive miss; don't retry
|
|
928
|
+
return None
|
|
929
|
+
# other statuses (5xx, 429, …) are transient → retry
|
|
930
|
+
except Exception as exc: # network/transport → retry
|
|
931
|
+
logger.debug(
|
|
932
|
+
"BOM POM fetch attempt %d/%d failed for %s: %s",
|
|
933
|
+
attempt,
|
|
934
|
+
self._BOM_FETCH_ATTEMPTS,
|
|
935
|
+
key,
|
|
936
|
+
exc,
|
|
937
|
+
)
|
|
938
|
+
if attempt < self._BOM_FETCH_ATTEMPTS:
|
|
939
|
+
time.sleep(0.5 * attempt) # linear backoff
|
|
870
940
|
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
logger.debug("BOM POM fetch failed for %s: %s", key, exc)
|
|
876
|
-
self._bom_pom_cache[key] = data
|
|
877
|
-
return data
|
|
941
|
+
# Transient failure exhausted retries. Degrade to None but do NOT cache it,
|
|
942
|
+
# so a later call (or run) can retry rather than being poisoned to null.
|
|
943
|
+
logger.debug("BOM POM fetch exhausted retries for %s", key)
|
|
944
|
+
return None
|
|
878
945
|
|
|
879
946
|
def _remote_managed_versions(
|
|
880
947
|
self, seeds: list[tuple[str, str, str]], seed_props: dict[str, str]
|
|
@@ -921,7 +988,7 @@ class DependencyExtractor:
|
|
|
921
988
|
pe = el.find(q("properties"))
|
|
922
989
|
if pe is not None:
|
|
923
990
|
for c in pe:
|
|
924
|
-
k = c.tag[len(ns):] if ns else c.tag
|
|
991
|
+
k = c.tag[len(ns) :] if ns else c.tag
|
|
925
992
|
if c.text:
|
|
926
993
|
own[k] = c.text.strip()
|
|
927
994
|
own.setdefault("project.version", v)
|
|
@@ -1100,15 +1167,16 @@ class DependencyExtractor:
|
|
|
1100
1167
|
if parent_group and parent_artifact and parent_version:
|
|
1101
1168
|
parent_coord = (parent_group, parent_artifact, parent_version)
|
|
1102
1169
|
|
|
1103
|
-
# Imported-BOM resolution (
|
|
1170
|
+
# Imported-BOM resolution (default ON): third-party deps (h2, mysql, jackson…)
|
|
1104
1171
|
# get their versions from the parent chain / imported BOMs (e.g.
|
|
1105
1172
|
# spring-boot-dependencies), NOT this file, so they'd otherwise be
|
|
1106
|
-
# version=None → silently treated downstream as "not affected"
|
|
1107
|
-
#
|
|
1108
|
-
#
|
|
1109
|
-
# Degrades cleanly to None on any
|
|
1173
|
+
# version=None → silently treated downstream as "not affected", hiding
|
|
1174
|
+
# real CVEs. Fetch those POMs from Maven Central and resolve their pins;
|
|
1175
|
+
# opt out for air-gapped runs via --offline (see
|
|
1176
|
+
# _maven_bom_resolution_enabled). Degrades cleanly to None on any
|
|
1177
|
+
# fetch/parse failure.
|
|
1110
1178
|
remote_managed: dict[str, str] = {}
|
|
1111
|
-
if
|
|
1179
|
+
if _maven_bom_resolution_enabled():
|
|
1112
1180
|
seeds = ([parent_coord] if parent_coord else []) + import_boms
|
|
1113
1181
|
if seeds:
|
|
1114
1182
|
remote_managed = self._remote_managed_versions(seeds, props)
|
|
@@ -6,6 +6,8 @@ This module defines the CLI commands using Click.
|
|
|
6
6
|
|
|
7
7
|
from __future__ import annotations
|
|
8
8
|
|
|
9
|
+
import contextlib
|
|
10
|
+
import os
|
|
9
11
|
import sys
|
|
10
12
|
from pathlib import Path
|
|
11
13
|
from typing import Any
|
|
@@ -150,7 +152,11 @@ def cli(ctx: click.Context, verbose: bool, quiet: bool, debug: bool, log_format:
|
|
|
150
152
|
@click.option(
|
|
151
153
|
"--timeout",
|
|
152
154
|
type=int,
|
|
153
|
-
help=
|
|
155
|
+
help=(
|
|
156
|
+
"Timeout in seconds for local analysis and, on upload, for waiting on "
|
|
157
|
+
"cloud verification. Large apps may need a higher value; a client "
|
|
158
|
+
"timeout never stops the analysis, which continues server-side."
|
|
159
|
+
),
|
|
154
160
|
)
|
|
155
161
|
@click.option(
|
|
156
162
|
"--dry-run",
|
|
@@ -163,6 +169,13 @@ def cli(ctx: click.Context, verbose: bool, quiet: bool, debug: bool, log_format:
|
|
|
163
169
|
is_flag=True,
|
|
164
170
|
help="Write manifest JSON to stdout instead of a file (for pipeline use).",
|
|
165
171
|
)
|
|
172
|
+
@click.option(
|
|
173
|
+
"--offline",
|
|
174
|
+
is_flag=True,
|
|
175
|
+
help="No network egress during extraction. Disables Maven BOM version "
|
|
176
|
+
"resolution (managed deps stay version=None, so their CVEs can't be matched). "
|
|
177
|
+
"Use for air-gapped runs.",
|
|
178
|
+
)
|
|
166
179
|
@click.pass_context
|
|
167
180
|
def analyze(
|
|
168
181
|
ctx: click.Context,
|
|
@@ -181,8 +194,14 @@ def analyze(
|
|
|
181
194
|
timeout: int | None,
|
|
182
195
|
dry_run: bool = False,
|
|
183
196
|
output_stdout: bool = False,
|
|
197
|
+
offline: bool = False,
|
|
184
198
|
) -> None:
|
|
185
199
|
log_format: str = ctx.obj.get("log_format", "text")
|
|
200
|
+
# Maven BOM version resolution is ON by default (resolves parent/BOM-managed
|
|
201
|
+
# dep versions so their CVEs are matchable). --offline opts out for
|
|
202
|
+
# no-egress environments; the extractor reads APISEC_OFFLINE.
|
|
203
|
+
if offline:
|
|
204
|
+
os.environ["APISEC_OFFLINE"] = "1"
|
|
186
205
|
"""
|
|
187
206
|
Analyze a codebase and generate a manifest.
|
|
188
207
|
|
|
@@ -250,8 +269,6 @@ def analyze(
|
|
|
250
269
|
)
|
|
251
270
|
|
|
252
271
|
if needs_registration():
|
|
253
|
-
import os
|
|
254
|
-
|
|
255
272
|
env_code = os.environ.get("APISEC_REGISTRATION_CODE", "").strip()
|
|
256
273
|
if env_code:
|
|
257
274
|
code = env_code
|
|
@@ -364,7 +381,7 @@ def analyze(
|
|
|
364
381
|
output_path = apisec_dir / "manifest.json"
|
|
365
382
|
manifest_json = result.manifest.to_json(pretty=True)
|
|
366
383
|
output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
367
|
-
output_path.write_text(manifest_json)
|
|
384
|
+
output_path.write_text(manifest_json, encoding="utf-8")
|
|
368
385
|
|
|
369
386
|
if log_format == "json":
|
|
370
387
|
_emit_json_summary(path, result, output_path=output_path)
|
|
@@ -428,6 +445,7 @@ def analyze(
|
|
|
428
445
|
api_url_override,
|
|
429
446
|
reasoning_url,
|
|
430
447
|
quiet,
|
|
448
|
+
verify_timeout=timeout,
|
|
431
449
|
)
|
|
432
450
|
elif cloud_url:
|
|
433
451
|
# Legacy: direct connection to reasoning engine (for local dev)
|
|
@@ -517,6 +535,7 @@ def _run_authenticated_upload(
|
|
|
517
535
|
api_url_override: str | None,
|
|
518
536
|
reasoning_url_override: str | None,
|
|
519
537
|
quiet: bool,
|
|
538
|
+
verify_timeout: int | None = None,
|
|
520
539
|
) -> None:
|
|
521
540
|
"""Register surface with applicationsservice (PAT), then upload manifest to the reasoning engine (same PAT + app_id)."""
|
|
522
541
|
import json
|
|
@@ -580,11 +599,14 @@ def _run_authenticated_upload(
|
|
|
580
599
|
write_state(project_path, RepoState(application_id=registration.app_id))
|
|
581
600
|
|
|
582
601
|
manifest_data = json.loads(result.manifest.to_json())
|
|
602
|
+
upload_kwargs: dict[str, Any] = {"reasoning_engine_url": reasoning_url_override}
|
|
603
|
+
if verify_timeout:
|
|
604
|
+
upload_kwargs["max_wait"] = verify_timeout
|
|
583
605
|
upload_result = client.upload_and_verify(
|
|
584
606
|
registration,
|
|
585
607
|
manifest_data,
|
|
586
608
|
project_path,
|
|
587
|
-
|
|
609
|
+
**upload_kwargs,
|
|
588
610
|
)
|
|
589
611
|
|
|
590
612
|
if not quiet:
|
|
@@ -601,6 +623,14 @@ def _run_authenticated_upload(
|
|
|
601
623
|
vtable.add_row("Error", f"[red]{upload_result['error']}[/red]")
|
|
602
624
|
console.print(vtable)
|
|
603
625
|
|
|
626
|
+
if upload_result.get("status") == "timeout":
|
|
627
|
+
console.print(
|
|
628
|
+
"[yellow]Stopped waiting for verification, but the analysis is "
|
|
629
|
+
"still running server-side[/yellow] — results will appear at the "
|
|
630
|
+
"console link below shortly. This is common for large apps; pass "
|
|
631
|
+
"[cyan]--timeout <seconds>[/cyan] to wait longer."
|
|
632
|
+
)
|
|
633
|
+
|
|
604
634
|
console.print()
|
|
605
635
|
view_url = _resolve_console_url(registration.console_url, registration.app_id)
|
|
606
636
|
panel_body = view_url
|
|
@@ -1097,6 +1127,14 @@ cloud:
|
|
|
1097
1127
|
|
|
1098
1128
|
def main() -> None:
|
|
1099
1129
|
"""Main entry point."""
|
|
1130
|
+
# Force UTF-8 on stdio so non-ASCII manifest/JSON output (e.g. the U+2192
|
|
1131
|
+
# arrow in data-flow output) does not crash on Windows consoles that
|
|
1132
|
+
# default to cp1252.
|
|
1133
|
+
for stream in (sys.stdout, sys.stderr):
|
|
1134
|
+
reconfigure = getattr(stream, "reconfigure", None)
|
|
1135
|
+
if reconfigure is not None:
|
|
1136
|
+
with contextlib.suppress(ValueError, OSError):
|
|
1137
|
+
reconfigure(encoding="utf-8")
|
|
1100
1138
|
cli(obj={})
|
|
1101
1139
|
|
|
1102
1140
|
|
{apisec_code_bolt-0.1.7 → apisec_code_bolt-0.1.9}/src/apisec_code_bolt/cloud/apisec_client.py
RENAMED
|
@@ -106,8 +106,15 @@ class ApisecClient:
|
|
|
106
106
|
manifest_data: dict[str, Any],
|
|
107
107
|
project_root: Path,
|
|
108
108
|
reasoning_engine_url: str | None = None,
|
|
109
|
+
max_wait: int = 900,
|
|
109
110
|
) -> dict[str, Any]:
|
|
110
|
-
"""Upload manifest to reasoning engine with Bearer PAT and app_id; run verification.
|
|
111
|
+
"""Upload manifest to reasoning engine with Bearer PAT and app_id; run verification.
|
|
112
|
+
|
|
113
|
+
``max_wait`` bounds how long the client polls for verification to
|
|
114
|
+
finish. Large apps (many endpoints / LLM verification) can legitimately
|
|
115
|
+
take several minutes, so this defaults generously; a client-side
|
|
116
|
+
timeout does not stop the analysis, which continues server-side.
|
|
117
|
+
"""
|
|
111
118
|
engine_url = reasoning_engine_url or self._api_url
|
|
112
119
|
with CloudClient(
|
|
113
120
|
api_url=engine_url,
|
|
@@ -120,7 +127,7 @@ class ApisecClient:
|
|
|
120
127
|
|
|
121
128
|
maybe_enrich_manifest(manifest_data, project_root, client)
|
|
122
129
|
|
|
123
|
-
executor = QueryExecutor(project_root=project_root)
|
|
130
|
+
executor = QueryExecutor(project_root=project_root, max_wait=max_wait)
|
|
124
131
|
result = executor.run_connected(client, manifest_data)
|
|
125
132
|
|
|
126
133
|
return {
|
|
@@ -17,6 +17,7 @@ PII GUARD:
|
|
|
17
17
|
from __future__ import annotations
|
|
18
18
|
|
|
19
19
|
import logging
|
|
20
|
+
import os
|
|
20
21
|
import threading
|
|
21
22
|
from pathlib import Path
|
|
22
23
|
from typing import Any
|
|
@@ -267,8 +268,6 @@ def get_registration_code() -> str:
|
|
|
267
268
|
Checks APISEC_REGISTRATION_CODE env var first so CI/automated environments
|
|
268
269
|
can bypass the interactive first-run prompt without writing a config file.
|
|
269
270
|
"""
|
|
270
|
-
import os
|
|
271
|
-
|
|
272
271
|
env_code = os.environ.get("APISEC_REGISTRATION_CODE", "").strip()
|
|
273
272
|
if env_code:
|
|
274
273
|
return env_code
|
|
@@ -335,7 +334,12 @@ def emit_install_event(*, probe_version: str = "") -> None:
|
|
|
335
334
|
threading.Thread(target=_send, daemon=True).start()
|
|
336
335
|
|
|
337
336
|
|
|
338
|
-
|
|
337
|
+
# Overridable for non-prod testing (staging/dev receivers), mirroring the
|
|
338
|
+
# analyze path's --api-url. Defaults to prod, so unset means no change.
|
|
339
|
+
_TELEMETRY_ENDPOINT = os.environ.get(
|
|
340
|
+
"APISEC_TELEMETRY_ENDPOINT",
|
|
341
|
+
"https://api.apisec.ai/v1/applications/surface/telemetry/events",
|
|
342
|
+
)
|
|
339
343
|
|
|
340
344
|
|
|
341
345
|
def emit_analyze_event(
|