Version: 0.1.0-draft
Status: Pre-implementation
Maintainer: @had-nu
Related project: Wardex
- Vision & Positioning
- Architecture
- Threat Model
- Scenario Catalogue
- Wardex Integration Spec
- Observability & Audit Evidence
- Gitflow & Branching Strategy
- Documentation Strategy
- Contributing Guidelines
- License Analysis
- Roadmap & Milestones
Wardex Foundry is a deliberately configurable lab environment designed for IT Risk analysts, GRC practitioners, and security consultants to practise risk identification, control validation, and release-gate decision-making in a reproducible, self-hosted environment.
It is the operational counterpart to Wardex: while Wardex provides the risk-driven release-gate engine, this lab provides the environment that Wardex governs.
Platforms like HackTheBox and TryHackMe serve offensive security practitioners — they provide vulnerable environments to practise attack techniques. No equivalent exists for defensive risk practitioners:
- There is no hands-on environment to practise identifying misconfigured broker authentication
- There is no reproducible lab to practise collecting audit evidence of access violations
- There is no scenario-based environment to validate that a release gate correctly blocks a deployment when specific controls are absent
Wardex Foundry fills this gap.
| Persona | Use Case |
|---|---|
| IT Risk Analyst (junior–mid) | Practise identifying control gaps in messaging and secrets infrastructure |
| GRC Consultant | Generate reproducible audit evidence for self-assessment exercises |
| Security Engineer | Validate that release-gate policies correctly gate on real misconfiguration |
| Hiring Manager / Evaluator | Assess a candidate's security posture reasoning, not just their tooling knowledge |
Most infrastructure repositories are installation guides. This lab is different in three ways:
- Every component has two states: a deliberately weakened v1 (with documented risks) and a hardened v2 (with documented mitigations). The transition between them is the learning exercise.
- Risk is documented before code is written: each scenario begins with a threat model entry, not a
docker-composeblock. - The Wardex gate is a first-class actor: the CI pipeline fails or passes based on Wardex's assessment of the environment, making the risk decision traceable and reproducible.
After completing the lab, a practitioner can demonstrate:
- Identification of authentication and encryption gaps in a Kafka broker
- Identification of hardcoded credential patterns and their remediation via Vault
- Collection of audit-grade evidence (log excerpts, metric screenshots, gate decisions) proving control effectiveness
- Articulation of residual risk in a hardened environment
- Operation of a risk-driven release gate policy in a CI context
The lab comprises four components, selected for relevance to financial infrastructure and demonstrable risk surface:
| Component | Role | Risk Domain |
|---|---|---|
| Kafka (Bitnami) | Message broker | Authentication, encryption-in-transit |
| HashiCorp Vault | Secrets management | Credential exposure, secrets lifecycle |
| Prometheus + Grafana | Observability | Audit trail, access violation detection |
| Vexil (CI/Subprocess) | Secrets & Policy scanner | Hardcoded credentials, configuration drift |
| Trivy (CI - Optional) | Container vulnerability scanner | Supply chain, CVE gate |
C4Context
title Wardex Foundry — System Context
Person(analyst, "Risk Analyst", "Practises control validation and risk identification via Web Dashboard")
Person(evaluator, "Portfolio Evaluator", "Reviews lab design and evidence artefacts")
System(lab, "Wardex Foundry Platform", "Locally-hosted web platform to orchestrate vulnerable/hardened environments and audit tools")
System_Ext(wardex, "Wardex Engine", "Risk-driven release gate engine")
System_Ext(vexil, "Vexil (Subprocess)", "Secrets and configuration scanner")
Rel(analyst, lab, "Starts scenarios, views real-time logs, collects evidence", "HTTP")
Rel(evaluator, lab, "Reviews architecture, threat model, audit logs")
Rel(lab, vexil, "Executes secrets scans on scenario configs")
Rel(lab, wardex, "Submits scan results, receives gate decision")
C4Container
title Wardex Foundry — Container View
Container(ui, "Web Dashboard", "HTMX / Tailwind", "Analyst interface for scenario management and evidence review")
Container(backend, "Go Backend", "Go 1.26.1", "Orchestrates Docker SDK, streams SSE logs, triggers scans")
Container(kafka, "Kafka Broker", "Bitnami Kafka", "Message streaming. Configurable: SASL off/on, TLS off/on")
Container(vault, "HashiCorp Vault", "Vault OSS", "Secrets storage. Configurable: dev mode / production mode with TLS")
Container(prometheus, "Prometheus", "prom/prometheus", "Metrics collection from Kafka and Vault")
Container(grafana, "Grafana", "grafana/grafana", "Audit dashboards, access violation alerts")
Rel(ui, backend, "Commands and log streams", "API / SSE")
Rel(backend, kafka, "Orchestrates lifecycle", "Docker Socket")
Rel(backend, vault, "Orchestrates lifecycle", "Docker Socket")
Rel(prometheus, kafka, "Scrapes JMX metrics", "HTTP /metrics")
Rel(prometheus, vault, "Scrapes Vault telemetry", "HTTP /v1/sys/metrics")
Rel(grafana, prometheus, "Queries metrics", "PromQL")
C4Component
title Kafka — Internal Components (Security Focus)
Component(listener, "Listener", "PLAINTEXT or SSL", "Controls transport encryption. v1: PLAINTEXT. v2: SSL/TLS")
Component(sasl, "SASL Mechanism", "NONE or SCRAM-SHA-512", "Controls broker authentication. v1: disabled. v2: SCRAM-SHA-512")
Component(acl, "ACL Authoriser", "AclAuthorizer", "Controls topic-level access. v1: absent. v2: enforced")
Component(jmx, "JMX Exporter", "prometheus/jmx_exporter", "Exposes metrics including failed authentication attempts")
Rel(listener, sasl, "Authenticates after handshake")
Rel(sasl, acl, "Passes identity for authorisation")
Rel(jmx, listener, "Monitors connection events")
graph TD
subgraph lab-network [Docker Bridge: lab-net]
kafka[Kafka :9092 / :9093]
vault[Vault :8200]
prometheus[Prometheus :9090]
grafana[Grafana :3000]
end
subgraph platform [Wardex Foundry]
ui[Web UI]
backend[Go Backend]
vexil[Vexil Subprocess]
wardex[Wardex Engine]
end
analyst[Analyst Browser] -->|HTTP :8080| ui
ui <-->|HTMX / SSE| backend
backend -->|Docker SDK| lab-network
backend -->|Executes Scan| vexil
vexil -->|Results| wardex
wardex -->|ReleaseDecision| backend
prometheus --> kafka
prometheus --> vault
grafana --> prometheus
STRIDE is applied to each component in its v1 (vulnerable) state. The threat model documents what is deliberately exposed and why. This is the analytical artefact that separates this lab from a tutorial.
Reference controls are mapped to ISO/IEC 27001:2022 Annex A where applicable — relevant for practitioners operating in regulated financial environments (e.g., Euronext, EBA, DORA).
| Threat | Category | Description | ISO 27001 Control | Mitigated in v2? |
|---|---|---|---|---|
| Unauthenticated producer | Spoofing | Any client can publish to any topic without credentials | A.8.3 — Information access restriction | ✅ SASL/SCRAM-SHA-512 |
| Plaintext message interception | Information Disclosure | PLAINTEXT listener allows passive network capture of all messages | A.8.24 — Use of cryptography | ✅ TLS listener on :9093 |
| Unrestricted topic consumption | Elevation of Privilege | Any consumer can subscribe to any topic without ACL | A.8.3 — Information access restriction | ✅ AclAuthorizer enforced |
| No authentication audit trail | Non-Repudiation | Failed auth attempts not logged or exported | A.8.15 — Logging | ✅ JMX exporter + Prometheus |
| Image with known CVEs | Tampering | Unscanned base image may contain exploitable packages | A.8.8 — Management of technical vulnerabilities | ✅ Trivy in CI |
| Threat | Category | Description | ISO 27001 Control | Mitigated in v2? |
|---|---|---|---|---|
| Dev mode with root token | Spoofing | Vault dev mode exposes a static root token, defeating auth entirely | A.8.5 — Secure authentication | ✅ Production mode + AppRole |
| Secrets in environment variables | Information Disclosure | docker-compose exposes credentials in env vars readable by any process in container | A.8.13 — Information backup / A.8.12 — Data leakage prevention | ✅ Vault dynamic secrets |
| Unsealed Vault with no TLS | Information Disclosure | API traffic to Vault unencrypted; token interception possible | A.8.24 — Use of cryptography | ✅ TLS on :8200 |
| No audit device enabled | Non-Repudiation | Vault operations not logged; no trail of secret access | A.8.15 — Logging | ✅ File audit device enabled |
Even in the hardened configuration, the following residual risks are documented and accepted for lab scope:
| Risk | Reason Accepted | Compensating Control |
|---|---|---|
| Self-signed TLS certificates | Lab scope; no PKI infrastructure | Certificate pinning in client configuration |
| Single-node Kafka | No HA; availability risk | Out of scope for risk lab (focus is confidentiality/integrity) |
| Vault unsealing via environment | Auto-unseal not configured | Documented as gap; Vault Shamir unseal is manual in lab |
| Grafana with default admin password | Lab convenience | Network isolation; not exposed externally |
Each scenario is a standalone directory under /scenarios/ containing its own README.md, docker-compose.yml (v1), and reference to the hardened equivalent in /hardened/.
Directory: /scenarios/01-plaintext-credentials/
Risk Category: Information Disclosure / Spoofing
CVSS (contextual): High — credentials transmitted in plaintext over network
Description: Kafka broker accepts PLAINTEXT connections with no authentication. Vault runs in dev mode with a static root token exposed as an environment variable in docker-compose.yml.
How to reproduce:
# Start v1 environment
docker compose -f scenarios/01-plaintext-credentials/docker-compose.yml up -d
# Capture broker traffic (demonstrates plaintext)
tcpdump -i any -A port 9092 | grep -i "password\|token"
# Inspect environment variables leaking credentials
docker inspect kafka | jq '.[0].Config.Env'Expected evidence: Network capture shows credentials in clear text. docker inspect reveals KAFKA_PASSWORD=changeme in environment.
Wardex gate behaviour (v1): BLOCK. Policy no-plaintext-brokers evaluates TLS_ENABLED=false → ReleaseDecision: blocked.
Mitigation (v2): TLS listener on :9093, credentials sourced from Vault via AppRole, no secrets in environment variables.
Directory: /scenarios/02-unauthenticated-broker/
Risk Category: Spoofing / Elevation of Privilege
CVSS (contextual): High — any client can produce/consume any topic
Description: Kafka broker has no SASL mechanism configured and no ACL authoriser. Any client on the network can publish to or consume from any topic without presenting credentials.
How to reproduce:
# Start v1 environment
docker compose -f scenarios/02-unauthenticated-broker/docker-compose.yml up -d
# Produce to sensitive topic without credentials
kafka-console-producer --broker-list localhost:9092 --topic financial-transactions
# Consume without credentials
kafka-console-consumer --bootstrap-server localhost:9092 --topic financial-transactions --from-beginningExpected evidence: Both operations succeed without any authentication prompt. Prometheus metric kafka_server_socket_server_metrics_failed_authentication_total remains at zero (no auth attempted, so no failure recorded — absence of the metric is itself the finding).
Wardex gate behaviour (v1): BLOCK. Policy require-sasl-auth evaluates SASL_ENABLED=false → ReleaseDecision: blocked.
Mitigation (v2): SASL/SCRAM-SHA-512 enabled; AclAuthorizer enforced; credentials stored in Vault.
Directory: /scenarios/03-no-audit-trail/
Risk Category: Non-Repudiation
CVSS (contextual): Medium — operations cannot be attributed or detected
Description: Neither Kafka nor Vault have audit logging configured. Failed authentication attempts on Kafka are not exported to Prometheus. Vault's audit device is disabled. An attacker can attempt credential stuffing or unauthorised secret access with no detectable trace.
How to reproduce:
# Start v1 environment
docker compose -f scenarios/03-no-audit-trail/docker-compose.yml up -d
# Attempt failed authentication on Kafka (wrong password)
kafka-console-producer --broker-list localhost:9092 \
--producer-property security.protocol=SASL_PLAINTEXT \
--producer-property sasl.mechanism=PLAIN \
--producer-property 'sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="attacker" password="wrongpassword";'
# Verify: no evidence in Prometheus
curl -s http://localhost:9090/api/v1/query?query=kafka_server_socket_server_metrics_failed_authentication_total
# Expected: empty result setExpected evidence: No metrics, no logs, no alert. The failed attempt is invisible.
Wardex gate behaviour (v1): BLOCK. Policy require-audit-trail checks Prometheus scrape targets and Vault audit device status → ReleaseDecision: blocked.
Mitigation (v2): JMX exporter enabled on Kafka; Vault file audit device configured; Grafana dashboard with alert rule on failed_authentication_total > 0.
Directory: /scenarios/04-exposed-secrets/
Risk Category: Information Disclosure
CVSS (contextual): Critical — infrastructure credentials committed to source/orchestration templates
Description: The docker-compose.yml file contains hardcoded passwords, API keys, or root tokens as environment variables. This represents a mature supply-chain risk where infrastructure-as-code exposes secrets.
How to reproduce:
# Scan the v1 compose file with Vexil
vexil scan -t compile scenarios/04-exposed-secrets/docker-compose.ymlExpected evidence: Vexil reports high-confidence matches for VAULT_DEV_ROOT_TOKEN_ID and KAFKA_PASSWORD in the plaintext YML hierarchy.
Wardex gate behaviour (v1): BLOCK. Policy no-exposed-secrets evaluates the JSON output of Vexil. max_confidence: HIGH threshold is breached → ReleaseDecision: blocked.
Mitigation (v2): Secrets removed from environment blocks. Vault agent injector or manual volume mounts used to deliver secrets at runtime. Vexil scan returns 0 findings.
Wardex Foundry natively integrates with Vexil to detect hardcoded secrets and configuration drift before evaluating release gates.
- Subprocess execution: The Go backend invokes the
vexilbinary against the active scenario'sdocker-compose.ymland environment files. - Contract: Vexil produces a
vexil-results.jsonartifact containing identified secrets and their confidence levels. - Gate Policy: Wardex consumes this JSON and evaluates the
no-exposed-secretsrule against it, blocking the release if high-confidence secrets are found.
Wardex consumes a release-policy.yaml file that defines the conditions under which a release is blocked or permitted. This file lives in /wardex-integration/ and is the bridge between the lab environment and the gate engine.
# wardex-integration/release-policy.yaml
apiVersion: wardex/v1
kind: ReleasePolicy
metadata:
name: lab-baseline-policy
description: Baseline security controls required before any lab component is considered release-ready
version: "1.0.0"
gates:
- id: no-plaintext-brokers
description: Kafka must not expose a PLAINTEXT listener
check:
type: env_var
target: kafka
key: KAFKA_CFG_LISTENERS
must_not_contain: "PLAINTEXT://"
on_fail: block
risk_level: high
- id: require-sasl-auth
description: Kafka must have SASL mechanism configured
check:
type: env_var
target: kafka
key: KAFKA_CFG_SASL_ENABLED_MECHANISMS
must_not_be_empty: true
on_fail: block
risk_level: high
- id: no-vault-dev-mode
description: Vault must not run in dev mode
check:
type: env_var
target: vault
key: VAULT_DEV_ROOT_TOKEN_ID
must_be_absent: true
on_fail: block
risk_level: critical
- id: require-audit-trail
description: Vault audit device must be enabled
check:
type: vault_api
endpoint: /v1/sys/audit
must_have_keys: ["file/"]
on_fail: block
risk_level: high
- id: no-exposed-secrets
description: No hardcoded credentials in environment configurations (Vexil)
check:
type: vexil_report
path: vexil-results.json
max_confidence: HIGH # Block on high confidence secrets only
on_fail: block
risk_level: critical# .github/workflows/wardex-gate.yml
name: Wardex Risk Gate
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Scan environment configs for secrets
uses: had-nu/vexil-action@v1
with:
target: './scenarios'
format: json
output: vexil-results.json
- name: Run Wardex gate
run: |
wardex evaluate \
--policy wardex-integration/release-policy.yaml \
--vexil-report vexil-results.json \
--compose hardened/docker-compose.ymlThe Wardex gate produces a ReleaseDecision record for each evaluation. This is the audit artefact that proves the gate operated.
{
"id": "rd-20250308-001",
"policy_version": "1.0.0",
"evaluated_at": "2025-03-08T14:32:00Z",
"decision": "blocked",
"gates_evaluated": 5,
"gates_passed": 3,
"gates_failed": [
{
"id": "no-vault-dev-mode",
"reason": "VAULT_DEV_ROOT_TOKEN_ID is present",
"risk_level": "critical"
},
{
"id": "require-audit-trail",
"reason": "No audit devices found at /v1/sys/audit",
"risk_level": "high"
}
],
"residual_risk_accepted_by": null,
"notes": "Environment matches Scenario 03 (no-audit-trail) v1 configuration"
}# infrastructure/prometheus/prometheus.yml
scrape_configs:
- job_name: kafka
static_configs:
- targets: ["kafka:9101"] # JMX exporter
metrics_path: /metrics
- job_name: vault
static_configs:
- targets: ["vault:8200"]
metrics_path: /v1/sys/metrics
params:
format: ["prometheus"]
bearer_token_file: /etc/prometheus/vault-token| Metric | Source | Audit Purpose |
|---|---|---|
kafka_server_socket_server_metrics_failed_authentication_total |
Kafka JMX | Detect credential stuffing / unauthorised access attempts |
kafka_network_requestmetrics_errors_total |
Kafka JMX | Detect malformed/unauthorised requests |
vault_audit_log_response_count |
Vault telemetry | Confirm audit device is active and receiving writes |
vault_token_lookup |
Vault telemetry | Track token usage patterns |
vault_secret_kv_count |
Vault telemetry | Monitor secrets inventory changes |
The lab ships with a pre-built Grafana dashboard (infrastructure/grafana/dashboards/lab-audit.json) containing:
- Row 1 — Authentication Events: Failed auth attempts over time (Kafka + Vault), with threshold alert at >0
- Row 2 — Secrets Access: Vault secret reads/writes per minute, token lifecycle events
- Row 3 — System Integrity: Container uptime, JMX exporter health, audit device write latency
- Row 4 — Wardex Gate History: Table of recent
ReleaseDecisionoutcomes (sourced from Wardex API)
For each scenario, the lab provides a collect-evidence.sh script that:
- Exports Prometheus metrics snapshot to
/evidence/metrics-$(date +%Y%m%d).json - Exports Grafana dashboard screenshot (headless) to
/evidence/dashboard-$(date +%Y%m%d).png - Exports Wardex
ReleaseDecisionto/evidence/gate-decision-$(date +%Y%m%d).json - Bundles all three into a timestamped
.tar.gzfor submission or portfolio inclusion
This project follows a simplified Gitflow adapted for a single-maintainer open-source lab:
main ← stable, tagged releases only
develop ← integration branch, always deployable
feature/* ← new scenarios, components, or integrations
fix/* ← bug fixes on existing scenarios or infrastructure
docs/* ← documentation-only changes (no infra impact)
chore/* ← dependency updates, CI changes, tooling
All commits must follow Conventional Commits v1.0.0:
<type>(<scope>): <description>
[optional body]
[optional footer: BREAKING CHANGE, Closes #issue]
Types:
| Type | Use |
|---|---|
feat |
New scenario, new component, new Wardex gate rule |
fix |
Corrected scenario configuration, broken compose, incorrect threat model |
docs |
README, SPEC, scenario write-ups, contributing guide |
chore |
Dependency bumps, CI config, .gitignore |
test |
Evidence collection scripts, scenario validation scripts |
security |
Hardening changes, CVE mitigations (non-breaking) |
refactor |
Infrastructure reorganisation without behaviour change |
Examples:
feat(scenario): add unauthenticated-broker scenario with v1 compose and STRIDE entry
fix(kafka): correct JMX exporter port mapping in hardened compose
docs(spec): add residual risk table to threat model section
security(vault): enable TLS on Vault listener in hardened configuration
chore(ci): update Trivy action to v0.18.0
Semantic versioning (MAJOR.MINOR.PATCH) applied to tagged releases on main:
MAJOR: Breaking change to Wardex policy contract or scenario directory structureMINOR: New scenario added or new component integratedPATCH: Fix to existing scenario, documentation correction, dependency update
All PRs targeting develop or main must:
- Pass GitHub Actions CI (Vexil scan + Wardex gate on hardened compose)
- Include an update to the relevant scenario
README.mdif infrastructure changes - Include a
CHANGELOG.mdentry following Keep a Changelog format - Be reviewed by at least one maintainer (self-review acceptable for docs PRs)
# Install hooks
./scripts/hooks/install-hooks.sh| Hook | Purpose |
|---|---|
commit-msg |
Validates Conventional Commits format |
pre-commit |
Runs docker compose config --quiet on changed compose files |
pre-push |
Runs Vexil scan on targeted environments before push to CI |
| Document | Location | Audience | Update Trigger |
|---|---|---|---|
SPEC.md (this file) |
/ |
Maintainers, evaluators | Architecture changes, new components |
README.md |
/ |
All — first contact | Every release |
Scenario README.md |
/scenarios/<name>/ |
Practitioners | Scenario content changes |
CHANGELOG.md |
/ |
All | Every PR merged to develop |
CONTRIBUTING.md |
/ |
Contributors | Process changes |
SECURITY.md |
/ |
Security reporters | Policy changes |
| Grafana dashboards | /infrastructure/grafana/ |
Operators | Metric changes |
Documentation is treated as a first-class deliverable, not an afterthought. The following rules apply:
A PR that changes infrastructure without updating documentation will not be merged.
Specifically:
- Adding or modifying a scenario requires updating that scenario's
README.mdand the threat model table inSPEC.md - Adding a new component requires updating the C4 diagrams in
SPEC.mdand the architecture section ofREADME.md - Changing the Wardex policy contract requires updating Section 5 of
SPEC.mdand the integration example inREADME.md - Changing the branching model or commit format requires updating Section 7 of
SPEC.mdandCONTRIBUTING.md
All diagrams are written in Mermaid and live in SPEC.md and README.md. They are version-controlled alongside the code they describe.
Rule: If a compose file changes network topology, the corresponding Mermaid diagram must be updated in the same commit.
Mermaid renders natively in GitHub. No external diagram tool dependencies.
Each scenario's README.md follows this structure:
# Scenario NN — <Name>
## Risk Summary
| Field | Value |
|---|---|
| Category | STRIDE category |
| Severity | Critical / High / Medium |
| ISO 27001 Control | A.X.Y — Control name |
| Mitigated in v2 | Yes / Partially / No |
## What You Will Learn
- ...
## Prerequisites
- ...
## Step-by-Step
### 1. Deploy v1 (Vulnerable)
### 2. Reproduce the Finding
### 3. Collect Evidence
### 4. Deploy v2 (Hardened)
### 5. Verify Mitigation
## Wardex Gate Behaviour
| Configuration | Gate Decision | Reason |
|---|---|---|
| v1 | BLOCK | ... |
| v2 | PASS | ... |
## Residual Risk
...## [1.1.0] - 2025-04-01
### Added
- Scenario 02: Unauthenticated Broker with SASL/SCRAM mitigation
- Grafana alert rule for failed_authentication_total
### Changed
- Kafka base image updated to Bitnami 3.7.x
### Fixed
- JMX exporter port conflict with Prometheus on default bridge network
### Security
- Trivy baseline updated; 3 HIGH CVEs resolved by image updateThe full
CONTRIBUTING.mdis generated from this section and lives at the repository root.
Wardex Foundry welcomes contributions that deepen the risk analysis dimension of the lab. Contributions that add tooling complexity without adding a corresponding risk narrative are out of scope.
A good contribution answers at least one of these questions:
- What new risk can a practitioner identify with this change?
- What new evidence can a practitioner collect?
- What new control can the Wardex gate validate?
| Type | Welcome? | Notes |
|---|---|---|
| New scenario (v1 + v2 + threat model entry) | ✅ Yes | Must follow scenario template |
| New Wardex gate rule | ✅ Yes | Must include test case |
| Improved audit evidence scripts | ✅ Yes | Must be POSIX-compatible |
| Documentation corrections | ✅ Yes | Docs PR; no CI gate required |
| New component (e.g., Elasticsearch, Redis) | Open an issue before implementing | |
| Tutorial-style how-to guides | ❌ No | Out of scope; refer to scenario template |
| Replacing existing components with alternatives | ❌ No | Stability of the lab environment is a priority |
1. Fork the repository
2. Create a branch: git checkout -b feat/scenario-04-no-tls-vault
3. Make changes following the scenario template
4. Run pre-commit hooks: ./scripts/hooks/install-hooks.sh
5. Commit with Conventional Commits format
6. Open a PR targeting `develop`
7. Fill in the PR template (scenario description, threat model entry, evidence screenshot)
8. Wait for CI and maintainer review
Do not open a public GitHub issue for security vulnerabilities.
This is a security lab — it intentionally contains vulnerable configurations. If you discover an unintentional vulnerability in the lab's own infrastructure (e.g., the CI pipeline, the evidence collection scripts), report it via email to the maintainer with subject line [SECURITY] Wardex Foundry.
See SECURITY.md for the full responsible disclosure policy.
This project follows the Contributor Covenant v2.1. All contributors are expected to uphold its standards.
This project operates in a European Union context and is intended as a portfolio piece for engagements in regulated financial infrastructure (Euronext, EBA-supervised entities, DORA-regulated firms). License selection must account for:
| Regulation | Relevance |
|---|---|
| DORA (Digital Operational Resilience Act) | Applies to financial entities and their ICT third-party providers. Open-source tools used in financial infrastructure may fall under ICT risk management obligations. |
| NIS2 Directive | Requires adequate cybersecurity measures for operators of essential services. Open-source components used in critical infrastructure must be maintainable and auditable. |
| Cyber Resilience Act (CRA) | Applies to products with digital elements placed on the EU market. Open-source software distributed "in the course of commercial activity" may fall under CRA obligations. |
| GDPR | Relevant if the lab processes any personal data (it does not, by design). |
| License | Type | Patent Grant | CRA Impact | EU Compatibility | Verdict |
|---|---|---|---|---|---|
| MIT | Permissive | ❌ None | Low — hobbyist/non-commercial framing available | ✅ Full | Simple, but no patent protection |
| Apache 2.0 | Permissive | ✅ Explicit | Low — same CRA framing available | ✅ Full | Recommended for tools with commercial derivative potential |
| EUPL v1.2 | Weak copyleft | ✅ Implicit | Low — developed by EU institutions | ✅ Full | EU-native; copyleft means derivatives must share source |
| GPL v3 | Strong copyleft | ✅ Implicit | Low | ✅ Full | Viral; may deter enterprise adoption |
| AGPL v3 | Network copyleft | ✅ Implicit | Low | ✅ Full | Strongest copyleft; SaaS derivatives must publish source |
| BSL 1.1 | Source-available | ❌ None | Not applicable | Inappropriate for this project |
Apache License 2.0 is recommended for Wardex Foundry for the following reasons:
- Patent protection: The explicit patent grant protects contributors and downstream users from patent claims, relevant in a commercial financial context.
- CRA compatibility: Apache 2.0 software distributed non-commercially (as a portfolio/educational project) falls within CRA's open-source steward exemption as currently interpreted.
- DORA compatibility: Permissive license allows financial entities to use, audit, and modify the lab without copyleft obligations — reducing adoption friction.
- Consistency with ecosystem: Kafka (Apache), Vault (BSL with Apache history), Prometheus (Apache), and Grafana (AGPL) — Apache 2.0 is the natural licence for a project in this ecosystem.
- Portfolio signal: Apache 2.0 signals awareness of IP considerations and commercial-readiness without the complexity of copyleft management.
| Dependency | License | Copyleft Risk | Notes |
|---|---|---|---|
| Bitnami Kafka | Apache 2.0 | None | Bitnami packaging is Apache 2.0 |
| HashiCorp Vault OSS | BSL 1.1 | BSL restricts competing SaaS use; lab use is compliant | |
| Prometheus | Apache 2.0 | None | |
| Grafana OSS | AGPL v3 | Grafana usage does not require publishing lab source under AGPL; only modifications to Grafana itself do | |
| Trivy | Apache 2.0 | None |
Vault BSL 1.1 note: HashiCorp changed Vault's license from MPL 2.0 to BSL 1.1 in August 2023. The BSL restricts use "in a competitive offering". A portfolio lab is not a competitive offering. If this is a concern, OpenBao (MPL 2.0 fork) is a drop-in alternative.
The repository includes a LICENSE file at the root containing the full Apache 2.0 license text, and a NOTICE file listing all third-party dependencies and their respective licenses per Apache 2.0 requirements.
Goal: Define the full scope before writing infrastructure code.
Deliverables:
-
SPEC.md(this document) -
README.mdskeleton with positioning statement -
CONTRIBUTING.mdgenerated from Section 9 -
LICENSEandNOTICEfiles - Repository structure created on GitHub
Completion criteria: Repository is publicly visible with spec, licence, and contributing guide in place.
Goal: All four components running, no security configuration applied (pure v1 state).
Deliverables:
-
/infrastructure/docker-compose.ymlwith all components - Prometheus scraping Kafka JMX and Vault telemetry
- Grafana accessible with blank dashboard
- GitHub Actions workflow running Trivy (expected: failures)
- Wardex gate running (expected: all gates BLOCK)
Completion criteria: docker compose up brings all services healthy. Wardex evaluates and produces a ReleaseDecision: blocked with all gates failing.
Goal: Three v1 scenarios documented and reproducible, each with evidence collection.
Deliverables:
- Scenario 01: Plaintext Credentials — compose,
README.md, evidence script - Scenario 02: Unauthenticated Broker — compose,
README.md, evidence script - Scenario 03: No Audit Trail — compose,
README.md, evidence script - Threat model complete in
SPEC.md - Evidence collection producing timestamped bundles
Completion criteria: A practitioner can follow any scenario README from zero to collected evidence without external documentation.
Goal: All controls applied in hardened compose; Wardex gate passes; dashboards show audit trail.
Deliverables:
-
/hardened/docker-compose.yml— TLS, SASL, Vault production mode, audit device - Wardex gate: all five policies passing →
ReleaseDecision: approved - Grafana dashboard: Lab Audit v1.0 with alert rules
- CI badge on README showing gate status
- CHANGELOG v1.0.0 entry
Completion criteria: wardex evaluate on hardened compose produces decision: approved. CI is green. Grafana shows live metrics from all components.
Specification maintained by @had-nu. Open an issue before proposing changes to Sections 2, 3, or 5.