Chapter 24Lesson 02~210 minutes

Security, Privacy, Test Accounts, and Safe Automation Boundaries: Guided Hands-On Workflow

Now build a local harness whose default behavior is to refuse unsafe targets and whose evidence/credentials remain synthetic and disposable.

AllowlistEnvironment secretsDisposable profileArtifact audit

Learning objectives

  • Generate a loopback-only AUT and reusable safety harness.
  • Load synthetic credentials from environment variables without printing them.
  • Create disposable browser profile/download/artifact directories.
  • Capture minimal evidence and audit it for exact secret values.
  • Prove a non-allowlisted/production-like target is rejected before browser creation.

1. Create the disposable lab

Run this generator in an empty practice directory. It creates only local files; the AUT binds to 127.0.0.1:8794. The synthetic account has viewer scope and the password is deliberately lab-only.

from pathlib import Path
root = Path('selenium-safe-lab')
root.mkdir(exist_ok=True)
(root/'safety_harness.py').write_text('from __future__ import annotations\nfrom dataclasses import dataclass\nfrom pathlib import Path\nfrom urllib.parse import urlparse\nimport hashlib, json, os, re, shutil, tempfile\n\nclass UnsafeTargetError(RuntimeError):\n    pass\n\n@dataclass(frozen=True)\nclass SafetyConfig:\n    base_url: str\n    allowed_hosts: frozenset[str]\n    allowed_ports: frozenset[int]\n    artifact_root: Path\n\nPRODUCTION_MARKERS = {"prod", "production", "live", "customer", "payments"}\n\n\ndef assert_authorized_target(url: str, cfg: SafetyConfig) -> None:\n    p = urlparse(url)\n    host = (p.hostname or "").lower()\n    port = p.port or (443 if p.scheme == "https" else 80)\n    labels = set(host.replace("-", ".").split("."))\n    if p.scheme not in {"http", "https"}:\n        raise UnsafeTargetError("unsupported scheme")\n    if host not in cfg.allowed_hosts or port not in cfg.allowed_ports:\n        raise UnsafeTargetError("target is outside the explicit test allowlist")\n    if labels & PRODUCTION_MARKERS:\n        raise UnsafeTargetError("production-like target marker refused")\n\n\ndef require_fake_secret(name: str) -> str:\n    value = os.environ.get(name, "")\n    if not value:\n        raise RuntimeError(f"missing required environment variable: {name}")\n    if not value.startswith("LAB_ONLY_"):\n        raise RuntimeError(f"{name} must use the lab-only synthetic prefix")\n    return value\n\n\ndef redact_text(text: str, secrets: list[str]) -> str:\n    clean = text\n    for secret in secrets:\n        if secret:\n            clean = clean.replace(secret, "[REDACTED]")\n    clean = re.sub(r\'(?i)(password|token|secret)(["\\\\s:=]+)[^\\\\s,;]+\', r\'\\\\1\\\\2[REDACTED]\', clean)\n    return clean\n\n\ndef secret_fingerprint(value: str) -> str:\n    return hashlib.sha256(value.encode()).hexdigest()[:12]\n\n\ndef audit_tree(root: Path, forbidden: list[str]) -> list[str]:\n    findings = []\n    for path in root.rglob("*"):\n        if not path.is_file():\n            continue\n        if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".zip"}:\n            continue\n        text = path.read_text(encoding="utf-8", errors="ignore")\n        for value in forbidden:\n            if value and value in text:\n                findings.append(f"{path.name}: exact synthetic secret present")\n    return findings\n\n\ndef disposable_workspace(prefix="selenium-safe-"):\n    root = Path(tempfile.mkdtemp(prefix=prefix))\n    (root / "profile").mkdir()\n    (root / "downloads").mkdir()\n    (root / "artifacts").mkdir()\n    return root\n\n\ndef cleanup_workspace(root: Path) -> None:\n    shutil.rmtree(root, ignore_errors=True)\n', encoding='utf-8')
(root/'aut_server.py').write_text('from http.server import ThreadingHTTPServer, BaseHTTPRequestHandler\nfrom urllib.parse import parse_qs\nfrom http.cookies import SimpleCookie\nimport secrets\n\nHOST, PORT = "127.0.0.1", 8794\nUSER = "LAB_ONLY_user_24"\nPASSWORD = "LAB_ONLY_password_24"\nSESSIONS = set()\n\nPAGE = b"""<!doctype html><html><head><title>Safe Automation Lab</title></head><body>\n<h1>Safe Automation Lab</h1><p id=\'environment\'>environment=test</p>\n<form method=\'post\' action=\'/login\'>\n<label>User <input id=\'username\' name=\'username\' data-sensitive=\'true\'></label>\n<label>Password <input id=\'password\' name=\'password\' type=\'password\' data-sensitive=\'true\'></label>\n<button id=\'sign-in\' type=\'submit\'>Sign in</button></form>\n<p id=\'notice\'>Synthetic credentials only.</p></body></html>"""\n\n\ndef cookie_value(raw):\n    jar = SimpleCookie(); jar.load(raw or "")\n    m = jar.get("lab_session")\n    return m.value if m else None\n\nclass Handler(BaseHTTPRequestHandler):\n    def send_body(self, status, body, headers=()):\n        self.send_response(status)\n        self.send_header("Content-Type", "text/html; charset=utf-8")\n        self.send_header("Cache-Control", "no-store")\n        for k, v in headers: self.send_header(k, v)\n        self.end_headers(); self.wfile.write(body)\n    def do_GET(self):\n        if self.path == "/health": return self.send_body(200, b"ok")\n        if self.path == "/login": return self.send_body(200, PAGE)\n        if self.path == "/dashboard":\n            token = cookie_value(self.headers.get("Cookie"))\n            if token not in SESSIONS:\n                return self.send_body(401, b"<h1 id=\'state\'>not-authenticated</h1>")\n            body = f"<h1 id=\'state\'>authenticated</h1><p id=\'user\'>{USER}</p><p id=\'scope\'>role=viewer</p>".encode()\n            return self.send_body(200, body)\n        return self.send_body(404, b"not found")\n    def do_POST(self):\n        if self.path != "/login": return self.send_body(404, b"not found")\n        n = int(self.headers.get("Content-Length", "0"))\n        form = parse_qs(self.rfile.read(n).decode())\n        if form.get("username", [""])[0] != USER or form.get("password", [""])[0] != PASSWORD:\n            return self.send_body(401, b"<h1 id=\'state\'>invalid-credentials</h1>")\n        token = secrets.token_urlsafe(24); SESSIONS.add(token)\n        self.send_response(303)\n        self.send_header("Location", "/dashboard")\n        self.send_header("Set-Cookie", f"lab_session={token}; Path=/; HttpOnly; SameSite=Lax")\n        self.send_header("Cache-Control", "no-store")\n        self.end_headers()\n    def log_message(self, fmt, *args):\n        # Deliberately avoid request bodies, Cookie, Authorization, or credential values.\n        print("AUT", self.command, self.path.split("?", 1)[0])\n\nThreadingHTTPServer((HOST, PORT), Handler).serve_forever()\n', encoding='utf-8')
(root/'safe_test.py').write_text('import json, os\nfrom pathlib import Path\nfrom selenium import webdriver\nfrom selenium.webdriver.common.by import By\nfrom selenium.webdriver.support.ui import WebDriverWait\nfrom selenium.webdriver.support import expected_conditions as EC\nfrom safety_harness import (SafetyConfig, assert_authorized_target, require_fake_secret,\n                            redact_text, secret_fingerprint, audit_tree,\n                            disposable_workspace, cleanup_workspace)\n\nBASE_URL = os.environ.get("SEL_SAFE_BASE_URL", "http://127.0.0.1:8794")\nUSER = require_fake_secret("SEL_SAFE_USER")\nPASSWORD = require_fake_secret("SEL_SAFE_PASSWORD")\nroot = disposable_workspace()\nartifacts = root / "artifacts"\n\ncfg = SafetyConfig(\n    base_url=BASE_URL,\n    allowed_hosts=frozenset({"127.0.0.1", "localhost"}),\n    allowed_ports=frozenset({8794}),\n    artifact_root=artifacts,\n)\nassert_authorized_target(BASE_URL, cfg)  # must run before browser mutation\n\noptions = webdriver.ChromeOptions()\noptions.add_argument("--headless=new")\noptions.add_argument(f"--user-data-dir={root / \'profile\'}")\noptions.add_experimental_option("prefs", {"download.default_directory": str(root / "downloads")})\n\ndriver = None\ntry:\n    driver = webdriver.Chrome(options=options)\n    caps = dict(driver.capabilities)\n    driver.get(BASE_URL + "/login")\n    assert driver.find_element(By.ID, "environment").text == "environment=test"\n    driver.find_element(By.ID, "username").send_keys(USER)\n    driver.find_element(By.ID, "password").send_keys(PASSWORD)\n    driver.find_element(By.ID, "sign-in").click()\n    WebDriverWait(driver, 5).until(EC.text_to_be_present_in_element((By.ID, "state"), "authenticated"))\n    assert driver.find_element(By.ID, "scope").text == "role=viewer"\n\n    # Screenshot only after credential fields are gone from the authenticated page.\n    driver.save_screenshot(str(artifacts / "authenticated.png"))\n    cookie_meta = [{"name": c["name"], "httpOnly": c.get("httpOnly"), "sameSite": c.get("sameSite")} for c in driver.get_cookies()]\n    manifest = {\n        "session_id": driver.session_id,\n        "browser": caps.get("browserName"),\n        "browser_version": caps.get("browserVersion"),\n        "base_url": BASE_URL,\n        "url": driver.current_url,\n        "title": driver.title,\n        "account_scope": "viewer",\n        "password_fingerprint": secret_fingerprint(PASSWORD),\n        "cookies": cookie_meta,\n    }\n    (artifacts / "manifest.json").write_text(redact_text(json.dumps(manifest, indent=2), [USER, PASSWORD]))\n    findings = audit_tree(artifacts, [USER, PASSWORD])\n    if findings:\n        raise AssertionError("artifact audit failed: " + "; ".join(findings))\nfinally:\n    if driver is not None:\n        driver.quit()\n    # A real CI job would upload reviewed artifacts before this point, then invoke cleanup.\n    cleanup_workspace(root)\n', encoding='utf-8')
(root/'checkpoint.py').write_text('import json, os, tempfile\nfrom pathlib import Path\nfrom safety_harness import SafetyConfig, UnsafeTargetError, assert_authorized_target, require_fake_secret, redact_text, audit_tree\n\nallowed = SafetyConfig(\n    base_url="http://127.0.0.1:8794",\n    allowed_hosts=frozenset({"127.0.0.1", "localhost"}),\n    allowed_ports=frozenset({8794}),\n    artifact_root=Path("evidence"),\n)\n\n# Prediction 1: this target is authorized and passes before any browser starts.\nassert_authorized_target(allowed.base_url, allowed)\n\n# Prediction 2: a production-like or merely non-allowlisted target is refused before session creation.\nblocked = []\nfor target in ["https://production.example.com", "https://shop.example.com", "http://127.0.0.1:9999"]:\n    try:\n        assert_authorized_target(target, allowed)\n    except UnsafeTargetError as exc:\n        blocked.append({"target": target, "classification": type(exc).__name__})\n\nif len(blocked) != 3:\n    raise AssertionError("the safety boundary did not block every forbidden target")\n\npassword = require_fake_secret("SEL_SAFE_PASSWORD")\nwith tempfile.TemporaryDirectory(prefix="safe-evidence-") as tmp:\n    root = Path(tmp)\n    # Deliberately broken artifact: represents a bad logger that persisted a secret.\n    bad = root / "bad.log"\n    bad.write_text(f"login password={password}\\\\n")\n    first_findings = audit_tree(root, [password])\n    if not first_findings:\n        raise AssertionError("expected secret-leak audit finding")\n\n    # Least destructive repair: preserve the finding, redact the artifact, then re-audit.\n    bad.write_text(redact_text(bad.read_text(), [password]))\n    second_findings = audit_tree(root, [password])\n    if second_findings:\n        raise AssertionError("redaction repair failed")\n\n    packet = {\n        "blocked_targets": blocked,\n        "first_audit_findings": first_findings,\n        "post_redaction_findings": second_findings,\n        "safe_automation_checklist": [\n            "written target authorization / allowlist",\n            "least-privilege synthetic test identity",\n            "no MFA or anti-abuse bypass",\n            "disposable browser profile and downloads",\n            "redacted/minimized evidence",\n            "private Grid/network boundary",\n            "retention owner and expiration",\n            "failure-safe cleanup",\n        ],\n    }\n    (root / "checkpoint.json").write_text(json.dumps(packet, indent=2))\n    print((root / "checkpoint.json").read_text())\n', encoding='utf-8')
(root/'requirements.txt').write_text('selenium==4.47.0\n', encoding='utf-8')
print(root.resolve())

The following example makes the Create the disposable lab behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

python build_lab.py   # if you saved the generator as build_lab.py
cd selenium-safe-lab
python -m venv .venv
# Windows PowerShell: .venv\Scripts\Activate.ps1
# POSIX: source .venv/bin/activate
python -m pip install -r requirements.txt
Never substitute real secrets

The names and values in this lab are synthetic. Do not paste production passwords/tokens into commands, source files, screenshots, or chat transcripts.

2. Understand the safety harness before using it

The harness performs four important jobs before WebDriver logic matters:

  1. allowlist: exact scheme/host/port checks with production-like marker rejection;
  2. secret contract: mandatory environment injection using a lab-only prefix;
  3. data minimization: redaction plus one-way fingerprinting when correlation is needed;
  4. runtime isolation: per-run profile/download/artifact directories with explicit cleanup.

The following example makes the Understand the safety harness before using it behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

from __future__ import annotations
from dataclasses import dataclass
from pathlib import Path
from urllib.parse import urlparse
import hashlib, json, os, re, shutil, tempfile

class UnsafeTargetError(RuntimeError):
    pass

@dataclass(frozen=True)
class SafetyConfig:
    base_url: str
    allowed_hosts: frozenset[str]
    allowed_ports: frozenset[int]
    artifact_root: Path

PRODUCTION_MARKERS = {"prod", "production", "live", "customer", "payments"}


def assert_authorized_target(url: str, cfg: SafetyConfig) -> None:
    p = urlparse(url)
    host = (p.hostname or "").lower()
    port = p.port or (443 if p.scheme == "https" else 80)
    labels = set(host.replace("-", ".").split("."))
    if p.scheme not in {"http", "https"}:
        raise UnsafeTargetError("unsupported scheme")
    if host not in cfg.allowed_hosts or port not in cfg.allowed_ports:
        raise UnsafeTargetError("target is outside the explicit test allowlist")
    if labels & PRODUCTION_MARKERS:
        raise UnsafeTargetError("production-like target marker refused")


def require_fake_secret(name: str) -> str:
    value = os.environ.get(name, "")
    if not value:
        raise RuntimeError(f"missing required environment variable: {name}")
    if not value.startswith("LAB_ONLY_"):
        raise RuntimeError(f"{name} must use the lab-only synthetic prefix")
    return value


def redact_text(text: str, secrets: list[str]) -> str:
    clean = text
    for secret in secrets:
        if secret:
            clean = clean.replace(secret, "[REDACTED]")
    clean = re.sub(r'(?i)(password|token|secret)(["\\s:=]+)[^\\s,;]+', r'\\1\\2[REDACTED]', clean)
    return clean


def secret_fingerprint(value: str) -> str:
    return hashlib.sha256(value.encode()).hexdigest()[:12]


def audit_tree(root: Path, forbidden: list[str]) -> list[str]:
    findings = []
    for path in root.rglob("*"):
        if not path.is_file():
            continue
        if path.suffix.lower() in {".png", ".jpg", ".jpeg", ".zip"}:
            continue
        text = path.read_text(encoding="utf-8", errors="ignore")
        for value in forbidden:
            if value and value in text:
                findings.append(f"{path.name}: exact synthetic secret present")
    return findings


def disposable_workspace(prefix="selenium-safe-"):
    root = Path(tempfile.mkdtemp(prefix=prefix))
    (root / "profile").mkdir()
    (root / "downloads").mkdir()
    (root / "artifacts").mkdir()
    return root


def cleanup_workspace(root: Path) -> None:
    shutil.rmtree(root, ignore_errors=True)

The allowlist is intentionally exact. A substring check such as "test" in url is unsafe because an attacker or typo could produce a hostname that merely contains the word “test.” Parse the URL and compare normalized components.

3. Start the loopback AUT and inspect it without credentials

The following example makes the Start the loopback AUT and inspect it without credentials behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

# Terminal 1
python aut_server.py

# Terminal 2 — health/read-only preflight
python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8794/health').read().decode())"

Expected health output is ok. The login page explicitly says environment=test. This does not replace the allowlist; it provides a second independent signal that the fixture is the intended environment.

4. Inject fake credentials through the environment

The following example makes the Inject fake credentials through the environment behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

# PowerShell
$env:SEL_SAFE_USER = "LAB_ONLY_user_24"
$env:SEL_SAFE_PASSWORD = "LAB_ONLY_password_24"
$env:SEL_SAFE_BASE_URL = "http://127.0.0.1:8794"
python safe_test.py

# POSIX shell
export SEL_SAFE_USER='LAB_ONLY_user_24'
export SEL_SAFE_PASSWORD='LAB_ONLY_password_24'
export SEL_SAFE_BASE_URL='http://127.0.0.1:8794'
python safe_test.py

Environment variables are an interface, not automatically a secret vault. In CI, values should originate from the platform’s protected secret store and must not be echoed. Locally, use only the fake lab values shown here. The code never writes the password itself to the evidence packet.

5. Run the browser with disposable state and minimal evidence

The following example makes the Run the browser with disposable state and minimal evidence behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

import json, os
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from safety_harness import (SafetyConfig, assert_authorized_target, require_fake_secret,
                            redact_text, secret_fingerprint, audit_tree,
                            disposable_workspace, cleanup_workspace)

BASE_URL = os.environ.get("SEL_SAFE_BASE_URL", "http://127.0.0.1:8794")
USER = require_fake_secret("SEL_SAFE_USER")
PASSWORD = require_fake_secret("SEL_SAFE_PASSWORD")
root = disposable_workspace()
artifacts = root / "artifacts"

cfg = SafetyConfig(
    base_url=BASE_URL,
    allowed_hosts=frozenset({"127.0.0.1", "localhost"}),
    allowed_ports=frozenset({8794}),
    artifact_root=artifacts,
)
assert_authorized_target(BASE_URL, cfg)  # must run before browser mutation

options = webdriver.ChromeOptions()
options.add_argument("--headless=new")
options.add_argument(f"--user-data-dir={root / 'profile'}")
options.add_experimental_option("prefs", {"download.default_directory": str(root / "downloads")})

driver = None
try:
    driver = webdriver.Chrome(options=options)
    caps = dict(driver.capabilities)
    driver.get(BASE_URL + "/login")
    assert driver.find_element(By.ID, "environment").text == "environment=test"
    driver.find_element(By.ID, "username").send_keys(USER)
    driver.find_element(By.ID, "password").send_keys(PASSWORD)
    driver.find_element(By.ID, "sign-in").click()
    WebDriverWait(driver, 5).until(EC.text_to_be_present_in_element((By.ID, "state"), "authenticated"))
    assert driver.find_element(By.ID, "scope").text == "role=viewer"

    # Screenshot only after credential fields are gone from the authenticated page.
    driver.save_screenshot(str(artifacts / "authenticated.png"))
    cookie_meta = [{"name": c["name"], "httpOnly": c.get("httpOnly"), "sameSite": c.get("sameSite")} for c in driver.get_cookies()]
    manifest = {
        "session_id": driver.session_id,
        "browser": caps.get("browserName"),
        "browser_version": caps.get("browserVersion"),
        "base_url": BASE_URL,
        "url": driver.current_url,
        "title": driver.title,
        "account_scope": "viewer",
        "password_fingerprint": secret_fingerprint(PASSWORD),
        "cookies": cookie_meta,
    }
    (artifacts / "manifest.json").write_text(redact_text(json.dumps(manifest, indent=2), [USER, PASSWORD]))
    findings = audit_tree(artifacts, [USER, PASSWORD])
    if findings:
        raise AssertionError("artifact audit failed: " + "; ".join(findings))
finally:
    if driver is not None:
        driver.quit()
    # A real CI job would upload reviewed artifacts before this point, then invoke cleanup.
    cleanup_workspace(root)

The browser uses a temporary profile and download directory. The screenshot is taken only after navigation has removed the credential form. The cookie evidence keeps metadata but not the cookie value. The password fingerprint can correlate “same synthetic secret configured” without storing the secret itself; a real organization should decide whether even fingerprints are necessary.

6. Prove the production guard independently

The following example makes the Prove the production guard independently behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

from pathlib import Path
from safety_harness import SafetyConfig, UnsafeTargetError, assert_authorized_target

cfg = SafetyConfig(
    base_url="http://127.0.0.1:8794",
    allowed_hosts=frozenset({"127.0.0.1", "localhost"}),
    allowed_ports=frozenset({8794}),
    artifact_root=Path("artifacts"),
)
for target in ["https://production.example.com", "https://shop.example.com", "http://127.0.0.1:9999"]:
    try:
        assert_authorized_target(target, cfg)
        print("UNEXPECTED ALLOW", target)
    except UnsafeTargetError as exc:
        print("BLOCKED", target, type(exc).__name__)

All three must be blocked. The second hostname does not say “production,” but it is still outside the explicit allowlist. This illustrates why “production-looking” heuristics are only a defense-in-depth signal; the allowlist is the primary boundary.

7. Challenge: where should the control live?

Target allowlisting is not a mechanism for defeating MFA, CAPTCHA, rate limits, anti-bot controls, or conditional access. If such a control blocks the test, use an approved test tenant/mock/hook or coordinate with the owning team.

Your CI platform has three Selenium jobs. Someone proposes adding if "prod" not in BASE_URL inside each test method. Choose a stronger design.

Answer to work toward: parse and validate the selected environment once in bootstrap/preflight before session creation, use an explicit allowlist maintained as configuration/policy, and keep a second application-owned environment marker check after navigation. Individual tests should not each reimplement the safety rule.

8. Cleanup

The following example makes the Cleanup behavior concrete. Read it with the stated assumptions, then compare its observable output or state changes with the explanation that follows.

# Stop aut_server.py with Ctrl+C.
# Remove the disposable lab directory only after reviewed artifacts have been copied to an approved retention location.
cd ..
rm -rf selenium-safe-lab
# PowerShell: Remove-Item -Recurse -Force selenium-safe-lab

Also clear the temporary environment variables if your shell persists them. The Python test deletes its per-run browser/profile/download directory in finally; CI should additionally apply job-level cleanup for crash/interruption cases.

Knowledge checks

Answer from the operating model, then reveal the explanation.

Why is an environment variable not automatically a secure secret?

Why does the lab check both an allowlist and an AUT environment marker?

Why is the screenshot taken after authentication instead of while the password field is filled?

Would adding --no-sandbox make the browser lab more convenient?

A target does not contain the word production but is not allowlisted. What happens?

Summary and next bridge

  • Target authorization is validated before WebDriver starts.
  • Credentials are synthetic and injected through configuration without being logged.
  • Browser profile, downloads, and artifacts are disposable per run.
  • Evidence is minimized, redacted, and audited for exact secret values.

Lesson 3 compares policy/design choices and their maintainability, privacy, performance, and CI consequences.

Next lesson

Security, Privacy, Test Accounts, and Safe Automation Boundaries: Configuration, Design Patterns, and Trade-Offs

Continue with Security, Privacy, Test Accounts, and Safe Automation Boundaries: Configuration, Design Patterns, and Trade-Offs. It builds directly on the state, evidence, and operating assumptions established here, so carry those constraints forward rather than treating the next page as an isolated topic.

Primary references and version notes

Version baseline — August 2026

The mandatory examples pin selenium==4.47.0 and Python 3.10+. Selenium Manager remains the normal local driver-resolution path. Browser/OS policy, identity authorization, secret storage, Grid network controls, retention, and production approvals are infrastructure/governance state and are intentionally not hidden inside Selenium helpers.

Keep the academy open

Support free, practical DevOps education.

Every lesson is designed to remain readable in a browser, downloadable from GitHub, and usable without a paid learning platform. Contributions help expand and maintain the curriculum.

Ethereum / ERC-20
0x716c4Ab160C4B66F31a28AE2448BfF68fc3a2ef0Send only Ethereum/ERC-20 compatible assets to this address.