Financial institutions and SaaS/ICT providers supporting them will be examined under the ESAs’ DORA oversight guide 2025. The guide explains how Joint Examination Teams (JET)—coordinated by a Lead Overseer across EBA/ESMA/EIOPA—will supervise critical third-party ICT providers (CTPPs) and verify resilience in practice. This DEV-oriented post translates the guide into an evidence-first checklist, with copy-paste code to stand up an audit-ready evidence register, automate incident reporting artifacts, and align testing (incl. TLPT-style scenarios).
If you want a quick outside-in check while you assemble evidence, run our free website vulnerability scanner and attach the screenshots/reports to your evidence pack: free.pentesttesting.com.
What the Oversight Guide Covers (JET focus)
Scope & players (EBA/ESMA/EIOPA)
- Who is in scope: designated CTPPs and the ICT services they provide to EU financial entities.
- How oversight works: JETs perform examinations and monitoring under a Lead Overseer model; national competent authorities contribute expertise; governance uses formal forums for prioritization and information-sharing.
- What gets examined: governance and risk management, incident/major-outage handling, testing and scenario exercises (including TLPT-aligned practices), data portability & exit strategies, third-party/sub-outsourcing chains, and concentration risk.
What “good” looks like for developers & platform teams
- Traceability: policies ⇄ controls ⇄ telemetry ⇄ tickets ⇄ test results ⇄ evidence artifacts.
- Repeatable drills: incident playbooks and failover tests with preserved logs, screenshots, and change records.
- Measurable SLOs: service-level telemetry mapped to recovery objectives (RTO/RPO) and customer/contract commitments.
Free Website Vulnerability Scanner homepage screenshot:
Screenshot of the free tools webpage where you can access security assessment tools.
Evidence Expectations (make it easy for a JET to follow)
Below is an evidence-first view keyed to common Oversight Guide themes. Each bullet includes what to prove and how to generate/store the proof—with automation you can drop into CI/CD or a nightly job.
1) Governance & risk (incl. EIOPA expectations)
Prove: clear ownership, risk assessment cadence, exceptions with deadlines, change control.
Artifacts: policy PDFs, risk register exports, CAB minutes, signed exception forms, IaC policy checks.
# collect_evidence.sh — nightly snapshot of governance artifacts
set -euo pipefail
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
BASE="evidence/$STAMP/governance"
mkdir -p "$BASE"
cp policies/*.pdf "$BASE"/ || true
cp risk-register/*.csv "$BASE"/ || true
cp change-control/minutes/*.pdf "$BASE"/ || true
tar -czf "evidence/$STAMP-governance.tar.gz" -C "evidence/$STAMP" governance
echo "Wrote evidence/$STAMP-governance.tar.gz"
2) Incident & major-outage reporting
Prove: you can classify, notify, and report within DORA timeframes; show how “major” is decided; preserve forensic trail.
// incident_report.schema.json — align fields to DORA reporting expectations
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"title": "DORA Major Incident Report",
"type": "object",
"required": ["id","classification","start_time","detection_time","services_impacted",
"customers_impacted","confidentiality_impact","integrity_impact",
"availability_impact","data_exfiltration","root_cause","mitigations",
"notifications","timeline","evidence_bundle"],
"properties": {
"id": {"type":"string"},
"classification": {"enum":["major","significant","minor"]},
"start_time": {"type":"string","format":"date-time"},
"detection_time": {"type":"string","format":"date-time"},
"services_impacted": {"type":"array","items":{"type":"string"}},
"customers_impacted": {"type":"integer"},
"confidentiality_impact": {"type":"string"},
"integrity_impact": {"type":"string"},
"availability_impact": {"type":"string"},
"data_exfiltration": {"type":"boolean"},
"root_cause": {"type":"string"},
"mitigations": {"type":"array","items":{"type":"string"}},
"notifications": {"type":"array","items":{"type":"string"}},
"timeline": {"type":"array","items":{"type":"object"}},
"evidence_bundle": {"type":"string"}
}
}
# classify_incident.py — simple rules to flag “major” and build a JSON file that passes the schema
import json, sys, uuid, datetime as dt
from pathlib import Path
RULES = {
"availability_minutes": 60, # >= 60 minutes outage across a key service
"customers_impacted": 1000, # or >= 1k customers
"data_exfiltration": True # any exfil = major
}
def classify(event):
major = False
reasons = []
if event.get("availability_minutes",0) >= RULES["availability_minutes"]:
major = True; reasons.append(">=60min availability impact")
if event.get("customers_impacted",0) >= RULES["customers_impacted"]:
major = True; reasons.append(">=1000 customers impacted")
if event.get("data_exfiltration",False) is True:
major = True; reasons.append("data exfiltration")
return ("major" if major else "significant"), reasons
sample = {
"services_impacted": ["payments-api","auth"],
"availability_minutes": 72,
"customers_impacted": 1250,
"data_exfiltration": False
}
classification, reasons = classify(sample)
report = {
"id": str(uuid.uuid4()),
"classification": classification,
"start_time": dt.datetime.utcnow().isoformat()+"Z",
"detection_time": dt.datetime.utcnow().isoformat()+"Z",
"services_impacted": sample["services_impacted"],
"customers_impacted": sample["customers_impacted"],
"confidentiality_impact": "none",
"integrity_impact": "low",
"availability_impact": f"{sample['availability_minutes']} minutes across core APIs",
"data_exfiltration": sample["data_exfiltration"],
"root_cause": "upstream DB failover misconfig",
"mitigations": ["manual failover","connection pool tuning"],
"notifications": ["internal-SOC","customer-statuspage"],
"timeline": [{"t": "start", "desc":"API 5xx spike"}, {"t":"mitigation","desc":"manual failover"}],
"evidence_bundle": "s3://dora-evidence/incidents/2025-10-12-bundle.tar.gz",
"notes": {"classification_reasons": reasons}
}
Path("out").mkdir(exist_ok=True)
Path("out/incident_report.json").write_text(json.dumps(report, indent=2))
print("Wrote out/incident_report.json")
-- sla_major_incidents.sql — show outages meeting “major” thresholds in last 90 days
WITH incidents AS (
SELECT id, service, started_at, ended_at,
EXTRACT(EPOCH FROM (ended_at - started_at))/60 AS minutes,
customers_impacted, data_exfiltration
FROM ops_incidents
WHERE started_at >= NOW() - INTERVAL '90 days'
)
SELECT id, service, minutes, customers_impacted, data_exfiltration,
CASE WHEN minutes >= 60
OR customers_impacted >= 1000
OR data_exfiltration = TRUE
THEN 'major' ELSE 'significant' END AS classification
FROM incidents
ORDER BY started_at DESC;
3) Resilience testing & TLPT alignment
Prove: routine technical tests + scenario-based exercises cover your critical services, with findings tracked to closure.
# dora_testplan.yaml — map critical services to tests and TLPT-like scenarios
services:
- name: payments-api
owner: platform
controls: [waf, rate-limit, mTLS, db-failover]
tests:
- type: pentest
scope: api
cadence: quarterly
- type: chaos
scenario: db_primary_down
objective: "RTO <= 15m, no data loss"
evidence: "artifacts/chaos/payments-api-dbdown-*.md"
- type: tlpt
scenario: "credential-stuffing -> lateral to ledger"
objective: "detect & contain within 10m"
evidence: "tlpt/2025Q3/*"
- name: auth
owner: identity
tests:
- type: pentest
scope: oauth, oidc
cadence: quarterly
# .github/workflows/dora-readiness.yml — nightly “evidence freshness” CI
name: DORA Readiness Nightly
on:
schedule: [{ cron: "0 1 * * *" }]
jobs:
collect:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Validate incident schema
run: jq -e . out/incident_report.json > /dev/null
- name: Build evidence register
run: python3 scripts/build_evidence_register.py
- name: Publish artifact
uses: actions/upload-artifact@v4
with:
name: dora-evidence-${{ github.run_id }}
path: evidence/**
4) Data portability & exit strategy (incl. EIOPA priorities)
Prove: you can deliver customer data and configs in a documented, secure format; show an exercised exit runbook.
# export_portability.py — deterministic data+config export (hash + manifest)
import hashlib, json, os, tarfile, time
from pathlib import Path
ROOT="exports/portability"
srcs=["configs/tenant.json","db/snapshots/tenant.dump","infra/terraform.tfstate"]
ts=time.strftime("%Y%m%dT%H%M%SZ", time.gmtime())
bundle=f"{ROOT}/export-{ts}.tar.gz"
Path(ROOT).mkdir(parents=True, exist_ok=True)
with tarfile.open(bundle, "w:gz") as tar:
for p in srcs:
tar.add(p)
h=hashlib.sha256(Path(bundle).read_bytes()).hexdigest()
manifest={"bundle": bundle, "sha256": h, "created_at": ts, "format": "json+pgdump+tfstate"}
Path(f"{bundle}.manifest.json").write_text(json.dumps(manifest, indent=2))
print("Export:", bundle, "SHA256:", h)
5) Sub-outsourcing & supply-chain transparency
Prove: you know who your providers depend on, where data is processed, and how changes are controlled.
// vendor_sbom.json — software+service BOM for ICT supply chain
{
"provider": "acme-cloud",
"services": [
{"name":"object-storage","region":"eu-west-1","subprocessors":["netops-co","backup-inc"]},
{"name":"email-relay","region":"eu-central-1","subprocessors":["smtp-pro-1"]}
],
"data_locations": ["DE","IE"],
"change_control": {"notice_days": 30, "opt_out_supported": true}
}
Readiness Steps You Can Implement This Week
- Gap-map your controls to the guide. Create a control-to-oversight map; mark red/yellow/green and owners.
- Contract reviews: ensure clauses for incident reporting, testing rights (incl. TLPT), data portability, sub-outsourcing notice, and exit milestones.
- Playbook drills: run one failover and one security incident exercise; export timelines, chat logs, and dashboards.
- Service-level telemetry: tag SLOs for RTO/RPO and auto-capture dashboards as PDFs into your evidence folder nightly.
- Centralize artifacts: stand up a versioned evidence bucket with lifecycle policies.
# terraform — S3 evidence bucket with retention + immutability
resource "aws_s3_bucket" "dora_evidence" { bucket = "acme-dora-evidence" }
resource "aws_s3_bucket_versioning" "ver" {
bucket = aws_s3_bucket.dora_evidence.id
versioning_configuration { status = "Enabled" }
}
resource "aws_s3_bucket_lifecycle_configuration" "lc" {
bucket = aws_s3_bucket.dora_evidence.id
rule {
id = "retain-365"
status = "Enabled"
noncurrent_version_expiration { noncurrent_days = 365 }
}
}
For Non-EU Providers: Demonstrate Equivalence & Auditability
To accelerate onboarding under DORA as a non-EU provider:
- Assurance pack: SOC 2 Type II or ISAE 3000/3402, ISO 27001 certificate & SoA, data-location appendix, and incident reporting playbooks.
- Control mappings: cross-map your controls to DORA oversight guide 2025 sections—provide a CSV/JSON.
- Evidence APIs: expose a read-only endpoint for customers to fetch signed artifacts and audit logs.
# evidence_api.py — ultra-simple signed evidence link generator (Flask)
from flask import Flask, jsonify, request
import hmac, hashlib, time, base64
SECRET=b"..."
def sign(path, ttl=3600):
exp=int(time.time())+ttl
msg=f"{path}:{exp}".encode()
sig=base64.urlsafe_b64encode(hmac.new(SECRET, msg, hashlib.sha256).digest()).decode()
return f"{path}?exp={exp}&sig={sig}"
app=Flask(__name__)
@app.get("/evidence/ ")
def evidence(item):
return jsonify({"url": sign(f"s3://acme-dora-evidence/{item}")})
app.run(port=8080)
# dora_mapping.csv — sample Controls→Oversight sections
control_id,description,oversight_section,evidence_path
GOV-01,Board-approved risk policy,Governance,evidence/2025-10-12/governance/policies.pdf
INC-03,Incident classification rules,Incident Handling,evidence/2025-10-12/incidents/classifier.md
TEST-07,DB failover exercise Q3,Resilience Testing,evidence/2025-09-20/tests/db-failover/
PORT-02,Export format & hashes,Data Portability,evidence/2025-10-10/exports/manifest.json
Developer Helper: One-shot Evidence Register
# scripts/build_evidence_register.py — create a guide-aligned folder tree
import os, pathlib, json, datetime as dt
ROOT=pathlib.Path("evidence")/dt.datetime.utcnow().strftime("%Y%m%dT%H%M%SZ")
SECTIONS=["01-governance","02-incident","03-testing-tlpt","04-portability-exit",
"05-supply-chain","06-telemetry","07-contracts","08-risk-register"]
for s in SECTIONS: (ROOT/s).mkdir(parents=True, exist_ok=True)
index={"root": str(ROOT), "sections": SECTIONS}
(ROOT/"README.json").write_text(json.dumps(index, indent=2))
print("Evidence root:", ROOT)
Sample report (from the free tool) to check Website Vulnerability:
Sample vulnerability assessment report generated with our free tool, providing insights into possible vulnerabilities.
Related Services & Internal Links
Recent on our blog:
CTA
Need help to gap-map, test, and package a regulator-ready evidence set?
Email query@pentesttesting.com or start here: Risk Assessment Services and Remediation Services.