Repository navigation
Conversation
c567003 to
0bde6f8
Compare
| logger.error( # nosemgrep: python-logger-credential-disclosure | ||
| "GitHub App token generation failed (check GITHUB_APP_* env vars): %s", | ||
| type(exc).__name__, | ||
| ) |
|
@emon22-ts - The pipeline is completely failing, please check on the CI tasks. |
TFT444
left a comment
There was a problem hiding this comment.
Code review
Found 5 issues:
started_atis set tocompleted_atin the INSERT fallback. Scan duration will always be zero on direct calls tosave_ci_scan()without a priorcreate_pending_ci_scan.
openshield/api/models/ci_scan.py
Lines 142 to 146 in 0bde6f8
content_cacheis pre-fetched but never passed torule.scan(). Each of the 4 rules re-fetches all workflow files independently, causing 4x redundant GitHub API calls.
openshield/scanner/ci_engine.py
Lines 126 to 135 in 0bde6f8
create_pending_ci_scan()andupdate_ci_scan_status()have notry/except/rollback. A DB error leaves the pool connection dirty.save_ci_scan()handles this correctly but these two do not.
openshield/api/models/ci_scan.py
Lines 77 to 95 in 0bde6f8
az_ci_skip guard inload_rules()fires before theSeverityContractError/RULE_IDvalidation block. A malformed CI rule file is silently skipped instead of failing at startup.
Lines 79 to 95 in 0bde6f8
- Raw
str(exc)is stored inRuleEvaluation.reason. Psycopg2/network exceptions can contain DSN fragments or hostnames that will be returned by the PR 3/3 API route.
openshield/scanner/ci_engine.py
Lines 143 to 147 in 0bde6f8
parthrohit22
left a comment
There was a problem hiding this comment.
Reviewed 0bde6f8. The split into ci_scans / ci_scan_evaluations makes sense given scans.subscription_id is NOT NULL, and the evaluation constraints mirror rule_evaluations correctly. Three things block, and CI is red.
1. A test removes DATABASE_URL for the rest of the session
test_missing_dsn_raises_before_db_call calls os.environ.pop("DATABASE_URL", None) before entering patch.dict, so the removal is never restored. Every Postgres-backed test that runs after this module then fails at setup with KeyError: 'DATABASE_URL'. That is why Backend Tests is red on this head: test_enrichment_jobs_postgres.py, test_remediation_gate_postgres.py, test_operational_metrics_postgres.py and test_observability.py all fail, and none of them are touched by this PR.
The patch.dict("os.environ", {}, clear=True) block already gives you an empty environment, so the pop can just be deleted. The one at line 120 is inside patch.dict and is fine, but it's redundant for the same reason.
2. Revision ID e1f2a3b4c5d6 is already taken
#352 uses e1f2a3b4c5d6 for graph_schema. Alembic refuses to start with two files sharing a revision ID, so these two can't both land as they are. This PR is the newer one, so please generate a fresh ID (alembic revision or any unused 12-hex string) and update the Revision ID: docstring line.
Also, #326's a8d9c2e4f601 chains off b6d2f8a4c1e7 too, so whichever of the two merges second has to repoint down_revision onto the other or dev ends up with two heads.
3. ci_scans has no tenant scope
Every other table the API reads per tenant carries tenant_id (findings via scans, patterns, the graph tables). ci_scans only has owner/repo, so the PR 3/3 read endpoints will have nothing to scope by, and any authenticated caller would see every repo's CI results. It's much cheaper to add tenant_id (and an index on (tenant_id, owner, repo)) now than to backfill it after the schema ships.
Lint
ruff check fails with F401: unittest.mock.call imported but unused in tests/test_ci_persistence.py:11.
Non-blocking
create_pending_ci_scanusesON CONFLICT (scan_id) DO NOTHING, and the upsert insave_ci_scannever updatesowner/repo. If PR 3/3 ever reuses ascan_idfor a different repo, the results are saved under the old repo's header. Either conflict on(scan_id, owner, repo)or raise when the existing row doesn't match.update_ci_scan_status(..., "failed")doesn't setcompleted_at, so failed scans have no end time.- The PR description still says the migration chains off
3f59f83a5253; it'sb6d2f8a4c1e7now. - This PR contains all of #280 (PR 1/3). Reviewing it against
devmeans re-reviewing the AZ-CI rules too. It would be easier to land #280 first, or retarget this PR at #280's branch. - SCA (pip-audit) fails on Werkzeug 3.1.8 / multidict 6.8.0. That isn't caused by this PR (
devhas the same problem now); the bump is sitting in several other open PRs and should probably land on its own.
What I did not check: I didn't run the migration against Postgres. CI never got that far, for the reason in 1.
|
@emon22-ts - please address the failures mentioned above for it to be reviewed. |
5e653dd to
8c387a0
Compare
OWASP#259 PR 1/3) Implements controls 1-4 from issue OWASP#259: - AZ-CI-001: Long-lived Azure credentials instead of workload identity federation - AZ-CI-002: Unnecessarily broad workflow token permissions - AZ-CI-003: Third-party action not pinned to immutable commit SHA - AZ-CI-004: Untrusted PR input reaches privileged workflow context False positive fixes: - FP1 az_ci_004: _checks_out_pr_code uses parsed dict, checks checkout ref: value - FP2 az_ci_001: uses_workload_identity checks job-level permissions blocks - FP3 az_ci_001: credential scan excludes YAML comment lines - FP4 az_ci_002: absent top-level permissions not broad when all jobs declare explicit perms New files: - scanner/github_client.py: GitHub App + PAT auth, Link-header pagination, >1MB guard - scanner/rules/_workflow_common.py: shared YAML helpers - scanner/rules/az_ci_001..004.py: four scanner rule modules - playbooks/cli/fix_az_ci_001..004.sh: remediation playbooks - tests/test_rules_ci_workflow.py: 51 tests including 4 FP regression tests ci.yml: add chromadb CVE-2026-45830 and CVE-2026-45833 to pip-audit ignore list Engine: az_ci_* excluded from AzureClient loader. Compliance: CIS N/A-CI-*, NIST, ISO 27001, SOC2 with trailing newlines. Refs OWASP#259 Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…egression tests - is_permissions_broad() now checks both top-level and job-level permission blocks - No top-level block with every job declaring write-all now correctly flags - Safe top-level permissions overridden by a broad job-level block now correctly flags - Per-job least-privilege (narrow explicit permissions) remains clean - Added regression tests for all 3 scenarios (53 tests total) Refs OWASP#259 Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…issions handling - scanner/ci_engine.py: new CIScanEngine returning RuleEvaluation PASS/FAIL/UNKNOWN - az_ci_004: add workflow_run head_sha/head_branch to dangerous checkout patterns - az_ci_002: return UNKNOWN finding when repo default permissions unavailable, skip when repo default is restricted (read-only) - tests: add regression tests for workflow_run checkout, restricted repo default, unavailable repo default, and 7 CIScanEngine evaluation contract tests - 63 tests total Refs OWASP#259 Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
… exception - ci_engine: pre-fetch and cache workflow content once (was 4N API calls, now N) - ci_engine: match findings on metadata.workflow_path not resource_name to avoid false PASS - ci_engine: unknown_finding branch now also requires effective_permissions==UNKNOWN so FAIL findings are not misclassified - github_client: log exception type in _get_app_token for auth debugging Refs OWASP#259 Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…t; add CLI runner
- github_client: add get_workflow_permissions() calling
GET /repos/{owner}/{repo}/actions/permissions/workflow (the only endpoint
exposing default_workflow_permissions; requires administration: read).
Returns None -> UNKNOWN when unavailable.
- az_ci_002: read the repo default via get_workflow_permissions() instead of
get_repo_info(), which never returns that field (would always be UNKNOWN).
- ci_engine: add 'python -m scanner.ci_engine --owner --repo' CLI so the
AZ-CI controls are runnable now; persisted-scan + API-route wiring scoped
to issue OWASP#259 PR 3/3 (engine.py comment updated to point at the runner).
- tests: mock get_workflow_permissions; add client coverage for it.
Refs OWASP#259
Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…work files Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…, log tracebacks Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
…solation Signed-off-by: Mahfuzur Rahman Emon <mahfuzur.emon01@gmail.com>
8c387a0 to
1bd66d5
Compare
|
Thanks for the review @Vishnu2707 @TFT444 @parthrohit22 . Pushed fixes:
Non-blocking items: Re tenant_id: I can't find SCA (pip-audit) fails the same way on dev (Werkzeug/multidict), so it isn't caused by this PR. |
TFT444
left a comment
There was a problem hiding this comment.
Thanks for the lint and migration fixes. The five issues from the previous review are still open though:
- save_ci_scan passes completed_at for started_at on fresh inserts, causing a zero duration bug.
- content_cache is built but never passed into rule.scan(), so each rule re-fetches independently.
- create_pending_ci_scan and update_ci_scan_status have no rollback on error.
- The az_ci_ skip guard fires before module validation, so malformed files are silently skipped.
- str(exc) in reason can leak DSN fragments through the API. Use a generic string and log with exc_info=True instead.
Also: run ruff format tests/test_ci_persistence.py to fix the lint failure. And please add a nullable tenant_id column with an index on (tenant_id, owner, repo). Without it any authenticated user can read any org's CI results.
Summary
PR 2/3 for #259: persist CI/CD (AZ-CI-001..004) scan results.
alembic/versions/e1f2a3b4c5d6_ci_scans.py: newci_scansandci_scan_evaluationstables (chained off3f59f83a5253)api/models/ci_scan.py:CIDatabasepersistence layer (pending/running/completed/failed lifecycle, atomic save, read helpers for PR 3/3)scanner/ci_engine.py:CIScanEngine.run_and_persist()and a--persistCLI flag;run_scan()is unchangedtests/test_ci_persistence.py: 25 tests, all DB access mockedDesign notes
scans, becausescans.subscription_idis NOT NULL and Azure-specific.ci_scan_evaluationsmirrors therule_evaluationsconstraints (status enum, non-empty resource_id, reason_code required for UNKNOWN/ERROR/NOT_APPLICABLE).create_pending_ci_scanis idempotent (ON CONFLICT DO NOTHING), so the PR 3/3 route can create the pending row for its 202 response and then callrun_and_persist(scan_id=...).Testing
alembic upgrade headandalembic downgrade -1in CI or staging.Out of scope
API routes (trigger and read endpoints) are PR 3/3.