sourcecode 4.5.2__tar.gz → 4.5.3__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.
- {sourcecode-4.5.2 → sourcecode-4.5.3}/CHANGELOG.md +87 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/PKG-INFO +4 -4
- {sourcecode-4.5.2 → sourcecode-4.5.3}/README.md +3 -3
- {sourcecode-4.5.2 → sourcecode-4.5.3}/pyproject.toml +1 -1
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ast_extractor.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cli.py +11 -3
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/client_calls.py +5 -2
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_init.py +56 -15
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/data_exposure.py +31 -2
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/remedies.py +17 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ris.py +27 -17
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_config_scan.py +19 -4
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/servlet_surface.py +8 -2
- sourcecode-4.5.3/src/sourcecode/source_text.py +103 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/token_estimate.py +1 -1
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_edit.py +2 -2
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_repo.py +2 -1
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/verify_rules.py +32 -12
- {sourcecode-4.5.2 → sourcecode-4.5.3}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/.gitignore +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/.ruff.toml +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/CLAUDE.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/CONTRIBUTING.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/LICENSE +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/SECURITY.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/raw +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/scripts/perf_harness.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/baseline_autocapture.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/data_labels.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/license.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/openrewrite_recipe.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/release_info.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/rule_catalog.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/sarif.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_security_audit.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/README.md +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.5.2 → sourcecode-4.5.3}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,93 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.5.3] — 2026-08-04
|
|
6
|
+
|
|
7
|
+
Patch: the fixable half of field evaluation #10 (7,5/10, CI reliability **4/10**), in one
|
|
8
|
+
battery. Five defects, one commit each, every one reproduced on a fixture before it was
|
|
9
|
+
touched. No new capability, no published field removed, no exit code moved.
|
|
10
|
+
|
|
11
|
+
Three of the evaluation's thirteen complaints are **not** fixed here because they did not
|
|
12
|
+
survive verification against HEAD — `spring-audit --compact` has shipped since 3.7.0 (the
|
|
13
|
+
second time that closed row has been re-derived as missing), the mojibake diagnosis is
|
|
14
|
+
wrong (`_force_utf8_streams` already reconfigures both streams; this is the **fourth**
|
|
15
|
+
field report of console mojibake and none has reproduced under measurement — the one
|
|
16
|
+
variable they share is the platform with no runner, C3-39), and the "dead surface"
|
|
17
|
+
capability ships as `endpoints --client-usage`. Full record and the F-A…F-E capability
|
|
18
|
+
queue in `.planning/FIELD-EVAL-10-2026-08-04.md`.
|
|
19
|
+
|
|
20
|
+
### Fixed
|
|
21
|
+
|
|
22
|
+
- **`verify --init` derived a contract asserting an invariant the same build reported as
|
|
23
|
+
violated** (C1-24, **critical**). It proposed `no-sec-004` — *"SEC-004 fires nowhere in
|
|
24
|
+
this repository"* — on a tree where `spring-audit` reported SEC-004 `high`. Committed
|
|
25
|
+
without `--dry-run`, that gate is red on the commit it was derived from, on the
|
|
26
|
+
`core`-tier command whose promise is *"safe to gate CI on"*. Cause: the derivation
|
|
27
|
+
computed "fires nowhere" from the CIR pattern audit alone, and SEC-004…SEC-007 are
|
|
28
|
+
produced by `security_config_scan`, the second authority `spring-audit` also runs, which
|
|
29
|
+
returns nothing without a `root`. The root is now threaded through, so the derivation,
|
|
30
|
+
`verify`, `verify-edit` and the self-check all evaluate the same audit. **The safety net
|
|
31
|
+
had the same hole** — `execute_candidates` runs every candidate through the real engine
|
|
32
|
+
before publishing it, and it too evaluated without a root, so it approved the rule it
|
|
33
|
+
exists to stop. Same row, second half: SEC-005/006/007 appeared in **neither** the
|
|
34
|
+
proposed nor the rejected list, because the catalogue was built from the pattern
|
|
35
|
+
registries, which stop at SEC-004; it now comes from `rule_catalog.RULES`, the authority
|
|
36
|
+
`--help` and the README already render from.
|
|
37
|
+
- **The security configuration scan could not tell code from a comment** (E-2). `SEC-004`
|
|
38
|
+
and `SEC-007` fired identically on a live statement and on a commented-out block —
|
|
39
|
+
**31 % of the `high` findings** in the field, and the count understates it, because the
|
|
40
|
+
correlation engine wrote a persuasive paragraph about shared credentials over a block
|
|
41
|
+
that was switched off. Confident prose about dead code is worse than a terse warning: it
|
|
42
|
+
invites trust. This is E-1's class in a module written after E-1 closed, so the stripper
|
|
43
|
+
is now one authority (`source_text`) wired at the **three points a file is read** rather
|
|
44
|
+
than inside each rule — a rule added later is covered by construction. Comments are
|
|
45
|
+
**blanked, not deleted**, so offsets survive and every observation still points at the
|
|
46
|
+
line a reader can open. String literals are deliberately kept: these rules carry their
|
|
47
|
+
evidence *inside* the literal. A/B with the cache cold over BroadleafCommerce,
|
|
48
|
+
openmrs-core, keycloak and spring-petclinic: **byte-identical security findings** — the
|
|
49
|
+
fix removes false positives and loses no true one.
|
|
50
|
+
- **Two path conventions inside one document** (C2-24). `servlet_surface.mappings[].source_file`
|
|
51
|
+
emitted the platform separator while every other payload emits POSIX. The reported
|
|
52
|
+
emitter was one of **four**: checking for the recurrence found `client_calls` (the
|
|
53
|
+
`--client-usage` join, whose entire product is a join on path) and the file-too-large
|
|
54
|
+
branch of `ast_extractor`, which spelled the path differently from the two lines directly
|
|
55
|
+
above it; a static guard over every module then found a fourth in `token_estimate`. All
|
|
56
|
+
four fixed, and the guard now fails any `str(x.relative_to(y))` without normalisation.
|
|
57
|
+
- **An untracked source file was not counted as a change** (C3-40). `cache freshness`
|
|
58
|
+
reported `has_uncommitted_changes: False` with untracked files present, contradicting the
|
|
59
|
+
root scan's `--changed-only` help in the same CLI. The measurement underneath was worse
|
|
60
|
+
than the contradiction: an untracked `.java` file **is** in the IR, so the tree reported
|
|
61
|
+
`fresh: true` for a snapshot that never contained that file — a false *fresh*. Bound to
|
|
62
|
+
`baseline_autocapture.worktree_dirty`, which already had to answer this exactly right for
|
|
63
|
+
the architectural history; gitignored build output and cache directories are still not
|
|
64
|
+
modifications.
|
|
65
|
+
- **`data-exposure` measured nothing and offered no way to measure** (C4-14). It answered
|
|
66
|
+
`answered: false` and stopped, on the payload aimed at a regulated buyer — ninth
|
|
67
|
+
appearance of the C4 class. It now carries the registry remedy plus a `declare` block
|
|
68
|
+
(file, key, copy-pasteable example), all read from the module that parses them. The
|
|
69
|
+
example is **executed** in the battery — written to disk, parsed by the real loader, and
|
|
70
|
+
re-run to confirm the answer changes — because an example that has drifted from the schema
|
|
71
|
+
teaches a buyer the wrong shape.
|
|
72
|
+
|
|
73
|
+
### Changed
|
|
74
|
+
|
|
75
|
+
- Cache-hit provenance moved from inside the `git_context` payload block to `_meta`
|
|
76
|
+
(`stale_fields_refreshed`). Surfaced by C3-40: written into the payload it changed that
|
|
77
|
+
block's *shape* depending on whether the answer came from cache, so one request answered
|
|
78
|
+
two ways compared unequal on a key describing the run rather than the repository. The
|
|
79
|
+
previous key was undocumented, unschema'd, underscore-prefixed and reachable only on one
|
|
80
|
+
cache path — treated as internal provenance rather than a published field, which is why
|
|
81
|
+
this is a patch and not a major.
|
|
82
|
+
|
|
83
|
+
### Still open from this evaluation, on purpose
|
|
84
|
+
|
|
85
|
+
- **C1-25** — reachability is measured at type scope and attributed at member scope
|
|
86
|
+
(~1 700× over-attribution measured in one direction; global `@Configuration` defects
|
|
87
|
+
degraded to `internal` in the other). Feature-sized, and it **gates** the `exposure` join
|
|
88
|
+
(F-C): that table built on this model would put the over-attribution behind a
|
|
89
|
+
CISO-facing headline.
|
|
90
|
+
- **P-8** — a pricing decision, and the third independent arrival at the per-repository band.
|
|
91
|
+
|
|
5
92
|
## [4.5.2] — 2026-08-04
|
|
6
93
|
|
|
7
94
|
Patch: two affordances that shipped command by command, and therefore drifted, become
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.5.
|
|
3
|
+
Version: 4.5.3
|
|
4
4
|
Summary: Persistent structural context and ultra-fast repeated analysis for AI coding agents
|
|
5
5
|
License-File: LICENSE
|
|
6
6
|
Keywords: agents,ai,codebase,context,developer-tools,llm
|
|
@@ -42,7 +42,7 @@ Description-Content-Type: text/markdown
|
|
|
42
42
|
|
|
43
43
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
44
44
|
|
|
45
|
-

|
|
46
46
|

|
|
47
47
|
|
|
48
48
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -97,7 +97,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
97
97
|
# pip / pipx
|
|
98
98
|
pipx install sourcecode # or: pip install sourcecode
|
|
99
99
|
|
|
100
|
-
ask version # ask 4.5.
|
|
100
|
+
ask version # ask 4.5.3 — and, on a build that has aged,
|
|
101
101
|
# how many releases have probably shipped since
|
|
102
102
|
```
|
|
103
103
|
|
|
@@ -309,7 +309,7 @@ from it.
|
|
|
309
309
|
| `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
|
|
310
310
|
| `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
|
|
311
311
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
312
|
-
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
|
|
312
|
+
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
313
313
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
314
314
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
315
315
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
**Context · Impact · Migration · Architecture · Review — everything from one structural model.**
|
|
6
6
|
|
|
7
|
-

|
|
8
8
|

|
|
9
9
|
|
|
10
10
|
> **ASK Engine** is the product. The CLI command is **`ask`**. The legacy **`sourcecode`**
|
|
@@ -59,7 +59,7 @@ brew tap haroundominique/sourcecode && brew install sourcecode
|
|
|
59
59
|
# pip / pipx
|
|
60
60
|
pipx install sourcecode # or: pip install sourcecode
|
|
61
61
|
|
|
62
|
-
ask version # ask 4.5.
|
|
62
|
+
ask version # ask 4.5.3 — and, on a build that has aged,
|
|
63
63
|
# how many releases have probably shipped since
|
|
64
64
|
```
|
|
65
65
|
|
|
@@ -271,7 +271,7 @@ from it.
|
|
|
271
271
|
| `risk` | experimental | what each defect actually costs, once reach, access, write effect and the shape of the input path are in it | `defect_severity × reachability × auth_verdict × write_effect × query_construction × input_constraints`; every factor names its authority |
|
|
272
272
|
| `enrich` | experimental | another scanner's SARIF findings, ranked by what this repository does with them | `--sarif <log>`; the same composition as `risk`, with the scanner as the severity authority |
|
|
273
273
|
| `migrate-recipe` | experimental | the migration report as the OpenRewrite recipe that applies it | only recipes a finding named; the manual remainder published beside them; writes nothing without `--write`, and never a runnable command for an empty recipe list |
|
|
274
|
-
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008) |
|
|
274
|
+
| `data-exposure` | experimental | which routes can carry the data you labelled, and who reaches them | labels declared in `sourcecode.config.json` — never inferred from a name; `signature` and `call_reach` evidence published apart, field-level flow out of scope (NC-008). With nothing declared, `ask data-exposure /path/to/repo` answers `answered: false` and hands back the file, the key and an example to declare — it never reports zero exposed routes |
|
|
275
275
|
| `endpoints` | core | every REST endpoint, effective path, security policy, confidence | Spring MVC + JAX-RS (~65 % recall on JAX-RS sub-resource locators). `--compact` answers the exposure census without the rows; `--servlets` lists the servlet-mounted surface as its own population; `--client-usage` says which routes the TS/JS client in this repository actually calls |
|
|
276
276
|
| `spring-audit` | core | transactional anomalies + security surface + validation gaps | `--ci`, `-f github-comment` |
|
|
277
277
|
| `migrate-check` | core | Boot 2→3 readiness: located blockers, per-dimension score, effort | `--blast-radius` orders the re-test plan |
|
|
@@ -1252,7 +1252,7 @@ class AstExtractor:
|
|
|
1252
1252
|
stat = path.stat()
|
|
1253
1253
|
if stat.st_size > self.max_file_size:
|
|
1254
1254
|
return FileContract(
|
|
1255
|
-
path=
|
|
1255
|
+
path=rel_path,
|
|
1256
1256
|
language=language,
|
|
1257
1257
|
extraction_method="heuristic",
|
|
1258
1258
|
limitations=[f"file_too_large: {stat.st_size} bytes > {self.max_file_size}"],
|
|
@@ -2533,9 +2533,17 @@ def main(
|
|
|
2533
2533
|
[l for l in _uc_r.stdout.splitlines() if l.strip()]
|
|
2534
2534
|
)
|
|
2535
2535
|
_patched["git_context"]["uncommitted_files"] = _uc_count
|
|
2536
|
-
|
|
2537
|
-
|
|
2538
|
-
|
|
2536
|
+
# Provenance goes in `_meta`, never inside the payload
|
|
2537
|
+
# block. Written into `git_context` it changed that
|
|
2538
|
+
# block's *shape* depending on whether the answer came
|
|
2539
|
+
# from cache, so one request answered two ways compared
|
|
2540
|
+
# unequal on a key that describes the run rather than
|
|
2541
|
+
# the repository (surfaced by C3-40, which made an
|
|
2542
|
+
# untracked file dirty again and put this path back in
|
|
2543
|
+
# reach).
|
|
2544
|
+
_meta_gc = _patched.setdefault("_meta", {})
|
|
2545
|
+
if isinstance(_meta_gc, dict):
|
|
2546
|
+
_meta_gc["stale_fields_refreshed"] = ["git_context.uncommitted_files"]
|
|
2539
2547
|
_cache_hit_content = _json_gc.dumps(
|
|
2540
2548
|
_patched, indent=2, ensure_ascii=False
|
|
2541
2549
|
)
|
|
@@ -185,9 +185,12 @@ def extract_client_calls(
|
|
|
185
185
|
except OSError:
|
|
186
186
|
continue
|
|
187
187
|
try:
|
|
188
|
-
|
|
188
|
+
# POSIX like every other published path (C2-24). A client-usage row
|
|
189
|
+
# exists to be joined against a route's declaring file, and a join
|
|
190
|
+
# on path fails silently when the two sides spell it differently.
|
|
191
|
+
relative = file_path.relative_to(base).as_posix()
|
|
189
192
|
except ValueError:
|
|
190
|
-
relative =
|
|
193
|
+
relative = Path(file_path).as_posix()
|
|
191
194
|
for match in _VERB_CALL.finditer(text):
|
|
192
195
|
receiver = match.group("receiver") or ""
|
|
193
196
|
if not _HTTP_RECEIVER.search(receiver):
|
|
@@ -125,24 +125,52 @@ def _security_rules(cir: Any) -> tuple[list[dict], list[dict]]:
|
|
|
125
125
|
return proposed, rejected
|
|
126
126
|
|
|
127
127
|
|
|
128
|
-
def
|
|
129
|
-
"""
|
|
130
|
-
|
|
128
|
+
def _rule_catalogue() -> "list[tuple[str, str]]":
|
|
129
|
+
"""Every rule this build ships, as `(pattern_id, severity)`.
|
|
130
|
+
|
|
131
|
+
Read from `rule_catalog.RULES` — the one authority `--help`, the README and
|
|
132
|
+
the rule reference already render from, and the one a test binds to the
|
|
133
|
+
pattern registries so a new rule cannot exist without a row.
|
|
134
|
+
|
|
135
|
+
C1-24's second half: this list used to be built from the two *pattern
|
|
136
|
+
registries* alone, which stop at SEC-004. SEC-005, SEC-006 and SEC-007 are
|
|
137
|
+
produced by `security_config_scan` and appeared in **neither** the proposed
|
|
138
|
+
nor the rejected list — a rule could be silently outside contract derivation
|
|
139
|
+
with nothing saying so, which is the failure mode a "not proposed, and why"
|
|
140
|
+
list exists to prevent.
|
|
141
|
+
"""
|
|
142
|
+
from sourcecode.rule_catalog import RULES
|
|
143
|
+
from sourcecode.spring_security_audit import (
|
|
144
|
+
_DEFAULT_SECURITY_PATTERNS,
|
|
145
|
+
SecurityScanner,
|
|
146
|
+
)
|
|
131
147
|
from sourcecode.spring_tx_analyzer import _DEFAULT_TX_PATTERNS
|
|
148
|
+
|
|
149
|
+
severities: "dict[str, str]" = {}
|
|
150
|
+
for pattern in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS):
|
|
151
|
+
pattern_id = getattr(pattern, "pattern_id", "")
|
|
152
|
+
if pattern_id:
|
|
153
|
+
severities[pattern_id] = getattr(pattern, "severity", "low")
|
|
154
|
+
# The configuration rules carry their severity on the scanner that emits
|
|
155
|
+
# them, not on a pattern object; same authority, different shape.
|
|
156
|
+
severities.update(getattr(SecurityScanner, "_SEVERITY", {}) or {})
|
|
157
|
+
return sorted(
|
|
158
|
+
(rule.id, severities.get(rule.id, "low")) for rule in RULES if rule.id
|
|
159
|
+
)
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def _finding_rules(cir: Any, root: "Optional[Path]" = None) -> tuple[list[dict], list[dict]]:
|
|
163
|
+
"""`forbid_finding` for audit patterns that fire nowhere today."""
|
|
132
164
|
from sourcecode.verify_rules import _audit_findings
|
|
133
165
|
|
|
134
|
-
|
|
166
|
+
# Same `root` the gate evaluates with: without it the configuration rules
|
|
167
|
+
# cannot fire, and every one of them would be proposed as "fires nowhere".
|
|
168
|
+
findings = _audit_findings(cir, root)
|
|
135
169
|
firing = Counter(f.pattern_id for f in findings)
|
|
136
170
|
|
|
137
171
|
proposed: list[dict] = []
|
|
138
172
|
rejected: list[dict] = []
|
|
139
|
-
|
|
140
|
-
(getattr(p, "pattern_id", ""), getattr(p, "severity", "low"))
|
|
141
|
-
for p in (*_DEFAULT_TX_PATTERNS, *_DEFAULT_SECURITY_PATTERNS)
|
|
142
|
-
]
|
|
143
|
-
for pattern_id, severity in sorted(set(catalogue)):
|
|
144
|
-
if not pattern_id:
|
|
145
|
-
continue
|
|
173
|
+
for pattern_id, severity in _rule_catalogue():
|
|
146
174
|
count = firing.get(pattern_id, 0)
|
|
147
175
|
if count:
|
|
148
176
|
rejected.append({
|
|
@@ -283,7 +311,9 @@ def _slug(annotation: str) -> str:
|
|
|
283
311
|
return annotation.lstrip("@").replace(".", "-").lower()
|
|
284
312
|
|
|
285
313
|
|
|
286
|
-
def execute_candidates(
|
|
314
|
+
def execute_candidates(
|
|
315
|
+
candidates: list[dict], cir: Any, root: "Optional[Path]" = None
|
|
316
|
+
) -> tuple[list[dict], list[dict]]:
|
|
287
317
|
"""``(kept, dropped)`` — run every candidate before publishing it.
|
|
288
318
|
|
|
289
319
|
The check that makes this a baseline rather than a wish list, and the one
|
|
@@ -292,6 +322,12 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
|
|
|
292
322
|
the engine `verify` runs and drops anything that disagrees, so a derivation
|
|
293
323
|
that grows a blind spot cannot ship a red gate on the commit it was
|
|
294
324
|
generated from.
|
|
325
|
+
|
|
326
|
+
C1-24: this net had the hole it was meant to catch. It evaluated without a
|
|
327
|
+
`root`, so the security *configuration* rules could not fire here either, and
|
|
328
|
+
`no-sec-004` passed the check that exists to stop exactly that rule from
|
|
329
|
+
being published. A safety net blind in the same way as the thing it guards
|
|
330
|
+
is not a second opinion.
|
|
295
331
|
"""
|
|
296
332
|
from sourcecode.verify_rules import evaluate_all, parse_rules
|
|
297
333
|
|
|
@@ -300,7 +336,7 @@ def execute_candidates(candidates: list[dict], cir: Any) -> tuple[list[dict], li
|
|
|
300
336
|
for rule in candidates:
|
|
301
337
|
payload = {k: v for k, v in rule.items() if not k.startswith("_")}
|
|
302
338
|
try:
|
|
303
|
-
violations = evaluate_all(parse_rules({"contracts": [payload]}), cir)
|
|
339
|
+
violations = evaluate_all(parse_rules({"contracts": [payload]}), cir, root)
|
|
304
340
|
except Exception as exc: # a rule this module built must never break the run
|
|
305
341
|
dropped.append({"id": rule.get("id", "?"), "reason": f"did not parse: {exc}"})
|
|
306
342
|
continue
|
|
@@ -333,12 +369,17 @@ def derive_contracts(root: Path, cir: Optional[Any] = None) -> dict:
|
|
|
333
369
|
|
|
334
370
|
proposed: list[dict] = []
|
|
335
371
|
rejected: list[dict] = []
|
|
372
|
+
# `_finding_rules` needs the root; the graph-only derivations do not. Passed
|
|
373
|
+
# by name rather than positionally so adding a third file-reading derivation
|
|
374
|
+
# is a one-word change instead of a silent blind spot (C1-24).
|
|
336
375
|
for derive in (_security_rules, _finding_rules, _edge_rules):
|
|
337
|
-
rules, skipped =
|
|
376
|
+
rules, skipped = (
|
|
377
|
+
derive(cir, root) if derive is _finding_rules else derive(cir)
|
|
378
|
+
)
|
|
338
379
|
proposed.extend(rules)
|
|
339
380
|
rejected.extend(skipped)
|
|
340
381
|
|
|
341
|
-
kept, dropped = execute_candidates(proposed, cir)
|
|
382
|
+
kept, dropped = execute_candidates(proposed, cir, root)
|
|
342
383
|
|
|
343
384
|
return {
|
|
344
385
|
"schema_version": SCHEMA_VERSION,
|
|
@@ -146,15 +146,44 @@ def build_data_exposure(
|
|
|
146
146
|
if not decl.labels:
|
|
147
147
|
# No declaration is not "nothing is exposed" — it is a question nobody asked
|
|
148
148
|
# yet, and the counts stay null rather than reading as a clean bill (I-3/R9).
|
|
149
|
+
#
|
|
150
|
+
# C4-14: the statement said what to do and the payload gave the reader no
|
|
151
|
+
# way to do it — "cero valor out-of-the-box, cero guía de onboarding en la
|
|
152
|
+
# propia salida", on the payload aimed at a regulated buyer. The file name,
|
|
153
|
+
# the key and the shape are read from `data_labels`, the module that parses
|
|
154
|
+
# them, so an example that stops being valid fails the build instead of
|
|
155
|
+
# teaching a buyer the wrong schema.
|
|
156
|
+
from sourcecode.data_labels import CONFIG_FILENAME, CONFIG_KEY
|
|
157
|
+
from sourcecode.remedies import remedy as _remedy
|
|
158
|
+
|
|
149
159
|
payload.update(
|
|
150
160
|
{
|
|
151
161
|
"answered": False,
|
|
152
162
|
"statement": (
|
|
153
163
|
"No data labels are declared, so this run measured nothing. A "
|
|
154
164
|
"label is a judgement about a domain and is never inferred from a "
|
|
155
|
-
"field name: declare one under `
|
|
156
|
-
"
|
|
165
|
+
f"field name: declare one under `{CONFIG_KEY}` in "
|
|
166
|
+
f"{CONFIG_FILENAME} and re-run."
|
|
157
167
|
),
|
|
168
|
+
"remedy": _remedy("no_data_labels_declared").to_dict(),
|
|
169
|
+
"declare": {
|
|
170
|
+
"file": CONFIG_FILENAME,
|
|
171
|
+
"key": CONFIG_KEY,
|
|
172
|
+
"example": {
|
|
173
|
+
CONFIG_KEY: [
|
|
174
|
+
{
|
|
175
|
+
"label": "pii",
|
|
176
|
+
"types": ["com.example.Person"],
|
|
177
|
+
"fields": ["com.example.Account#iban"],
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
},
|
|
181
|
+
"note": (
|
|
182
|
+
"`types` labels every route whose signature names the type; "
|
|
183
|
+
"`fields` labels the declaring type through the member. Both "
|
|
184
|
+
"are fully-qualified, and neither is matched by name shape."
|
|
185
|
+
),
|
|
186
|
+
},
|
|
158
187
|
"summary": {
|
|
159
188
|
"labels": 0,
|
|
160
189
|
"seeds": 0,
|
|
@@ -75,6 +75,23 @@ REMEDIES: "dict[str, Remedy]" = {
|
|
|
75
75
|
),
|
|
76
76
|
headline="which profile set actually runs",
|
|
77
77
|
),
|
|
78
|
+
Remedy(
|
|
79
|
+
key="no_data_labels_declared",
|
|
80
|
+
command="data-exposure",
|
|
81
|
+
# The next move is a declaration, not a flag: nothing this command
|
|
82
|
+
# could be told on the command line would make it *infer* that a
|
|
83
|
+
# field is sensitive, because that is a judgement about a domain.
|
|
84
|
+
# The invocation is the re-run, and `declare` beside it in the
|
|
85
|
+
# payload carries the file, the key and a copy-pasteable example.
|
|
86
|
+
option="",
|
|
87
|
+
answers=(
|
|
88
|
+
"no data labels are declared, so nothing was measured — declare which "
|
|
89
|
+
"types and fields are sensitive under `dataLabels` in "
|
|
90
|
+
"sourcecode.config.json (a label is a judgement about a domain and is "
|
|
91
|
+
"never inferred from a field name), then re-run"
|
|
92
|
+
),
|
|
93
|
+
headline="declare the labels, then re-run",
|
|
94
|
+
),
|
|
78
95
|
Remedy(
|
|
79
96
|
key="no_contracts_declared",
|
|
80
97
|
command="verify",
|
|
@@ -451,24 +451,34 @@ def _tree_signature(repo_root: Path) -> str:
|
|
|
451
451
|
|
|
452
452
|
|
|
453
453
|
def _has_uncommitted_changes(repo_root: Path) -> bool:
|
|
454
|
-
"""
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
454
|
+
"""True when the working tree differs from HEAD in a way the analysis sees.
|
|
455
|
+
|
|
456
|
+
C3-40: this used to pass ``--untracked-files=no``, to keep legacy
|
|
457
|
+
`.sourcecode-cache/` directories from reading as modifications. That excused
|
|
458
|
+
far more than the noise it was aimed at — **an untracked `.java` file is in
|
|
459
|
+
the IR**, so a repository with a new source file reported
|
|
460
|
+
`has_uncommitted_changes: False` and, through it, `fresh: true` for a snapshot
|
|
461
|
+
that did not contain that file. A false *fresh* is the confident-falsehood
|
|
462
|
+
direction. It also contradicted the root scan's own `--changed-only`, whose
|
|
463
|
+
help promises "staged, unstaged, **untracked**" in the same CLI.
|
|
464
|
+
|
|
465
|
+
One authority now answers it: `baseline_autocapture.worktree_dirty`, which
|
|
466
|
+
already had to get this exactly right for the architectural history. It
|
|
467
|
+
honours `.gitignore` (so build output and any gitignored cache directory are
|
|
468
|
+
not modifications — the original concern, handled properly) and excuses only
|
|
469
|
+
this tool's own footprint under `.ask/`, for the reason recorded there: the
|
|
470
|
+
first capture leaves an untracked file behind, and counting it would leave
|
|
471
|
+
every history one entry long.
|
|
472
|
+
|
|
473
|
+
Returns False when the state cannot be determined (non-git directories), which
|
|
474
|
+
is the pre-existing contract of this helper — `worktree_dirty` answers `None`
|
|
475
|
+
there, and the RIS freshness fields have no way to carry it.
|
|
459
476
|
"""
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
timeout=2,
|
|
466
|
-
)
|
|
467
|
-
if result.returncode == 0:
|
|
468
|
-
return bool(result.stdout.strip())
|
|
469
|
-
except Exception:
|
|
470
|
-
pass
|
|
471
|
-
return False
|
|
477
|
+
from sourcecode.baseline_autocapture import HISTORY_DIRNAME, worktree_dirty
|
|
478
|
+
|
|
479
|
+
root = Path(repo_root)
|
|
480
|
+
own = (root / HISTORY_DIRNAME, root / HISTORY_DIRNAME.parts[0])
|
|
481
|
+
return bool(worktree_dirty(root, ignore=own))
|
|
472
482
|
|
|
473
483
|
|
|
474
484
|
def get_cold_start_context(repo_root: Path) -> dict:
|
|
@@ -338,7 +338,22 @@ def scan_security_configuration(
|
|
|
338
338
|
java_files: "Optional[list[str]]" = None,
|
|
339
339
|
config_files: "Optional[list[str]]" = None,
|
|
340
340
|
) -> "list[SecurityConfigObservation]":
|
|
341
|
-
"""Every textbook weakness under ``root``. Best-effort per file, never fatal.
|
|
341
|
+
"""Every textbook weakness under ``root``. Best-effort per file, never fatal.
|
|
342
|
+
|
|
343
|
+
E-2: every rule here matches on raw text, and until 4.5.3 none of them could
|
|
344
|
+
tell code from a comment — `SEC-004` and `SEC-007` fired identically on a live
|
|
345
|
+
statement and on a commented-out block, which cost 31 % of the `high` findings
|
|
346
|
+
in a field evaluation. Comments are blanked **here**, at the three points a
|
|
347
|
+
file is read, rather than inside each rule: a rule added later is covered by
|
|
348
|
+
construction instead of by whoever remembers. `source_text` preserves offsets,
|
|
349
|
+
so the line each observation reports is still the line in the real file.
|
|
350
|
+
"""
|
|
351
|
+
from sourcecode.source_text import (
|
|
352
|
+
blank_hash_comments,
|
|
353
|
+
blank_java_comments,
|
|
354
|
+
blank_xml_comments,
|
|
355
|
+
)
|
|
356
|
+
|
|
342
357
|
root = Path(root)
|
|
343
358
|
if java_files is None:
|
|
344
359
|
try:
|
|
@@ -353,7 +368,7 @@ def scan_security_configuration(
|
|
|
353
368
|
source = (root / rel).read_text(encoding="utf-8", errors="replace")
|
|
354
369
|
except OSError:
|
|
355
370
|
continue
|
|
356
|
-
out.extend(_scan_java(source, rel))
|
|
371
|
+
out.extend(_scan_java(blank_java_comments(source), rel))
|
|
357
372
|
|
|
358
373
|
if config_files is None:
|
|
359
374
|
config_files = []
|
|
@@ -367,7 +382,7 @@ def scan_security_configuration(
|
|
|
367
382
|
text = (root / rel).read_text(encoding="utf-8", errors="replace")
|
|
368
383
|
except OSError:
|
|
369
384
|
continue
|
|
370
|
-
out.extend(_scan_config(text, rel))
|
|
385
|
+
out.extend(_scan_config(blank_hash_comments(text), rel))
|
|
371
386
|
|
|
372
387
|
# CL-10 — the descriptors. Discovered the same way `environment_resolution`
|
|
373
388
|
# classifies them, so the two surfaces cannot disagree about which files are
|
|
@@ -386,7 +401,7 @@ def scan_security_configuration(
|
|
|
386
401
|
text = (root / rel).read_text(encoding="utf-8", errors="replace")
|
|
387
402
|
except OSError:
|
|
388
403
|
continue
|
|
389
|
-
out.extend(_scan_descriptor(text, rel))
|
|
404
|
+
out.extend(_scan_descriptor(blank_xml_comments(text), rel))
|
|
390
405
|
|
|
391
406
|
return sorted(
|
|
392
407
|
_shared_across_environments(out), key=lambda o: (o.rule, o.file, o.line)
|
|
@@ -144,9 +144,15 @@ def build_servlet_surface(root: Path, *, limit: Optional[int] = None) -> dict:
|
|
|
144
144
|
if any(part in _SKIP_DIRS for part in path.parts) or not path.is_file():
|
|
145
145
|
continue
|
|
146
146
|
try:
|
|
147
|
-
|
|
147
|
+
# POSIX, like every other path this CLI publishes. C2-18 fixed this
|
|
148
|
+
# for `spring_profiles.conditional_beans[].source_file`; this module
|
|
149
|
+
# shipped two releases later without the convention, so one response
|
|
150
|
+
# could carry `src/main/java/...` beside `src\main\options\...`
|
|
151
|
+
# and defeat a consumer joining on path — which is what a servlet
|
|
152
|
+
# population is for (C2-24).
|
|
153
|
+
relative = path.relative_to(root).as_posix()
|
|
148
154
|
except ValueError:
|
|
149
|
-
relative =
|
|
155
|
+
relative = Path(path).as_posix()
|
|
150
156
|
if path.name == "web.xml":
|
|
151
157
|
mappings.extend(_from_web_xml(path, relative))
|
|
152
158
|
elif path.suffix == ".java":
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
"""source_text.py — the one authority for "this text, with comments neutralised".
|
|
2
|
+
|
|
3
|
+
A static analyzer that matches raw text cannot tell code from a note about code.
|
|
4
|
+
This project has now paid for that three times:
|
|
5
|
+
|
|
6
|
+
* **E-1 (3.7.0)** — a comment containing `class` before an annotated declaration
|
|
7
|
+
swallowed the real declaration, and the file left the graph entirely.
|
|
8
|
+
* **E-2 (4.5.3)** — the security configuration scan reported `SEC-004` and
|
|
9
|
+
`SEC-007` on commented-out code, which cost **31 % of the `high` findings** in a
|
|
10
|
+
field evaluation. Worse than the count: the correlation engine wrote a
|
|
11
|
+
persuasive paragraph about credentials in a block that was switched off.
|
|
12
|
+
Confident prose about dead code is worse than a terse warning, because it
|
|
13
|
+
invites trust.
|
|
14
|
+
|
|
15
|
+
**Offsets are preserved exactly.** Comment bodies are overwritten with spaces and
|
|
16
|
+
every newline is kept, so the result has the same length and the same line
|
|
17
|
+
structure as the input. A match found in the blanked text has the same
|
|
18
|
+
`start()`, and therefore the same reported line, as it would in the original —
|
|
19
|
+
which is what lets a scanner keep quoting the source file it read.
|
|
20
|
+
|
|
21
|
+
**String literals are deliberately kept.** This is the difference between these
|
|
22
|
+
functions and `hibernate_strat._strip_comments_strings`, which blanks literal
|
|
23
|
+
*content* on purpose so that a pattern cannot match a substring living inside a
|
|
24
|
+
string. The security rules need the opposite: `new MessageDigestPasswordEncoder(
|
|
25
|
+
"SHA-1")` and `<param-value>admin:{SHA-256}…</param-value>` carry their evidence
|
|
26
|
+
*in* the literal. Two different questions, two functions, neither a copy of the
|
|
27
|
+
other.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
from __future__ import annotations
|
|
31
|
+
|
|
32
|
+
import re
|
|
33
|
+
|
|
34
|
+
#: Java/C-family lexical tokens, in precedence order. String and character
|
|
35
|
+
#: literals are matched **first** so that a `//` or `/*` living inside one — as in
|
|
36
|
+
#: `String url = "http://example.com";` — is never mistaken for a comment.
|
|
37
|
+
_JAVA_TOKENS = re.compile(
|
|
38
|
+
r'"""(?:\\.|[^\\])*?"""' # text block (Java 15+), before the plain string
|
|
39
|
+
r'|"(?:\\.|[^"\\\n])*"' # string literal
|
|
40
|
+
r"|'(?:\\.|[^'\\\n])*'" # char literal
|
|
41
|
+
r"|(?P<line>//[^\n]*)" # line comment
|
|
42
|
+
r"|(?P<block>/\*.*?\*/)", # block comment
|
|
43
|
+
re.DOTALL,
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
#: XML/HTML comments. Unterminated ones run to end of input on purpose: an
|
|
47
|
+
#: unclosed `<!--` comments out the rest of the document for a parser too.
|
|
48
|
+
_XML_COMMENT = re.compile(r"<!--.*?(?:-->|\Z)", re.DOTALL)
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
def _blank(text: str) -> str:
|
|
52
|
+
"""Same length, same newlines, no content."""
|
|
53
|
+
return "".join("\n" if ch == "\n" else " " for ch in text)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def blank_java_comments(source: str) -> str:
|
|
57
|
+
"""Java source with comment bodies replaced by spaces, offsets preserved.
|
|
58
|
+
|
|
59
|
+
String and character literals are left intact — see the module docstring.
|
|
60
|
+
"""
|
|
61
|
+
def _replace(match: "re.Match[str]") -> str:
|
|
62
|
+
if match.lastgroup in ("line", "block"):
|
|
63
|
+
return _blank(match.group(0))
|
|
64
|
+
return match.group(0) # a literal: untouched
|
|
65
|
+
|
|
66
|
+
return _JAVA_TOKENS.sub(_replace, source)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def blank_xml_comments(source: str) -> str:
|
|
70
|
+
"""XML/HTML with comment bodies replaced by spaces, offsets preserved."""
|
|
71
|
+
return _XML_COMMENT.sub(lambda m: _blank(m.group(0)), source)
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
#: A `#` comment in `.properties`/YAML, but only where it opens the line. A `#`
|
|
75
|
+
#: further along may be a comment in YAML and is part of the value in
|
|
76
|
+
#: `.properties` (`password=abc#123`), and blanking that would corrupt evidence to
|
|
77
|
+
#: avoid a false positive — the wrong trade for a scan that quotes what it found.
|
|
78
|
+
#: The fully-commented-out block E-2 is about is covered by the line-start form.
|
|
79
|
+
_HASH_COMMENT_LINE = re.compile(r"^[ \t]*#[^\n]*", re.MULTILINE)
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def blank_hash_comments(source: str) -> str:
|
|
83
|
+
"""`.properties`/YAML with whole-line `#` comments blanked, offsets preserved."""
|
|
84
|
+
return _HASH_COMMENT_LINE.sub(lambda m: _blank(m.group(0)), source)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def commented_spans(source: str, *, xml: bool) -> "list[tuple[int, int]]":
|
|
88
|
+
"""`(start, end)` offsets of every comment — what was switched off, not what runs.
|
|
89
|
+
|
|
90
|
+
The inverse of the functions above, and the reason they blank rather than
|
|
91
|
+
delete. A control that is present but commented out is not noise: somebody
|
|
92
|
+
decided it was needed and then turned it off, which is the most actionable
|
|
93
|
+
thing a reader can be told about a security surface. E-2's fix removes those
|
|
94
|
+
matches from the findings; this is what a future rule reports them *as*
|
|
95
|
+
(DEAD-001, queued as F-A). Nothing consumes it yet — it exists so the two
|
|
96
|
+
halves cannot drift apart when it does.
|
|
97
|
+
"""
|
|
98
|
+
pattern = _XML_COMMENT if xml else _JAVA_TOKENS
|
|
99
|
+
spans: "list[tuple[int, int]]" = []
|
|
100
|
+
for match in pattern.finditer(source):
|
|
101
|
+
if xml or match.lastgroup in ("line", "block"):
|
|
102
|
+
spans.append((match.start(), match.end()))
|
|
103
|
+
return spans
|