sourcecode 4.0.0__tar.gz → 4.0.1__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.0.0 → sourcecode-4.0.1}/CHANGELOG.md +41 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/PKG-INFO +50 -4
- {sourcecode-4.0.0 → sourcecode-4.0.1}/README.md +49 -3
- {sourcecode-4.0.0 → sourcecode-4.0.1}/pyproject.toml +1 -1
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/__init__.py +1 -1
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/cli.py +51 -14
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/remedies.py +17 -0
- sourcecode-4.0.1/src/sourcecode/rule_catalog.py +107 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_security_audit.py +3 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/.github/workflows/build-windows.yml +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/.gitignore +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/.ruff.toml +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/CONTRIBUTING.md +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/LICENSE +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/SECURITY.md +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/raw +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/scripts/compare_integration_engines.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/scripts/customer_smoke_test.sh +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/scripts/generate_jdk_exports.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/scripts/perf_harness.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/adaptive_scanner.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/archetype.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/architectural_baseline.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/architectural_delta.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/architecture_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/architecture_summary.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/ast_extractor.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/cache.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/cache_model.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/call_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/caller_metrics.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/caller_reach.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/canonical_ir.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/chain_rules.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/change_plan.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/cir_graphs.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/classifier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/code_notes_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/compare.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/confidence_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/constraint_diff.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/context_cache.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/context_graph.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/context_scorer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/context_summarizer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/contract_diff.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/contract_init.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/contract_model.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/contract_pipeline.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/coverage_parser.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/defect_identity.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/degradation.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/dependency_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/deployment_prefix.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/base.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/csproj_parser.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/dart.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/dotnet.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/elixir.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/go.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/heuristic.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/hybrid.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/java.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/jvm_ext.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/nodejs.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/parsers.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/php.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/project.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/python.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/ruby.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/rust.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/systems.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/terraform.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/detectors/tooling.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/doc_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/dynamic_argument_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/endpoint_literals.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/endpoint_metrics.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/entrypoint_classifier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/env_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/envelope.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/environment_resolution.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/error_schema.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/evidence_provider.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/explain.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/facts/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/facts/registry.json +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/file_chunker.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/file_classifier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/filter_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/format_contract.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/fqn_utils.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/git_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/git_checkout.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/graph_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/graph_evidence.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/hibernate_strat.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/identity_fallback.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/integration_coordinates.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/jdk_exports.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/license.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/onboarding/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/onboarding/applier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/onboarding/backup.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/onboarding/detector.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/onboarding/planner.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/orchestrator.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/registry.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/runner.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp/server.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/mcp_nudge.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/metrics_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/migrate_check.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/migration_blast.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/non_coverage.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/openapi_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/output_budget.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/parse_cache.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/path_admission.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/path_filters.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/perf.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/pipe_contract.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/posture.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/pr_comment_renderer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/pr_impact.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/prepare_context.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/progress.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/ranking_engine.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/readiness_timeline.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/reconciliation.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/redactor.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/reference_facts.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/relevance_scorer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/rename_refactor.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/repo_classifier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/repository_ir.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/context.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/errors.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/executor.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/planner.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/query.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/request.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/resolution.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/result.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/retriever.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/runtime.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_endpoint.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_graph.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_impact.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_intf.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_struct.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/retrieval/steps_txsec.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/ris.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/risk.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/runtime_classifier.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/scanner.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/schema.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/schemas/envelope-v1.schema.json +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/security_config.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/security_config_scan.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/security_posture.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/semantic_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/semantic_impact_engine.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/semantic_integration_engine.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/semantic_services.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/serializer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_event_topology.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_findings.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_impact.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_model.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_profiles.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_properties.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_semantic.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/spring_tx_analyzer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/summarizer.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/target_admission.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/__init__.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/config.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/consent.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/events.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/filters.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/telemetry/transport.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/test_gap_ranking.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/test_sources.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/token_estimate.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/tree_utils.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/type_usage_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/validation_inference.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/validation_surface.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/verify_edit.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/verify_repo.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/verify_rules.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/version_check.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/src/sourcecode/workspace.py +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/functions/README.md +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/functions/get-license/index.ts +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/functions/lemonsqueezy-webhook/index.ts +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/functions/telemetry/index.ts +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/sql/license_event_ordering.sql +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/sql/licensing_schema.sql +0 -0
- {sourcecode-4.0.0 → sourcecode-4.0.1}/supabase/sql/telemetry_events.sql +0 -0
|
@@ -2,6 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.0.1] — 2026-08-02
|
|
6
|
+
|
|
7
|
+
Patch, and the criterion is the one this project has used since 3.2.0: nothing here
|
|
8
|
+
moves a published field, an exit code or a default. Three surfaces stopped
|
|
9
|
+
misdescribing an engine that did not change — the release advertised half its own
|
|
10
|
+
security ruleset, the front page led to neither capability the field asked for by
|
|
11
|
+
name, and the document both of them call the full command reference covered fewer
|
|
12
|
+
than half the commands. All three were found in-house.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`spring-audit --help` advertised three security rules and the build ships six**
|
|
17
|
+
(C4-9). The one-liner the root panel prints, and the command's own screen, said
|
|
18
|
+
`security surface (SEC-001..003)` two releases after CL-9 shipped SEC-004, SEC-005
|
|
19
|
+
and SEC-006; `docs/PRODUCT_TIERS.md` said `TX-001..005 + SEC-001..003` and
|
|
20
|
+
`docs/USER_GUIDE.md` said `TX-001..TX-005`. The engine was right in every case —
|
|
21
|
+
the product advertised half its ruleset, in the release that answered the field's
|
|
22
|
+
complaint about security recall. `sourcecode/rule_catalog.py` now holds one summary
|
|
23
|
+
line per rule and the help renders from it, while the id *set* is derived from the
|
|
24
|
+
shipped pattern registries by `tests/test_rule_catalog.py`: a seventh rule fails the
|
|
25
|
+
build until it is documented, and no surface may name a rule the build cannot emit.
|
|
26
|
+
The non-coverage block in the same help is rendered from `non_coverage` too, which
|
|
27
|
+
had a renderer with no consumer beside four rows restated by hand.
|
|
28
|
+
- **The front page did not lead to either remedy that C4-8 closed** (C4-10). The
|
|
29
|
+
pointer lived in the payload that states the limitation, which reaches an agent and
|
|
30
|
+
not a reader who has not run the command yet — the same reader who wrote
|
|
31
|
+
`ask posture --provenance` and `ask contracts init` by hand after a full session.
|
|
32
|
+
`ask --help` now renders a *Questions the repository alone does not answer* block
|
|
33
|
+
from `remedies.REMEDIES`, and lists `risk .` in Start here; the README carries
|
|
34
|
+
`--resolve-environments`, `verify --init` and `ask risk` in the Quickstart,
|
|
35
|
+
`--resolve-environments` in §5b beside the `--diff` it qualifies, and a §5c for
|
|
36
|
+
composed risk. `tests/test_remedies.py` fails when a remedy is absent from either
|
|
37
|
+
surface.
|
|
38
|
+
- **The "full command reference" documented 14 of 32 commands** (C4-11). `verify`,
|
|
39
|
+
`verify-edit` and `pr-impact` — the three gate commands — appeared only as names in
|
|
40
|
+
the tier table, and eight more had no occurrence in the file at all. Sections added
|
|
41
|
+
for the gate family (with the exit-code contract they share), for `plan`, `compare`,
|
|
42
|
+
`delta` and `contract-diff`, and a table for the rest.
|
|
43
|
+
`tests/test_user_guide_coverage.py` takes the command population from the CLI and
|
|
44
|
+
parses every invocation the guide prints against it.
|
|
45
|
+
|
|
5
46
|
## [4.0.0] — 2026-08-02
|
|
6
47
|
|
|
7
48
|
Major because two published `gate_coverage` fields are removed, not because the
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: sourcecode
|
|
3
|
-
Version: 4.0.
|
|
3
|
+
Version: 4.0.1
|
|
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
|
|
@@ -129,6 +129,26 @@ ask spring-audit /path/to/repo
|
|
|
129
129
|
ask migrate-check /path/to/repo --compact
|
|
130
130
|
```
|
|
131
131
|
|
|
132
|
+
**Then the three that finish the sentence the four above start.** A profile-set answer stays
|
|
133
|
+
conditional until you know which set the deployment starts with, a gate nobody can populate is
|
|
134
|
+
a gate nobody runs, and a defect ranked without its reach is a linter note:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
# Which profile set ACTUALLY runs? Reads every artefact that can set it — build,
|
|
138
|
+
# descriptors, Dockerfile, Compose, Kubernetes, launch scripts — with file:line, and
|
|
139
|
+
# says NOT DECIDED IN THIS REPOSITORY when nothing in the tree decides it. Then ranks
|
|
140
|
+
# every admissible set by what it leaves reachable without authentication.
|
|
141
|
+
ask posture /path/to/repo --resolve-environments
|
|
142
|
+
|
|
143
|
+
# Derive the contracts this repository already satisfies, execute each one before
|
|
144
|
+
# writing it, and populate .ask/contracts.yml from the measurement instead of by hand.
|
|
145
|
+
ask verify /path/to/repo --init
|
|
146
|
+
|
|
147
|
+
# What each defect actually costs once reach, access and write effect are in it:
|
|
148
|
+
# severity × reachability × auth_verdict × write_effect, ordered, every factor traceable.
|
|
149
|
+
ask risk /path/to/repo
|
|
150
|
+
```
|
|
151
|
+
|
|
132
152
|
Then the everyday loop:
|
|
133
153
|
|
|
134
154
|
```bash
|
|
@@ -145,13 +165,17 @@ ask onboard /path/to/repo
|
|
|
145
165
|
ask review-pr /path/to/repo --since main
|
|
146
166
|
|
|
147
167
|
# CI gate on NEW violations only, instead of on pre-existing debt
|
|
168
|
+
ask verify /path/to/repo --init # derive the contracts, don't hand-write them
|
|
148
169
|
ask verify /path/to/repo --capture-baseline # accept today's debt, once
|
|
149
170
|
ask verify /path/to/repo # then: only new violations block
|
|
150
171
|
```
|
|
151
172
|
|
|
152
|
-
> **Adopting a gate on a real codebase.**
|
|
153
|
-
>
|
|
154
|
-
>
|
|
173
|
+
> **Adopting a gate on a real codebase.** Nobody hand-writes contracts for a 3 000-file
|
|
174
|
+
> monolith, so `ask verify --init` derives the ones the repository satisfies **today** and
|
|
175
|
+
> executes each before writing it — init followed by verify passes by construction.
|
|
176
|
+
> A repository that starts declaring contracts already violates them somewhere; `ask verify`
|
|
177
|
+
> is **baseline-relative by default** (`--fail-on new`) so the gate survives contact with
|
|
178
|
+
> reality instead of being switched off on day one.
|
|
155
179
|
> `ask baseline capture|diff|trend` is a different thing: versioned architectural metrics over
|
|
156
180
|
> time, for trend reporting rather than blocking.
|
|
157
181
|
|
|
@@ -194,10 +218,32 @@ What a profile set **actually wires**: which conditional beans register, which d
|
|
|
194
218
|
`ask posture` · `ask posture --diff dev:prod` · `ask posture --property k=v`
|
|
195
219
|
→ [posture.md](docs/posture.md)
|
|
196
220
|
|
|
221
|
+
**`--resolve-environments` carries it past the edge of the repository.** Every answer above
|
|
222
|
+
is conditional on a profile set, and which set runs is decided by the deployment. This flag
|
|
223
|
+
reads every artefact in the tree that can set `spring.profiles.active` — Maven/Gradle,
|
|
224
|
+
`web.xml`, `Dockerfile`, Compose, Kubernetes, launch scripts — with `file:line`, and returns
|
|
225
|
+
`decided_in_repository`, `artefacts_disagree` (two deployments, never resolved by picking
|
|
226
|
+
one) or **`NOT DECIDED IN THIS REPOSITORY`**. Then it resolves every admissible set over one
|
|
227
|
+
parse and publishes the worst. A field audit closed those five hops by hand with grep before
|
|
228
|
+
the flag existed.
|
|
229
|
+
`ask posture . --resolve-environments`
|
|
230
|
+
|
|
197
231
|
Unresolved is a first-class outcome: a condition the resolver cannot decide is reported as a
|
|
198
232
|
hole with the condition named, never folded into active or inactive. **A posture answer that
|
|
199
233
|
guesses is a confident security falsehood — the worst failure mode this tool has.**
|
|
200
234
|
|
|
235
|
+
### 5c · Composed Risk *(experimental)*
|
|
236
|
+
Every command above answers one axis, and a reader composes them by hand. `ask risk` does the
|
|
237
|
+
join over the endpoint and symbol ids these commands already share with each other:
|
|
238
|
+
`severity_effective = defect_severity × reachability × auth_verdict × write_effect`. The
|
|
239
|
+
product of the published factors **is** the published score — decomposable to the four
|
|
240
|
+
figures and the authority behind each — and an axis this build cannot measure is `unknown`,
|
|
241
|
+
weighted 1.0 and named in `blind_axes` rather than silently treated as safe. Measured on a
|
|
242
|
+
field case: a defect both `spring-audit` and `impact-chain` called *medium* composes to
|
|
243
|
+
**high (8.06)** once *reachable unauthenticated* and *writes to the database* are in it.
|
|
244
|
+
`ask risk .` · `ask risk . --min-band high` · `ask risk . --limit 10`
|
|
245
|
+
→ [reference](docs/USER_GUIDE.md)
|
|
246
|
+
|
|
201
247
|
### 6 · Developer Workflows
|
|
202
248
|
The everyday loop: diff-based PR review, symptom-driven bug triage, and delta context for continuous agent runs.
|
|
203
249
|
`ask review-pr` · `ask fix-bug` · `ask prepare-context`
|
|
@@ -91,6 +91,26 @@ ask spring-audit /path/to/repo
|
|
|
91
91
|
ask migrate-check /path/to/repo --compact
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
+
**Then the three that finish the sentence the four above start.** A profile-set answer stays
|
|
95
|
+
conditional until you know which set the deployment starts with, a gate nobody can populate is
|
|
96
|
+
a gate nobody runs, and a defect ranked without its reach is a linter note:
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
# Which profile set ACTUALLY runs? Reads every artefact that can set it — build,
|
|
100
|
+
# descriptors, Dockerfile, Compose, Kubernetes, launch scripts — with file:line, and
|
|
101
|
+
# says NOT DECIDED IN THIS REPOSITORY when nothing in the tree decides it. Then ranks
|
|
102
|
+
# every admissible set by what it leaves reachable without authentication.
|
|
103
|
+
ask posture /path/to/repo --resolve-environments
|
|
104
|
+
|
|
105
|
+
# Derive the contracts this repository already satisfies, execute each one before
|
|
106
|
+
# writing it, and populate .ask/contracts.yml from the measurement instead of by hand.
|
|
107
|
+
ask verify /path/to/repo --init
|
|
108
|
+
|
|
109
|
+
# What each defect actually costs once reach, access and write effect are in it:
|
|
110
|
+
# severity × reachability × auth_verdict × write_effect, ordered, every factor traceable.
|
|
111
|
+
ask risk /path/to/repo
|
|
112
|
+
```
|
|
113
|
+
|
|
94
114
|
Then the everyday loop:
|
|
95
115
|
|
|
96
116
|
```bash
|
|
@@ -107,13 +127,17 @@ ask onboard /path/to/repo
|
|
|
107
127
|
ask review-pr /path/to/repo --since main
|
|
108
128
|
|
|
109
129
|
# CI gate on NEW violations only, instead of on pre-existing debt
|
|
130
|
+
ask verify /path/to/repo --init # derive the contracts, don't hand-write them
|
|
110
131
|
ask verify /path/to/repo --capture-baseline # accept today's debt, once
|
|
111
132
|
ask verify /path/to/repo # then: only new violations block
|
|
112
133
|
```
|
|
113
134
|
|
|
114
|
-
> **Adopting a gate on a real codebase.**
|
|
115
|
-
>
|
|
116
|
-
>
|
|
135
|
+
> **Adopting a gate on a real codebase.** Nobody hand-writes contracts for a 3 000-file
|
|
136
|
+
> monolith, so `ask verify --init` derives the ones the repository satisfies **today** and
|
|
137
|
+
> executes each before writing it — init followed by verify passes by construction.
|
|
138
|
+
> A repository that starts declaring contracts already violates them somewhere; `ask verify`
|
|
139
|
+
> is **baseline-relative by default** (`--fail-on new`) so the gate survives contact with
|
|
140
|
+
> reality instead of being switched off on day one.
|
|
117
141
|
> `ask baseline capture|diff|trend` is a different thing: versioned architectural metrics over
|
|
118
142
|
> time, for trend reporting rather than blocking.
|
|
119
143
|
|
|
@@ -156,10 +180,32 @@ What a profile set **actually wires**: which conditional beans register, which d
|
|
|
156
180
|
`ask posture` · `ask posture --diff dev:prod` · `ask posture --property k=v`
|
|
157
181
|
→ [posture.md](docs/posture.md)
|
|
158
182
|
|
|
183
|
+
**`--resolve-environments` carries it past the edge of the repository.** Every answer above
|
|
184
|
+
is conditional on a profile set, and which set runs is decided by the deployment. This flag
|
|
185
|
+
reads every artefact in the tree that can set `spring.profiles.active` — Maven/Gradle,
|
|
186
|
+
`web.xml`, `Dockerfile`, Compose, Kubernetes, launch scripts — with `file:line`, and returns
|
|
187
|
+
`decided_in_repository`, `artefacts_disagree` (two deployments, never resolved by picking
|
|
188
|
+
one) or **`NOT DECIDED IN THIS REPOSITORY`**. Then it resolves every admissible set over one
|
|
189
|
+
parse and publishes the worst. A field audit closed those five hops by hand with grep before
|
|
190
|
+
the flag existed.
|
|
191
|
+
`ask posture . --resolve-environments`
|
|
192
|
+
|
|
159
193
|
Unresolved is a first-class outcome: a condition the resolver cannot decide is reported as a
|
|
160
194
|
hole with the condition named, never folded into active or inactive. **A posture answer that
|
|
161
195
|
guesses is a confident security falsehood — the worst failure mode this tool has.**
|
|
162
196
|
|
|
197
|
+
### 5c · Composed Risk *(experimental)*
|
|
198
|
+
Every command above answers one axis, and a reader composes them by hand. `ask risk` does the
|
|
199
|
+
join over the endpoint and symbol ids these commands already share with each other:
|
|
200
|
+
`severity_effective = defect_severity × reachability × auth_verdict × write_effect`. The
|
|
201
|
+
product of the published factors **is** the published score — decomposable to the four
|
|
202
|
+
figures and the authority behind each — and an axis this build cannot measure is `unknown`,
|
|
203
|
+
weighted 1.0 and named in `blind_axes` rather than silently treated as safe. Measured on a
|
|
204
|
+
field case: a defect both `spring-audit` and `impact-chain` called *medium* composes to
|
|
205
|
+
**high (8.06)** once *reachable unauthenticated* and *writes to the database* are in it.
|
|
206
|
+
`ask risk .` · `ask risk . --min-band high` · `ask risk . --limit 10`
|
|
207
|
+
→ [reference](docs/USER_GUIDE.md)
|
|
208
|
+
|
|
163
209
|
### 6 · Developer Workflows
|
|
164
210
|
The everyday loop: diff-based PR review, symptom-driven bug triage, and delta context for continuous agent runs.
|
|
165
211
|
`ask review-pr` · `ask fix-bug` · `ask prepare-context`
|
|
@@ -297,6 +297,27 @@ def _group_help_block() -> str:
|
|
|
297
297
|
return "\n".join(lines)
|
|
298
298
|
|
|
299
299
|
|
|
300
|
+
def _remedy_help_block() -> str:
|
|
301
|
+
"""The front page names the answers the field asked for as missing features.
|
|
302
|
+
|
|
303
|
+
C4-8 was closed in the payload: a limitation now carries the invocation that
|
|
304
|
+
resolves it. But the evaluator who wrote `ask posture --provenance` and `ask
|
|
305
|
+
contracts init` by hand was reading `--help` first, and neither shipped flag
|
|
306
|
+
was on it. The block is rendered from `remedies.REMEDIES`, so the pointer and
|
|
307
|
+
the capability cannot drift — `tests/test_remedies.py` already refuses a
|
|
308
|
+
remedy whose option the CLI does not accept.
|
|
309
|
+
"""
|
|
310
|
+
from sourcecode.remedies import headlines
|
|
311
|
+
|
|
312
|
+
lines = ["[bold]Questions the repository alone does not answer:[/bold]"]
|
|
313
|
+
for invocation, headline in headlines():
|
|
314
|
+
# Bare invocation, like every other block on this page: the header panels
|
|
315
|
+
# print without the `ask ` prefix (C3-16).
|
|
316
|
+
bare = invocation.removeprefix("ask ")
|
|
317
|
+
lines.append(f" {bare:<33}[dim]# {headline}[/dim]")
|
|
318
|
+
return "\n".join(lines)
|
|
319
|
+
|
|
320
|
+
|
|
300
321
|
def _build_help_text() -> str:
|
|
301
322
|
"""Build --help text dynamically based on current license state."""
|
|
302
323
|
try:
|
|
@@ -323,6 +344,9 @@ of files) in minutes. Semantic analysis itself is sub-second; repo indexing domi
|
|
|
323
344
|
endpoints . [dim]# endpoints + effective path + policy[/dim]
|
|
324
345
|
spring-audit . [dim]# TX anomalies + security surface[/dim]
|
|
325
346
|
migrate-check . --compact [dim]# Boot 2→3: located blockers + effort[/dim]
|
|
347
|
+
risk . [dim]# defects ranked by reach × access (exp.)[/dim]
|
|
348
|
+
|
|
349
|
+
{_remedy_help_block()}
|
|
326
350
|
|
|
327
351
|
[bold]Agent context:[/bold]
|
|
328
352
|
ask --compact [dim]# high-signal summary (~2,500–4,000 tokens)[/dim]
|
|
@@ -6438,19 +6462,11 @@ def spring_audit_cmd(
|
|
|
6438
6462
|
help=_NO_CACHE_SUBCOMMAND_HELP,
|
|
6439
6463
|
),
|
|
6440
6464
|
) -> None:
|
|
6441
|
-
"""Spring semantic audit:
|
|
6465
|
+
"""Spring semantic audit: {{RULE_SUMMARY}}.
|
|
6442
6466
|
|
|
6443
6467
|
\b
|
|
6444
6468
|
Detects:
|
|
6445
|
-
|
|
6446
|
-
TX-002 REQUIRES_NEW nested in REQUIRED call chain
|
|
6447
|
-
TX-003 readOnly=true boundary propagating to write operation
|
|
6448
|
-
TX-004 NOT_SUPPORTED/NEVER within active TX chain
|
|
6449
|
-
TX-005 Exception swallowing inside @Transactional
|
|
6450
|
-
TX-006 Self-invocation of @Transactional sibling (proxy bypass)
|
|
6451
|
-
SEC-001 Unsecured endpoint in annotation_based security model
|
|
6452
|
-
SEC-002 CVE-2025-41248: @PreAuthorize on inherited method from generic supertype
|
|
6453
|
-
SEC-003 @Transactional on @Controller/@RestController (TX in wrong layer)
|
|
6469
|
+
{{RULE_LIST}}
|
|
6454
6470
|
|
|
6455
6471
|
\b
|
|
6456
6472
|
Findings and defects are two counts, both published. A rule fires per
|
|
@@ -6482,10 +6498,7 @@ def spring_audit_cmd(
|
|
|
6482
6498
|
\b
|
|
6483
6499
|
What it does not look at (the whole list is in the payload's `non_coverage`,
|
|
6484
6500
|
and in the README):
|
|
6485
|
-
|
|
6486
|
-
NC-002 secrets outside Java and Spring configuration
|
|
6487
|
-
NC-003 filter-chain ORDER — a custom filter's presence is structural only
|
|
6488
|
-
NC-004 known vulnerabilities in dependencies — no CVE matching
|
|
6501
|
+
{{NON_COVERAGE}}
|
|
6489
6502
|
Silence about those is not evidence that there is nothing to find.
|
|
6490
6503
|
"""
|
|
6491
6504
|
import json as _json
|
|
@@ -6587,6 +6600,30 @@ def spring_audit_cmd(
|
|
|
6587
6600
|
raise typer.Exit(code=1)
|
|
6588
6601
|
|
|
6589
6602
|
|
|
6603
|
+
def _render_audit_help(doc: str) -> str:
|
|
6604
|
+
"""Fill the rule and non-coverage blocks from the tables that own them.
|
|
6605
|
+
|
|
6606
|
+
The rules this command detects were written out by hand in its own help, and
|
|
6607
|
+
stayed at `SEC-001..003` for two releases after CL-9 shipped three more — the
|
|
6608
|
+
C4-1 shape, where a curated list beside a generated one drifts and the
|
|
6609
|
+
curated one is what the reader gets. Both blocks are rendered now, so adding
|
|
6610
|
+
a rule updates the help by construction.
|
|
6611
|
+
"""
|
|
6612
|
+
from sourcecode import non_coverage, rule_catalog
|
|
6613
|
+
|
|
6614
|
+
nc_lines = [
|
|
6615
|
+
f" {line}" for line in non_coverage.render_help_lines("security_surface", width=68)
|
|
6616
|
+
]
|
|
6617
|
+
return (
|
|
6618
|
+
doc.replace("{{RULE_SUMMARY}}", rule_catalog.summary_line())
|
|
6619
|
+
.replace("{{RULE_LIST}}", rule_catalog.render_help_block())
|
|
6620
|
+
.replace("{{NON_COVERAGE}}", "\n".join(nc_lines))
|
|
6621
|
+
)
|
|
6622
|
+
|
|
6623
|
+
|
|
6624
|
+
spring_audit_cmd.__doc__ = _render_audit_help(spring_audit_cmd.__doc__ or "")
|
|
6625
|
+
|
|
6626
|
+
|
|
6590
6627
|
# ── verify-edit: in-loop semantic diff gate ───────────────────────────────────
|
|
6591
6628
|
|
|
6592
6629
|
|
|
@@ -38,6 +38,10 @@ class Remedy:
|
|
|
38
38
|
#: feature name — the evaluator's request was written in their own words, so
|
|
39
39
|
#: the pointer has to be recognisable from the question they were asking.
|
|
40
40
|
answers: str
|
|
41
|
+
#: The same thing in a help-screen column. A payload can afford the sentence;
|
|
42
|
+
#: a front page cannot, and rewriting it there by hand is how the pointer and
|
|
43
|
+
#: the capability drift apart.
|
|
44
|
+
headline: str = ""
|
|
41
45
|
|
|
42
46
|
@property
|
|
43
47
|
def invocation(self) -> str:
|
|
@@ -63,6 +67,7 @@ REMEDIES: "dict[str, Remedy]" = {
|
|
|
63
67
|
"this repository; to read every artefact that can set it with file:line "
|
|
64
68
|
"and rank the admissible sets by what they leave reachable"
|
|
65
69
|
),
|
|
70
|
+
headline="which profile set actually runs",
|
|
66
71
|
),
|
|
67
72
|
Remedy(
|
|
68
73
|
key="no_contracts_declared",
|
|
@@ -73,11 +78,23 @@ REMEDIES: "dict[str, Remedy]" = {
|
|
|
73
78
|
"and write `.ask/contracts.yml` from the current measurement instead of "
|
|
74
79
|
"by hand"
|
|
75
80
|
),
|
|
81
|
+
headline="derive contracts from measurement",
|
|
76
82
|
),
|
|
77
83
|
)
|
|
78
84
|
}
|
|
79
85
|
|
|
80
86
|
|
|
87
|
+
def headlines() -> "tuple[tuple[str, str], ...]":
|
|
88
|
+
"""`(invocation, headline)` for every remedy, for a help screen or a README.
|
|
89
|
+
|
|
90
|
+
Rendered rather than restated: the front page is where the evaluator looked
|
|
91
|
+
first and found neither of these commands.
|
|
92
|
+
"""
|
|
93
|
+
return tuple(
|
|
94
|
+
(r.invocation, r.headline or r.answers) for r in REMEDIES.values()
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
|
|
81
98
|
def remedy(key: str) -> Remedy:
|
|
82
99
|
"""The remedy for `key`. Raises rather than returning a silent blank, because a
|
|
83
100
|
missing pointer is exactly the defect this module exists to prevent."""
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
"""The rules `spring-audit` ships, in the one place every reader is rendered from.
|
|
2
|
+
|
|
3
|
+
The help screen used to name its own rules. It said `SEC-001..003` for two
|
|
4
|
+
releases after CL-9 shipped SEC-004, SEC-005 and SEC-006, so the product
|
|
5
|
+
advertised three security rules while holding six — the same shape as C4-1
|
|
6
|
+
(`endpoints` missing from every help list) and C4-8 (a shipped capability the
|
|
7
|
+
field asked for as a feature): a curated list beside a generated one drifts, and
|
|
8
|
+
the curated one is what a buyer reads.
|
|
9
|
+
|
|
10
|
+
Two seams keep this honest, and neither is a promise:
|
|
11
|
+
|
|
12
|
+
* The **summaries** live here, keyed by rule id, and `--help` renders from them
|
|
13
|
+
rather than restating them.
|
|
14
|
+
* The **id set** is not authored here at all. It belongs to the pattern
|
|
15
|
+
registries in `spring_tx_analyzer` and `spring_security_audit`, and
|
|
16
|
+
`tests/test_rule_catalog.py` derives it from them: a seventh SEC rule fails
|
|
17
|
+
the build until it has a line in this table. That import is deliberately not
|
|
18
|
+
done at module level — the analyzers are heavy and this module is imported to
|
|
19
|
+
render a help screen.
|
|
20
|
+
|
|
21
|
+
A pattern class that emits several ids declares them in `pattern_ids`; one that
|
|
22
|
+
emits its own declares nothing and is read through `pattern_id`.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
from __future__ import annotations
|
|
26
|
+
|
|
27
|
+
from dataclasses import dataclass
|
|
28
|
+
|
|
29
|
+
SCHEMA_VERSION = "rule-catalog-v1"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
@dataclass(frozen=True)
|
|
33
|
+
class Rule:
|
|
34
|
+
"""One audit rule, as a reader meets it."""
|
|
35
|
+
|
|
36
|
+
id: str
|
|
37
|
+
family: str # "TX" or "SEC" — the scope flag that selects it
|
|
38
|
+
summary: str # one line, no wrapping games
|
|
39
|
+
|
|
40
|
+
def to_dict(self) -> dict:
|
|
41
|
+
return {"id": self.id, "family": self.family, "summary": self.summary}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
RULES: tuple[Rule, ...] = (
|
|
45
|
+
Rule("TX-001", "TX", "@Transactional on private/final method (CGLIB proxy bypass)"),
|
|
46
|
+
Rule("TX-002", "TX", "REQUIRES_NEW nested in REQUIRED call chain"),
|
|
47
|
+
Rule("TX-003", "TX", "readOnly=true boundary propagating to write operation"),
|
|
48
|
+
Rule("TX-004", "TX", "NOT_SUPPORTED/NEVER within active TX chain"),
|
|
49
|
+
Rule("TX-005", "TX", "Exception swallowing inside @Transactional"),
|
|
50
|
+
Rule("TX-006", "TX", "Self-invocation of @Transactional sibling (proxy bypass)"),
|
|
51
|
+
Rule("SEC-001", "SEC", "Unsecured endpoint in annotation_based security model"),
|
|
52
|
+
Rule("SEC-002", "SEC", "CVE-2025-41248: @PreAuthorize inherited from a generic supertype"),
|
|
53
|
+
Rule("SEC-003", "SEC", "@Transactional on @Controller/@RestController (TX in wrong layer)"),
|
|
54
|
+
Rule("SEC-004", "SEC", "Password stored under a fast unsalted digest (SHA-1, MD5)"),
|
|
55
|
+
Rule("SEC-005", "SEC", "CSRF disabled under session-bearing authentication"),
|
|
56
|
+
Rule("SEC-006", "SEC", "Cookie created without the Secure attribute"),
|
|
57
|
+
)
|
|
58
|
+
|
|
59
|
+
FAMILY_ORDER: tuple[str, ...] = ("TX", "SEC")
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def ids() -> tuple[str, ...]:
|
|
63
|
+
"""Every rule id this build can emit, in help order."""
|
|
64
|
+
return tuple(rule.id for rule in RULES)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def for_family(family: str) -> tuple[Rule, ...]:
|
|
68
|
+
return tuple(rule for rule in RULES if rule.family == family)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def range_label(family: str) -> str:
|
|
72
|
+
"""`TX-001..006` — the span a one-line summary can carry without lying."""
|
|
73
|
+
rules = for_family(family)
|
|
74
|
+
if not rules:
|
|
75
|
+
return ""
|
|
76
|
+
if len(rules) == 1:
|
|
77
|
+
return rules[0].id
|
|
78
|
+
first, last = rules[0].id, rules[-1].id
|
|
79
|
+
_, _, tail = last.partition("-")
|
|
80
|
+
return f"{first}..{tail}"
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def summary_line() -> str:
|
|
84
|
+
"""The command's one-liner: the families and how far each one reaches."""
|
|
85
|
+
return " + ".join(
|
|
86
|
+
f"{'TX anomalies' if fam == 'TX' else 'security surface'} "
|
|
87
|
+
f"({range_label(fam)})"
|
|
88
|
+
for fam in FAMILY_ORDER
|
|
89
|
+
if for_family(fam)
|
|
90
|
+
)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def help_lines(*, indent: str = " ") -> list[str]:
|
|
94
|
+
"""One line per rule, id column aligned, for a `--help` screen."""
|
|
95
|
+
width = max(len(rule.id) for rule in RULES)
|
|
96
|
+
return [f"{indent}{rule.id.ljust(width)} {rule.summary}" for rule in RULES]
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def render_help_block(*, indent: str = " ") -> str:
|
|
100
|
+
return "\n".join(help_lines(indent=indent))
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def render_markdown() -> str:
|
|
104
|
+
"""The docs table, generated from the same tuple."""
|
|
105
|
+
lines = ["| Rule | Detects |", "|---|---|"]
|
|
106
|
+
lines.extend(f"| `{rule.id}` | {rule.summary} |" for rule in RULES)
|
|
107
|
+
return "\n".join(lines)
|
|
@@ -406,6 +406,9 @@ class _SEC00XConfigurationWeaknesses:
|
|
|
406
406
|
"SEC-005": "CSRF protection disabled under session-bearing authentication",
|
|
407
407
|
"SEC-006": "Cookie created without the Secure attribute",
|
|
408
408
|
}
|
|
409
|
+
#: A pattern that emits more than one rule id declares them all, so a catalogue
|
|
410
|
+
#: reader is never left with the group's identity standing for the group.
|
|
411
|
+
pattern_ids = tuple(_TITLE)
|
|
409
412
|
_FIX = {
|
|
410
413
|
"SEC-004": (
|
|
411
414
|
"Store passwords with an adaptive, salted function — BCryptPasswordEncoder, "
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|