feat(security): add repowise security scan --history for full git-history secret scanning#821
feat(security): add repowise security scan --history for full git-history secret scanning#821Ayush7614 wants to merge 5 commits into
Conversation
…tory secret scanning Implements repowise-dev#818. Reuses the existing SecurityScanner pattern registry and the shared security_findings table rather than forking a new subsystem. Changes: - security_scan: extend persist() to carry commit_sha/commit_at and make re-runs idempotent via ON CONFLICT DO NOTHING / INSERT OR IGNORE. - history_scan: new HistorySecurityScanner that scans unique git blobs (git rev-list --objects --all, deduped by blob SHA) once each and attributes hits to the first-introducing commit. Defaults to the secret-oriented patterns (hardcoded_password/hardcoded_secret) to avoid code-smell noise. - models + migration 0037: add nullable commit_sha/commit_at and a uq_security_finding_provenance unique constraint for dedup. - CLI: repowise security scan --history [--since <rev>] [--to <rev>] [--all-patterns] [--output json]. - server: GET /security gains a history filter; responses include commit_sha + found_in_history so the UI can tell history from working tree. Addresses maintainer review: idempotent persist, mandatory schema migration, symbol scan disabled in history (no parsed symbols on raw blobs), secret-only gate, and blob-dedup scan strategy.
|
✅ Health: 7.5 (unchanged) 📋 At a glance 📌 Before you merge
🗺️ Change map flowchart LR
subgraph PR ["Changed in this PR (5 with dependents)"]
f_packages_cli_src_repowise_cli_main_py[".../cli/main.py 🔥"]:::changed
f_packages_core_src_repowise_core_analysis_security_scan_py[".../analysis/security_scan.py 🔥"]:::changed
f_packages_core_src_repowise_core_persistence_models_py[".../persistence/models.py 🔥"]:::changed
f_packages_server_src_repowise_server_schemas_code_quality_py[".../schemas/code_quality.py 🔥"]:::changed
f_packages_server_src_repowise_server_routers_security_py[".../routers/security.py"]:::changed
end
f_packages_cli_src_repowise_cli_commands_init_cmd_command_py[".../init_cmd/command.py"]
f_packages_cli_src_repowise_cli_main_py --> f_packages_cli_src_repowise_cli_commands_init_cmd_command_py
f_packages_cli_src_repowise_cli_helpers_py[".../cli/helpers.py"]
f_packages_cli_src_repowise_cli_main_py --> f_packages_cli_src_repowise_cli_helpers_py
f_packages_core_src_repowise_core_pipeline_persist_py[".../pipeline/persist.py"]
f_packages_core_src_repowise_core_analysis_security_scan_py --> f_packages_core_src_repowise_core_pipeline_persist_py
f_packages_cli_src_repowise_cli_commands_decision_cmd_py[".../commands/decision_cmd.py"]
f_packages_core_src_repowise_core_persistence_models_py --> f_packages_cli_src_repowise_cli_commands_decision_cmd_py
f_packages_cli_src_repowise_cli_commands_delete_cmd_py[".../commands/delete_cmd.py"]
f_packages_core_src_repowise_core_persistence_models_py --> f_packages_cli_src_repowise_cli_commands_delete_cmd_py
f_packages_cli_src_repowise_cli_commands_doctor_cmd_repo_checks_py[".../doctor_cmd/repo_checks.py"]
f_packages_core_src_repowise_core_persistence_models_py --> f_packages_cli_src_repowise_cli_commands_doctor_cmd_repo_checks_py
f_packages_cli_src_repowise_cli_commands_export_cmd_py[".../commands/export_cmd.py"]
f_packages_core_src_repowise_core_persistence_models_py --> f_packages_cli_src_repowise_cli_commands_export_cmd_py
f_packages_server_src_repowise_server_schemas___init___py[".../schemas/__init__.py"]
f_packages_server_src_repowise_server_schemas_code_quality_py --> f_packages_server_src_repowise_server_schemas___init___py
f_packages_server_src_repowise_server_app_py[".../server/app.py"]
f_packages_server_src_repowise_server_routers_security_py --> f_packages_server_src_repowise_server_app_py
more(["+111 more dependents"])
PR --> more
w_packages_core_src_repowise_core_pipeline_persist_py(["⚠️ .../pipeline/persist.py changed together 15×, not in PR"]):::warn
f_packages_core_src_repowise_core_persistence_models_py -.- w_packages_core_src_repowise_core_pipeline_persist_py
w_tests_unit_persistence_test_models_py(["⚠️ .../persistence/test_models.py changed together 14×, not in PR"]):::warn
f_packages_core_src_repowise_core_persistence_models_py -.- w_tests_unit_persistence_test_models_py
t_tests_integration_test_cli_py(["✅ tests/integration/test_cli.py"]):::guard
t_tests_integration_test_cli_py -.-> f_packages_cli_src_repowise_cli_main_py
t_tests_unit_analysis_test_security_scan_py(["✅ .../analysis/test_security_scan.py"]):::guard
t_tests_unit_analysis_test_security_scan_py -.-> f_packages_core_src_repowise_core_analysis_security_scan_py
t_tests_integration_persistence_test_embed_batch_pg_py(["✅ .../persistence/test_embed_batch_pg.py"]):::guard
t_tests_integration_persistence_test_embed_batch_pg_py -.-> f_packages_core_src_repowise_core_persistence_models_py
classDef changed fill:#dbeafe,stroke:#1d4ed8,color:#1e3a5f
classDef warn fill:#fef3c7,stroke:#b45309,color:#78350f
classDef guard fill:#dcfce7,stroke:#15803d,color:#14532d
Solid arrows: code that imports the changed files (120 direct dependents, from the last indexed snapshot). Dashed: history/tests. 🚨 Change risk: high (riskier than 80% of this repo's commits)
🩹 Review priority (files here with the most recent bug-fix history — defects cluster, so review these first)
🔎 More signals (2)🔥 Hotspots touched (4)
1 more
🔗 Hidden coupling (1 file)
📊 Full report · ⭐ Star Repowise · 📥 Install bot · Last updated 2026-07-20 10:45 UTC |
|
Thanks @Ayush7614, and thanks for actually working through the review points on #818 rather than just the headline ask. The shape is right: reusing the pattern registry, the secret-only gate with The That said, this can't merge yet. Three blockers, and the reason none of them showed up as a failing test is worth taking seriously. The migration can't run on SQLite. SQLite backs every local The Postgres INSERT is invalid SQL. The conflict clause is concatenated ahead of the column list, so it builds: INSERT INTO security_findings ON CONFLICT ON CONSTRAINT uq_... DO NOTHING (repository_id, ...) VALUES (...)
Migration The blob-dedup speedup is given back in the attribution loop. This is the one real design issue. The unique-blob scan is implemented correctly, and then: for commit, _iso in commits:
for blob_sha in self._blobs_in_commit(repo_path, commit):
blob_introduced_at.setdefault(blob_sha, commit)That's one Both are avoidable in one pass each: On the tests. The fixture builds the schema with Smaller, while you're in there: Happy to look again once the migration and the Postgres path are sorted. The core of this is good work and I'd like to land it. |
…y scan Use batch_alter_table for the SQLite migration, fix Postgres ON CONFLICT placement, and replace the per-commit ls-tree attribution loop with a single git log --raw pass plus cat-file --batch for blob reads.
Merge upstream/main into feat/security-scan-history. Keep both replace_findings (working-tree indexing) and persist (history scan), protect history rows from re-index deletes, and renumber the security_finding_history migration to 0041 after upstream landed 0037-0040.
|
Thanks for the detailed review @RaghavChamadiya — I pushed the follow-up fixes and merged Addressed points:
Because I also kept the newer working-tree Validation run locally after the merge resolution:
Latest push: |
…st unrelated to this PR)
Patch logging.config.fileConfig during the migration upgrade test so Alembic does not reconfigure stdlib logging and break caplog in later MCP tests on Linux CI. Also switch security_scan warnings to stdlib logging.
Summary
Implements #818. Reuses the existing
SecurityScannerpattern registry and the sharedsecurity_findingstable rather than forking a new subsystem.
Changes
persist()to carrycommit_sha/commit_atand make re-runs idempotent viaON CONFLICT DO NOTHING/INSERT OR IGNORE.HistorySecurityScannerthat scans unique git blobs (git rev-list --objects --all,deduped by blob SHA) once each and attributes hits to the first-introducing commit. Defaults to the secret-ori
ented patterns (
hardcoded_password/hardcoded_secret) to avoid code-smell noise.commit_sha/commit_atand auq_security_finding_provenanceunique constraint for dedup.
repowise security scan --history [--since <rev>] [--to <rev>] [--all-patterns] [--output json].GET /securitygains a history filter; responses includecommit_sha+found_in_historyso the UI can tell history from working tree.
no parsed symbols on raw blobs), secret-only gate, and blob-dedup scan strategy.
Related Issues
Closes #818
Test Plan
pytest tests/unit/analysis/test_security_scan_history.py)ruff check .)0037→0036)Checklist