Skip to content

Commit fdcdc4a

Browse files
Ibraheem OlasupoIbraheem Olasupo
authored andcommitted
Implement milestone 7 findings normalisation and risk enrichment
1 parent 66348cd commit fdcdc4a

80 files changed

Lines changed: 12970 additions & 184 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/findings.yml

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
name: Findings Normalisation
2+
3+
on:
4+
pull_request:
5+
branches: ["main"]
6+
push:
7+
branches: ["main"]
8+
9+
permissions:
10+
contents: read
11+
12+
concurrency:
13+
group: findings-${{ github.workflow }}-${{ github.ref }}
14+
cancel-in-progress: true
15+
16+
jobs:
17+
findings:
18+
runs-on: ubuntu-24.04
19+
timeout-minutes: 15
20+
steps:
21+
# actions/checkout v4.2.2
22+
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
23+
24+
# actions/setup-python v5.6.0
25+
- uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065
26+
with:
27+
python-version: "3.11"
28+
cache: "pip"
29+
30+
- name: Install dependencies
31+
run: |
32+
python -m pip install --upgrade pip==25.1.1
33+
python -m pip install -e ".[dev]"
34+
35+
- name: Source evidence verification
36+
run: make verify-threat-model-evidence verify-api-security-evidence verify-infrastructure-evidence verify-appsec-evidence verify-dynamic-evidence
37+
38+
- name: Findings tests
39+
run: pytest tests/security/test_findings_pipeline.py -q
40+
41+
- name: Findings normalisation and validation
42+
run: make findings-normalise findings-deduplicate findings-enrich findings-validate
43+
44+
- name: Deterministic findings evidence
45+
run: make findings-evidence verify-findings-evidence
46+
47+
- name: Findings reports
48+
run: make findings-report

CONTRIBUTING.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ Run:
1010
make quality
1111
make appsec-fast
1212
make dynamic-fast
13+
make findings-full
1314
```
1415

1516
Use deterministic synthetic data only. Do not add real patient, NHS, genomic, credential, or cloud account data.
@@ -23,3 +24,4 @@ Use deterministic synthetic data only. Do not add real patient, NHS, genomic, cr
2324
- Run scanner targets after changing dependencies, Dockerfile, Terraform, authentication or request-handling code.
2425
- Keep dynamic-security scans pointed only at localhost, loopback or approved local Docker targets.
2526
- Do not add broad scanner suppressions. Use `security/config/suppressions.yaml` with an owner, expiry and exact path or rule scope.
27+
- Keep findings IDs deterministic and avoid absolute local paths, raw JWTs, private keys or real data in canonical findings outputs.

Makefile

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
.PHONY: setup install format format-check lint type-check test test-coverage auth-test api-security-test terraform-fmt terraform-fmt-check terraform-init terraform-validate terraform-test infrastructure-test infrastructure-evidence verify-infrastructure-evidence infrastructure-report security-tools secrets-scan sast sast-semgrep sast-bandit semgrep-test sca dependency-audit sbom verify-sbom iac-scan checkov-scan container-build-security container-scan appsec-fast appsec-full appsec-evidence verify-appsec-evidence appsec-report dynamic-tools dynamic-server-start dynamic-server-wait dynamic-server-stop schemathesis-test api-schema-security-test zap-baseline zap-api-scan auth-boundary-test authorisation-boundary-test object-access-test input-mutation-test security-header-test cors-test resource-consumption-test audit-dynamic-test dast dynamic-evidence verify-dynamic-evidence dynamic-report dynamic-fast dynamic-full pre-commit-install pre-commit-run quality run docker-build docker-run threat-model-validate threat-model-evidence verify-threat-model-evidence threat-model-report api-security-evidence verify-api-security-evidence api-security-report dev-token-researcher dev-token-approver dev-token-auditor clean
1+
.PHONY: setup install format format-check lint type-check test test-coverage auth-test api-security-test terraform-fmt terraform-fmt-check terraform-init terraform-validate terraform-test infrastructure-test infrastructure-evidence verify-infrastructure-evidence infrastructure-report security-tools secrets-scan sast sast-semgrep sast-bandit semgrep-test sca dependency-audit sbom verify-sbom iac-scan checkov-scan container-build-security container-scan appsec-fast appsec-full appsec-evidence verify-appsec-evidence appsec-report dynamic-tools dynamic-server-start dynamic-server-wait dynamic-server-stop schemathesis-test api-schema-security-test zap-baseline zap-api-scan auth-boundary-test authorisation-boundary-test object-access-test input-mutation-test security-header-test cors-test resource-consumption-test audit-dynamic-test dast dynamic-evidence verify-dynamic-evidence dynamic-report dynamic-fast dynamic-full findings-normalise findings-deduplicate findings-enrich findings-validate findings-evidence verify-findings-evidence findings-report findings-full pre-commit-install pre-commit-run quality run docker-build docker-run threat-model-validate threat-model-evidence verify-threat-model-evidence threat-model-report api-security-evidence verify-api-security-evidence api-security-report dev-token-researcher dev-token-approver dev-token-auditor clean
22

33
PYTHON ?= python3
44
VENV ?= .venv
@@ -183,6 +183,30 @@ dynamic-fast:
183183
dynamic-full:
184184
PYTHONPATH=src $(PYTHON) scripts/dynamic_security_tools.py full
185185

186+
findings-normalise:
187+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings normalise
188+
189+
findings-deduplicate:
190+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings deduplicate
191+
192+
findings-enrich:
193+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings enrich
194+
195+
findings-validate:
196+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings validate
197+
198+
findings-evidence:
199+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings evidence --timestamp 2026-01-01T00:00:00Z --as-of-date 2026-01-01
200+
201+
verify-findings-evidence:
202+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings verify
203+
204+
findings-report:
205+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings report
206+
207+
findings-full:
208+
PYTHONPATH=src $(PYTHON) -m genomic_research_access_api.security.findings full --timestamp 2026-01-01T00:00:00Z --as-of-date 2026-01-01
209+
186210
pre-commit-install:
187211
$(PYTHON) -m pip install pre-commit==3.8.0
188212
$(VENV)/bin/pre-commit install

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ Milestone 5 delivered the core AppSec pipeline: pinned scanner configuration, Se
2828

2929
Milestone 6 adds local-only dynamic API security validation: dynamic pytest boundary tests, pinned Schemathesis OpenAPI testing, pinned OWASP ZAP baseline scanning, bounded resource-consumption checks, deterministic dynamic evidence and dynamic-security reports. Dynamic scans are restricted to localhost, loopback and approved local Docker targets.
3030

31+
Milestone 7 adds canonical findings normalisation and risk enrichment across threat-model, AppSec, infrastructure and dynamic-security outputs. It preserves native evidence and governed suppressions, assigns deterministic owners and SLAs, and does not implement release gates or lifecycle workflow.
32+
3133
## Milestone 1 Scope
3234

3335
Implemented:
@@ -178,6 +180,7 @@ Open `http://127.0.0.1:8000/docs` for FastAPI's local OpenAPI UI.
178180
- `make infrastructure-test`: run local infrastructure policy tests without AWS credentials.
179181
- `make verify-infrastructure-evidence`: verify deterministic infrastructure evidence.
180182
- `make security-tools`: print pinned AppSec scanner inventory.
183+
- `make findings-full`: normalise existing security evidence into canonical findings, validate deterministic evidence and generate findings reports.
181184
- `make secrets-scan`: run Gitleaks via local binary or pinned container.
182185
- `make semgrep-test`: run Semgrep custom rule tests.
183186
- `make sast`: run Semgrep and Bandit.

SECURITY.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,15 @@ If you identify a vulnerability or unsafe pattern in this demonstration code, op
66

77
Do not submit real patient data, NHS data, genomic data, credentials, cloud account identifiers, or sensitive operational details in reports.
88

9-
Current implemented security controls include local JWT authentication, role-based and object-level authorisation, threat-model validation, non-deployed Terraform reference controls, a Milestone 5 AppSec pipeline, and Milestone 6 local dynamic API security validation.
9+
Current implemented security controls include local JWT authentication, role-based and object-level authorisation, threat-model validation, non-deployed Terraform reference controls, a Milestone 5 AppSec pipeline, Milestone 6 local dynamic API security validation, and Milestone 7 canonical findings normalisation.
1010

1111
Run local security checks with:
1212

1313
```bash
1414
make quality
1515
make appsec-fast
1616
make dynamic-fast
17+
make findings-full
1718
```
1819

19-
Docker-backed Gitleaks, Trivy, Schemathesis and ZAP scans require a running Docker daemon when native binaries or scanner containers are used. Dynamic scan targets must remain local-only. Do not treat local scanner evidence as a production vulnerability-management programme, penetration test or release approval gate.
20+
Docker-backed Gitleaks, Trivy, Schemathesis and ZAP scans require a running Docker daemon when native binaries or scanner containers are used. Dynamic scan targets must remain local-only. Do not treat local scanner or findings evidence as a production vulnerability-management programme, penetration test or release approval gate.

config/findings/asset-context.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"schema_version": "1.0",
3+
"assets": [
4+
{"asset_id": "AST-APP-FASTAPI", "name": "FastAPI application", "type": "application", "criticality": "high", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "locally_validated", "owner": "Application Engineering", "squad": "Application Engineering", "repository_path": "src/genomic_research_access_api", "threat_ids": ["THR-API-001", "THR-API-002"], "security_requirement_ids": ["SR-API-001", "SR-API-003"]},
5+
{"asset_id": "AST-AUTHN", "name": "authentication subsystem", "type": "service", "criticality": "critical", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "locally_validated", "owner": "Product Security", "squad": "Identity and Application Security", "repository_path": "src/genomic_research_access_api/security/authentication", "threat_ids": ["THR-AUTH-001", "THR-AUTH-002"], "security_requirement_ids": ["SR-AUTH-001"]},
6+
{"asset_id": "AST-AUTHZ", "name": "authorisation subsystem", "type": "service", "criticality": "critical", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "locally_validated", "owner": "Product Security", "squad": "Identity and Application Security", "repository_path": "src/genomic_research_access_api/security/authorisation.py", "threat_ids": ["THR-AUTHZ-001", "THR-AUTHZ-002"], "security_requirement_ids": ["SR-AUTHZ-001", "SR-AUTHZ-002"]},
7+
{"asset_id": "AST-WORKFLOW-ACCESS", "name": "access-request workflow", "type": "workflow", "criticality": "high", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "locally_validated", "owner": "Application Engineering", "squad": "Application Engineering", "repository_path": "src/genomic_research_access_api/services/access_requests.py", "threat_ids": ["THR-AUTHZ-003", "THR-AUTHZ-004"], "security_requirement_ids": ["SR-AUTHZ-004", "SR-AUTHZ-005"]},
8+
{"asset_id": "AST-AUDIT", "name": "audit subsystem", "type": "service", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "not_deployed", "environment": "locally_validated", "owner": "Product Security", "squad": "Application Engineering", "repository_path": "src/genomic_research_access_api/audit", "threat_ids": ["THR-AUDIT-001", "THR-AUDIT-002"], "security_requirement_ids": ["SR-AUDIT-001"]},
9+
{"asset_id": "AST-CONTAINER", "name": "Docker image", "type": "container_image", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "not_deployed", "environment": "not_deployed", "owner": "Platform Engineering", "squad": "Platform Engineering", "repository_path": "Dockerfile", "threat_ids": ["THR-SUPPLY-001"], "security_requirement_ids": ["SR-CONTAINER-001"]},
10+
{"asset_id": "AST-DEPS-PYTHON", "name": "Python dependency set", "type": "dependency_set", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "not_deployed", "environment": "configured", "owner": "Application Engineering", "squad": "Application Engineering", "repository_path": "pyproject.toml", "threat_ids": ["THR-SUPPLY-001"], "security_requirement_ids": ["SR-SUPPLY-001"]},
11+
{"asset_id": "AST-TERRAFORM", "name": "Terraform codebase", "type": "infrastructure_code", "criticality": "high", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "not_deployed", "owner": "Cloud Security", "squad": "Cloud Platform Security", "repository_path": "infrastructure", "threat_ids": ["THR-CLOUD-001"], "security_requirement_ids": ["SR-INFRA-001"]},
12+
{"asset_id": "AST-ECS-TASK", "name": "ECS task definition", "type": "compute", "criticality": "high", "data_sensitivity": "high", "internet_exposure": "planned", "environment": "not_deployed", "owner": "Cloud Security", "squad": "Cloud Platform Security", "repository_path": "infrastructure/modules/compute", "threat_ids": ["THR-CLOUD-001"], "security_requirement_ids": ["SR-INFRA-001"]},
13+
{"asset_id": "AST-IAM", "name": "IAM roles", "type": "identity", "criticality": "critical", "data_sensitivity": "high", "internet_exposure": "not_deployed", "environment": "not_deployed", "owner": "Cloud Security", "squad": "Cloud Platform Security", "repository_path": "infrastructure/modules/iam", "threat_ids": ["THR-CI-001"], "security_requirement_ids": ["SR-CI-002"]},
14+
{"asset_id": "AST-DYNAMODB", "name": "DynamoDB configuration", "type": "datastore", "criticality": "critical", "data_sensitivity": "high", "internet_exposure": "not_deployed", "environment": "not_deployed", "owner": "Cloud Security", "squad": "Cloud Platform Security", "repository_path": "infrastructure/modules/datastore", "threat_ids": ["THR-DATA-001"], "security_requirement_ids": ["SR-DATA-001"]},
15+
{"asset_id": "AST-CLOUDTRAIL", "name": "CloudTrail configuration", "type": "audit_logging", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "not_deployed", "environment": "not_deployed", "owner": "Cloud Security", "squad": "Cloud Platform Security", "repository_path": "infrastructure/modules/audit", "threat_ids": ["THR-AUDIT-001"], "security_requirement_ids": ["SR-AUDIT-001"]},
16+
{"asset_id": "AST-CI", "name": "CI workflows", "type": "ci_cd", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "configured", "environment": "configured", "owner": "DevSecOps Engineering", "squad": "DevSecOps Engineering", "repository_path": ".github/workflows", "threat_ids": ["THR-SUPPLY-002"], "security_requirement_ids": ["SR-CI-001"]},
17+
{"asset_id": "AST-OPENAPI", "name": "OpenAPI contract", "type": "api_contract", "criticality": "high", "data_sensitivity": "medium", "internet_exposure": "planned", "environment": "locally_validated", "owner": "Application Engineering", "squad": "Application Engineering", "repository_path": "outputs/security/dynamic/raw/openapi.json", "threat_ids": ["THR-API-001"], "security_requirement_ids": ["SR-API-001"]}
18+
]
19+
}

config/findings/deduplication.yaml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
{
2+
"schema_version": "1.0",
3+
"exact_keys": ["cve", "cwe", "source_rule_resource", "endpoint_condition", "threat_requirement_control"],
4+
"cross_tool_examples": [
5+
{"tools": ["pip-audit", "trivy"], "key": "cve+package_name"},
6+
{"tools": ["semgrep", "bandit"], "key": "cwe+file+line"},
7+
{"tools": ["zap", "dynamic-pytest"], "key": "security_requirement_ids+asset_id"}
8+
],
9+
"fuzzy_matching": false
10+
}

config/findings/ownership.yaml

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
{
2+
"schema_version": "1.0",
3+
"allowed_owners": ["Application Engineering", "Platform Engineering", "Product Security", "Cloud Security", "DevSecOps Engineering", "Risk Owner", "unowned"],
4+
"rules": [
5+
{"match": "src/genomic_research_access_api/security/authentication/**", "squad": "Identity and Application Security", "technical_owner": "Product Security", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
6+
{"match": "src/genomic_research_access_api/security/authorisation.py", "squad": "Identity and Application Security", "technical_owner": "Product Security", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
7+
{"match": "src/genomic_research_access_api/**", "squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
8+
{"match": "infrastructure/modules/iam/**", "squad": "Cloud Platform Security", "technical_owner": "Cloud Security", "risk_owner": "Risk Owner", "remediation_owner": "Cloud Security"},
9+
{"match": "infrastructure/**", "squad": "Cloud Platform Security", "technical_owner": "Cloud Security", "risk_owner": "Risk Owner", "remediation_owner": "Cloud Security"},
10+
{"match": "Dockerfile", "squad": "Platform Engineering", "technical_owner": "Platform Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Platform Engineering"},
11+
{"match": ".github/workflows/**", "squad": "DevSecOps Engineering", "technical_owner": "DevSecOps Engineering", "risk_owner": "Risk Owner", "remediation_owner": "DevSecOps Engineering"},
12+
{"match": "pyproject.toml", "squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"}
13+
],
14+
"source_tool_defaults": {
15+
"checkov": {"squad": "Cloud Platform Security", "technical_owner": "Cloud Security", "risk_owner": "Risk Owner", "remediation_owner": "Cloud Security"},
16+
"dynamic-pytest": {"squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
17+
"gitleaks": {"squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
18+
"pip-audit": {"squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
19+
"zap": {"squad": "Application Engineering", "technical_owner": "Application Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Application Engineering"},
20+
"trivy": {"squad": "Platform Engineering", "technical_owner": "Platform Engineering", "risk_owner": "Risk Owner", "remediation_owner": "Platform Engineering"},
21+
"threat-model": {"squad": "Product Security", "technical_owner": "Product Security", "risk_owner": "Risk Owner", "remediation_owner": "Product Security"}
22+
},
23+
"unowned": {"squad": "unowned", "technical_owner": "unowned", "risk_owner": "Risk Owner", "remediation_owner": "unowned"}
24+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"schema_version": "1.0",
3+
"portfolio_notice": "Demonstration values for this repository; not Genomics England policy.",
4+
"sla_days": {"critical": 3, "high": 14, "medium": 30, "low": 60, "informational": 90, "unknown": 90},
5+
"context_adjustments": [
6+
{"name": "internet-facing critical secret", "finding_type": "Secret", "severity": "critical", "internet_exposure": "internet_facing", "sla_days": 0},
7+
{"name": "suppression review governs follow-up", "suppression_status": "active", "use_suppression_expiry": true}
8+
]
9+
}

config/findings/risk-scoring.yaml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
{
2+
"schema_version": "1.0",
3+
"weights": {
4+
"technical_severity": 30,
5+
"exploitability": 20,
6+
"internet_exposure": 15,
7+
"asset_criticality": 15,
8+
"data_sensitivity": 10,
9+
"privilege_required": 5,
10+
"age_or_recurrence": 5
11+
},
12+
"factor_scores": {
13+
"technical_severity": {"critical": 1.0, "high": 0.8, "medium": 0.55, "low": 0.3, "informational": 0.1, "unknown": 0.2},
14+
"exploitability": {"known": 1.0, "probable": 0.75, "possible": 0.5, "unlikely": 0.2, "unknown": 0.35},
15+
"internet_exposure": {"internet_facing": 1.0, "planned": 0.65, "configured": 0.45, "locally_validated": 0.25, "not_deployed": 0.15, "unknown": 0.3},
16+
"asset_criticality": {"critical": 1.0, "high": 0.8, "medium": 0.55, "low": 0.3, "unknown": 0.35},
17+
"data_sensitivity": {"high": 1.0, "medium": 0.65, "low": 0.3, "unknown": 0.4},
18+
"privilege_required": {"none": 1.0, "low": 0.7, "high": 0.35, "unknown": 0.5},
19+
"age_or_recurrence": {"recurring": 1.0, "first_seen": 0.4, "unknown": 0.3}
20+
},
21+
"priority_thresholds": {"P1": 80, "P2": 60, "P3": 40, "P4": 20, "P5": 0}
22+
}

0 commit comments

Comments
 (0)