// cat ./projects/openbanking-load-testing.md
Open Banking Auth Flow Load Testing
k6, mTLS, FAPI/OAuth2 with PAR, browser-driven SCA
A load-testing harness driving a complete FAPI-compliant open banking authorization flow — client credentials, consent creation, pushed authorization requests, and strong customer authentication — against a regulated sandbox over mutual TLS.
## Problem
The authorization flow for a regulated open banking API is six chained steps across three hosts, every one requiring mutual TLS, ending in an interactive consent screen. Load-testing it means reproducing the entire sequence programmatically, including the part explicitly designed to require a human.
## Approach
The harness performs the client credentials grant, creates an account-access consent, builds the pushed authorization request object locally, submits it, drives strong customer authentication in a real headless browser, and exchanges the resulting code for a user-level access token. Building the request object locally was possible because the sandbox advertises `none` as a supported signing algorithm — saving a mutual-TLS round trip per iteration against the server's signing helper.
## The bug that defined the project
Every request failed with an invalid-client error stating no mTLS session was established, while curl and openssl s_client succeeded with the same certificate. The cause is in Go's TLS stack, which k6 is built on: it silently declines to send a client certificate whose issuer does not appear in the server's advertised AcceptableCAs list. Not an error, not a warning — no certificate at all. curl does not apply that filter, which is exactly why the manual check kept passing and misdirected the investigation toward credentials.
## The fix
The certificate chain was padded by extracting the sandbox's own CA from its TLS handshake and appending it to the leaf, so the issuer matched what the server advertised. The certificate also had to be bound explicitly to all three hostnames — its subject was a bare UUID with no subject alternative names, leaving nothing for the client to match on automatically.
## What only appeared under live execution
Two values carried over from the reference collection were inert until the flow actually ran: a consent expiry hardcoded to a date that had since passed, which the server rejected outright, and a placeholder value in the authorization request that the server refused by name. Strong customer authentication also turned out to be two screens rather than one — a login form followed by a separate account-selection and consent step. None of this is visible by reading the specification.
## Outcome
The full flow passes 7 of 7 checks, and 28 of 28 across a concurrent run, obtaining real user-level access tokens end to end. Certificates, credentials, and host configuration are supplied through environment variables and kept out of version control.
// ls ../
FLAGSHIP
Snock →
Autonomous Cloud Security AI Agent
RESEARCH
SpecterHeal →
AI-Assisted Self-Healing Infrastructure
PRODUCTION
Enterprise DDoS Defense →
AWS Shield Advanced & Firewall Manager Automation
PRODUCTION
Hardened GitLab DevSecOps Platform →
Secure CI/CD with Terraform, tfsec, Trivy, SonarQube
ENGAGEMENT
Trend Micro XDR Remediation →
Enterprise Endpoint Detection & Response Tuning
PRODUCTION
AWS Incident Response Automation →
Event-Driven Containment with Lambda + CloudWatch
PRODUCTION
SOC Automation & Visualization →
TheHive + Elasticsearch + Grafana + n8n
PRODUCTION
Fortinet SD-WAN Deployment →
Resilient Multi-Site Connectivity
INTERNAL
CTF Training Platform →
Practical Cybersecurity Training Lab
PRODUCTION
Cloud Monitoring & Observability Platform →
Terraform, ECS Fargate, cross-account CloudWatch
DELIVERED
CIS Benchmark & Remediation Program →
Measured posture improvement across a DevOps platform