From 389d318e417cb494df1493ed5b65b671405cbde1 Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Fri, 6 Mar 2026 13:45:32 -0700
Subject: [PATCH 1/9] Update WebSocket URL construction in Dockerfile
Mixed Content Fix
---
labs/remotebrowser/Dockerfile | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile
index 6924e04..7f3fd26 100644
--- a/labs/remotebrowser/Dockerfile
+++ b/labs/remotebrowser/Dockerfile
@@ -166,7 +166,8 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML
function connect() {
strip();
msg('Connecting...', true);
- const url = 'ws://' + location.hostname + ':' + (location.port||'6080') + '/websockify';
+ const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
+ const url = proto + '//' + location.hostname + ':' + (location.port||'6080') + '/websockify';
rfb = new RFB(scrn, url, { credentials: {password:''} });
rfb.scaleViewport = true;
rfb.resizeSession = true;
From fd7f3209ce8db1c98f9b5d29fe1994b9f32bbef0 Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Fri, 6 Mar 2026 13:56:59 -0700
Subject: [PATCH 2/9] Fix URL construction for WebSocket connection
---
labs/remotebrowser/Dockerfile | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile
index 7f3fd26..667255a 100644
--- a/labs/remotebrowser/Dockerfile
+++ b/labs/remotebrowser/Dockerfile
@@ -167,7 +167,7 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML
strip();
msg('Connecting...', true);
const proto = location.protocol === 'https:' ? 'wss:' : 'ws:';
- const url = proto + '//' + location.hostname + ':' + (location.port||'6080') + '/websockify';
+ const url = proto + '//' + location.hostname + ':' + (location.port||'6080');
rfb = new RFB(scrn, url, { credentials: {password:''} });
rfb.scaleViewport = true;
rfb.resizeSession = true;
From 3716ed30ff3d0d4bdaa1aba9076bf2fdc2c97672 Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Sat, 7 Mar 2026 12:18:51 -0700
Subject: [PATCH 3/9] Create README.md
---
labs/remotebrowser_TLS/README.md | 1 +
1 file changed, 1 insertion(+)
create mode 100644 labs/remotebrowser_TLS/README.md
diff --git a/labs/remotebrowser_TLS/README.md b/labs/remotebrowser_TLS/README.md
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/labs/remotebrowser_TLS/README.md
@@ -0,0 +1 @@
+
From d20d1e414c263ea089b13816739b90285160a88c Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Sat, 7 Mar 2026 12:19:56 -0700
Subject: [PATCH 4/9] Add files via upload
---
labs/remotebrowser_TLS/Dockerfile | 616 ++++++++++++++++++++++
labs/remotebrowser_TLS/README.md | 2 +-
labs/remotebrowser_TLS/docker-compose.yml | 58 ++
3 files changed, 675 insertions(+), 1 deletion(-)
create mode 100644 labs/remotebrowser_TLS/Dockerfile
create mode 100644 labs/remotebrowser_TLS/docker-compose.yml
diff --git a/labs/remotebrowser_TLS/Dockerfile b/labs/remotebrowser_TLS/Dockerfile
new file mode 100644
index 0000000..ee8c77a
--- /dev/null
+++ b/labs/remotebrowser_TLS/Dockerfile
@@ -0,0 +1,616 @@
+# =============================================================================
+# TLSDebug + Headless Chrome + noVNC — single self-contained Dockerfile
+# =============================================================================
+FROM debian:bookworm-slim
+
+ENV DEBIAN_FRONTEND=noninteractive \
+ DISPLAY=:99 \
+ VNC_PORT=5900 \
+ NOVNC_PORT=6080 \
+ NOVNC_HTTPS_PORT=6443 \
+ PROXY_PORT=8080 \
+ CHROME_REMOTE_DEBUGGING_PORT=9222 \
+ PROXY_HOST=127.0.0.1 \
+ NOVNC_ENABLE_HTTPS=false
+
+# ---------------------------------------------------------------------------
+# 1. Base packages
+# ---------------------------------------------------------------------------
+RUN apt-get update && apt-get install -y --no-install-recommends \
+ golang-go \
+ git \
+ xvfb \
+ x11vnc \
+ openbox \
+ xterm \
+ python3 \
+ python3-pip \
+ python3-websockify \
+ novnc \
+ wget \
+ gnupg \
+ ca-certificates \
+ fonts-liberation \
+ libasound2 \
+ libatk-bridge2.0-0 \
+ libatk1.0-0 \
+ libcups2 \
+ libdbus-1-3 \
+ libgdk-pixbuf2.0-0 \
+ libgtk-3-0 \
+ libnspr4 \
+ libnss3 \
+ libnss3-tools \
+ libx11-xcb1 \
+ libxcomposite1 \
+ libxdamage1 \
+ libxfixes3 \
+ libxkbcommon0 \
+ libxrandr2 \
+ libxshmfence1 \
+ xdg-utils \
+ openssl \
+ curl \
+ netcat-openbsd \
+ procps \
+ psmisc \
+ x11-utils \
+ unclutter \
+ && rm -rf /var/lib/apt/lists/*
+
+# ---------------------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# 2. Install browser
+# amd64 -> Google Chrome stable (official Google .deb)
+# arm64 -> Chromium from Debian bookworm (native deb, no snap)
+#
+# Base image is debian:bookworm-slim which has real chromium packages
+# on all architectures — no snap wrappers.
+# ---------------------------------------------------------------------------
+RUN ARCH=$(dpkg --print-architecture) && \
+ if [ "${ARCH}" = "amd64" ]; then \
+ echo "[browser] amd64: installing Google Chrome ..." && \
+ wget -q -O /tmp/chrome.deb \
+ "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends /tmp/chrome.deb && \
+ rm /tmp/chrome.deb && \
+ rm -rf /var/lib/apt/lists/* && \
+ ln -sf /usr/bin/google-chrome /usr/local/bin/browser && \
+ echo "[browser] installed: $(google-chrome --version)"; \
+ elif [ "${ARCH}" = "arm64" ]; then \
+ echo "[browser] arm64: installing Chromium from Debian bookworm ..." && \
+ apt-get update && \
+ apt-get install -y --no-install-recommends chromium && \
+ rm -rf /var/lib/apt/lists/* && \
+ ln -sf /usr/bin/chromium /usr/local/bin/browser && \
+ echo "[browser] installed: $(chromium --version)"; \
+ else \
+ echo "[browser] ERROR: unsupported arch ${ARCH}" && exit 1; \
+ fi
+
+# ---------------------------------------------------------------------------
+# 3. Build TLSDebug from source
+# AllTrafficModule is already active by default and logs every request,
+# response, headers and body — no source patching needed.
+# We set log flags via the LOG_FLAGS env var at runtime instead.
+# ---------------------------------------------------------------------------
+WORKDIR /build
+RUN git clone --depth 1 https://github.com/secdev02/TLSDebug.git . \
+ && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/tlsproxy tlsproxy.go \
+ && chmod +x /usr/local/bin/tlsproxy \
+ && rm -rf /build
+
+# 4. Locate noVNC web root and write kiosk index.html
+# Kiosk mode: auto-connects, scales to fill browser window, hides toolbar,
+# no password prompt, no connection dialog — just the desktop.
+# ---------------------------------------------------------------------------
+RUN find /usr -name "vnc.html" 2>/dev/null | head -1 | xargs -I{} dirname {} > /opt/novnc_path \
+ && NOVNC_WEB=$(cat /opt/novnc_path) \
+ && echo "noVNC web root: ${NOVNC_WEB}" \
+ && ls -la "${NOVNC_WEB}"
+
+RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML'
+
+
+
+
+ TLSDebug
+
+
+
+
+ Connecting...
+
+
+
+HTML
+
+# Also overwrite vnc.html so any direct /vnc.html request shows our kiosk too
+RUN NOVNC_WEB=$(cat /opt/novnc_path) && cp "${NOVNC_WEB}/index.html" "${NOVNC_WEB}/vnc.html"
+
+# ---------------------------------------------------------------------------
+# 5. Working directories
+# ---------------------------------------------------------------------------
+RUN mkdir -p /opt/tlsdebug /opt/chrome-profile /var/log/tlsdebug /root/.config
+
+# ---------------------------------------------------------------------------
+# ---------------------------------------------------------------------------
+# 4b. Openbox config — fullscreen enforcement + black desktop, no decorations
+# ---------------------------------------------------------------------------
+RUN mkdir -p /root/.config/openbox
+
+# rc.xml — force every window fullscreen, no decorations, black desktop
+RUN cat > /root/.config/openbox/rc.xml << 'RCXML'
+
+
+
+ Clearlooks
+
+ no
+ no
+
+ 1
+
+
+ yes
+ yes
+ no
+ yes
+
+
+
+RCXML
+
+# autostart — black root window, hide idle cursor
+RUN cat > /root/.config/openbox/autostart << 'OBSTART'
+xsetroot -solid black
+unclutter -idle 0 -root -noevents &
+OBSTART
+
+# ---------------------------------------------------------------------------
+# 5b. Log viewer — real-time web UI on port 4040
+# ---------------------------------------------------------------------------
+RUN echo 'aW1wb3J0IGh0dHAuc2VydmVyLCBvcywganNvbgpmcm9tIGh0dHAuc2VydmVyIGltcG9ydCBCYXNlSFRUUFJlcXVlc3RIYW5kbGVyLCBIVFRQU2VydmVyCmZyb20gdXJsbGliLnBhcnNlIGltcG9ydCB1cmxwYXJzZSwgcGFyc2VfcXMKCkxPR19ESVIgPSBvcy5lbnZpcm9uLmdldCgiTE9HX0RJUiIsICIvdmFyL2xvZy90bHNkZWJ1ZyIpClBPUlQgICAgPSA0MDQwCgpMT0dfRklMRVMgPSBbInRsc3Byb3h5LmxvZyIsICJjYXB0dXJlZF90b2tlbnMuanNvbiIsICJjaHJvbWUubG9nIiwKICAgICAgICAgICAgICJ4MTF2bmMubG9nIiwgIm5vdm5jLmxvZyIsICJ4dmZiLmxvZyIsICJvcGVuYm94LmxvZyJdCgpkZWYgYnVpbGRfcGFnZSgpOgogICAgZmlsZXNfanMgPSBqc29uLmR1bXBzKExPR19GSUxFUykKICAgIHJldHVybiBmIiIiPCFET0NUWVBFIGh0bWw+CjxodG1sIGxhbmc9ImVuIj4KPGhlYWQ+CjxtZXRhIGNoYXJzZXQ9InV0Zi04Ij4KPG1ldGEgbmFtZT0idmlld3BvcnQiIGNvbnRlbnQ9IndpZHRoPWRldmljZS13aWR0aCxpbml0aWFsLXNjYWxlPTEiPgo8dGl0bGU+VExTRGVidWcgTG9nIFZpZXdlcjwvdGl0bGU+CjxzdHlsZT4KICA6cm9vdHt7LS1iZzojMGQxMTE3Oy0tcGFuZWw6IzE2MWIyMjstLWJvcmRlcjojMzAzNjNkOy0tYWNjZW50OiM1OGE2ZmY7CiAgICAgICAgLS1ncmVlbjojM2ZiOTUwOy0tdGV4dDojYzlkMWQ5Oy0tbXV0ZWQ6IzhiOTQ5ZX19CiAgKnt7Ym94LXNpemluZzpib3JkZXItYm94O21hcmdpbjowO3BhZGRpbmc6MH19CiAgYm9keXt7YmFja2dyb3VuZDp2YXIoLS1iZyk7Y29sb3I6dmFyKC0tdGV4dCk7Zm9udC1mYW1pbHk6J1NGIE1vbm8nLE1vbmFjbyxtb25vc3BhY2U7CiAgICAgICBmb250LXNpemU6MTNweDtkaXNwbGF5OmZsZXg7ZmxleC1kaXJlY3Rpb246Y29sdW1uO2hlaWdodDoxMDB2aH19CiAgaGVhZGVye3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpOwogICAgICAgICAgcGFkZGluZzoxMnB4IDIwcHg7ZGlzcGxheTpmbGV4O2FsaWduLWl0ZW1zOmNlbnRlcjtnYXA6MTZweDtmbGV4LXNocmluazowfX0KICBoZWFkZXIgaDF7e2ZvbnQtc2l6ZToxNXB4O2ZvbnQtd2VpZ2h0OjYwMDtjb2xvcjp2YXIoLS1hY2NlbnQpfX0KICAuYmFkZ2V7e2JhY2tncm91bmQ6dmFyKC0tZ3JlZW4pO2NvbG9yOiMwMDA7Zm9udC1zaXplOjEwcHg7Zm9udC13ZWlnaHQ6NzAwOwogICAgICAgICAgcGFkZGluZzoycHggOHB4O2JvcmRlci1yYWRpdXM6MTBweH19CiAgI3N0YXR1c2Jhcnt7Zm9udC1zaXplOjExcHg7Y29sb3I6dmFyKC0tbXV0ZWQpO21hcmdpbi1sZWZ0OmF1dG99fQogIC50YWJze3tkaXNwbGF5OmZsZXg7Z2FwOjRweDtvdmVyZmxvdy14OmF1dG87YmFja2dyb3VuZDp2YXIoLS1wYW5lbCk7CiAgICAgICAgIHBhZGRpbmc6OHB4IDE2cHggMDtib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjB9fQogIC50YWJ7e3BhZGRpbmc6NnB4IDE0cHg7Ym9yZGVyLXJhZGl1czo2cHggNnB4IDAgMDtjdXJzb3I6cG9pbnRlcjtjb2xvcjp2YXIoLS1tdXRlZCk7CiAgICAgICAgYm9yZGVyOjFweCBzb2xpZCB0cmFuc3BhcmVudDtib3JkZXItYm90dG9tOm5vbmU7Zm9udC1zaXplOjEycHg7d2hpdGUtc3BhY2U6bm93cmFwfX0KICAudGFiOmhvdmVye3tjb2xvcjp2YXIoLS10ZXh0KTtiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA1KX19CiAgLnRhYi5hY3RpdmV7e2NvbG9yOnZhcigtLWFjY2VudCk7YmFja2dyb3VuZDp2YXIoLS1iZyk7Ym9yZGVyLWNvbG9yOnZhcigtLWJvcmRlcil9fQogIC50b29sYmFye3tkaXNwbGF5OmZsZXg7Z2FwOjhweDtwYWRkaW5nOjhweCAxNnB4O2JhY2tncm91bmQ6dmFyKC0tYmcpOwogICAgICAgICAgICBib3JkZXItYm90dG9tOjFweCBzb2xpZCB2YXIoLS1ib3JkZXIpO2ZsZXgtc2hyaW5rOjA7YWxpZ24taXRlbXM6Y2VudGVyfX0KICAudG9vbGJhciBpbnB1dHt7ZmxleDoxO2JhY2tncm91bmQ6dmFyKC0tcGFuZWwpO2JvcmRlcjoxcHggc29saWQgdmFyKC0tYm9yZGVyKTsKICAgICAgICAgICAgICAgICAgY29sb3I6dmFyKC0tdGV4dCk7cGFkZGluZzo1cHggMTBweDtib3JkZXItcmFkaXVzOjZweDtmb250LWZhbWlseTppbmhlcml0fX0KICAudG9vbGJhciBsYWJlbHt7Y29sb3I6dmFyKC0tbXV0ZWQpO2ZvbnQtc2l6ZToxMnB4O2Rpc3BsYXk6ZmxleDthbGlnbi1pdGVtczpjZW50ZXI7Z2FwOjVweH19CiAgLnRvb2xiYXIgYnV0dG9ue3tiYWNrZ3JvdW5kOnZhcigtLXBhbmVsKTtib3JkZXI6MXB4IHNvbGlkIHZhcigtLWJvcmRlcik7Y29sb3I6dmFyKC0tdGV4dCk7CiAgICAgICAgICAgICAgICAgICBwYWRkaW5nOjVweCAxMnB4O2JvcmRlci1yYWRpdXM6NnB4O2N1cnNvcjpwb2ludGVyO2ZvbnQtZmFtaWx5OmluaGVyaXR9fQogIC50b29sYmFyIGJ1dHRvbjpob3Zlcnt7Ym9yZGVyLWNvbG9yOnZhcigtLWFjY2VudCk7Y29sb3I6dmFyKC0tYWNjZW50KX19CiAgI2xvZ3ZpZXd7e2ZsZXg6MTtvdmVyZmxvdy15OmF1dG87cGFkZGluZzoxMnB4IDE2cHg7d2hpdGUtc3BhY2U6cHJlLXdyYXA7CiAgICAgICAgICAgIHdvcmQtYnJlYWs6YnJlYWstYWxsO2xpbmUtaGVpZ2h0OjEuNn19CiAgLmxue3twYWRkaW5nOjFweCAwfX0gLmxuOmhvdmVye3tiYWNrZ3JvdW5kOnJnYmEoMjU1LDI1NSwyNTUsLjA0KX19CiAgLm1hdGNoe3tiYWNrZ3JvdW5kOnJnYmEoODgsMTY2LDI1NSwuMTIpfX0gLmhse3tiYWNrZ3JvdW5kOiNlM2IzNDE0NDtib3JkZXItcmFkaXVzOjJweH19Cjwvc3R5bGU+CjwvaGVhZD4KPGJvZHk+CjxoZWFkZXI+CiAgPGgxPiYjeDFGNTBEOyBUTFNEZWJ1ZyBMb2cgVmlld2VyPC9oMT4KICA8c3BhbiBjbGFzcz0iYmFkZ2UiPkxJVkU8L3NwYW4+CiAgPHNwYW4gaWQ9InN0YXR1c2JhciI+Q29ubmVjdGluZy4uLjwvc3Bhbj4KPC9oZWFkZXI+CjxkaXYgY2xhc3M9InRhYnMiIGlkPSJ0YWJzIj48L2Rpdj4KPGRpdiBjbGFzcz0idG9vbGJhciI+CiAgPGlucHV0IGlkPSJmaWx0ZXIiIHBsYWNlaG9sZGVyPSJGaWx0ZXIgbGluZXMuLi4iIG9uaW5wdXQ9ImFwcGx5RmlsdGVyKCkiPgogIDxsYWJlbD48aW5wdXQgdHlwZT0iY2hlY2tib3giIGlkPSJhdXRvIiBjaGVja2VkPiBBdXRvLXNjcm9sbDwvbGFiZWw+CiAgPGJ1dHRvbiBvbmNsaWNrPSJkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWU9Jyc7YXBwbHlGaWx0ZXIoKSI+Q2xlYXI8L2J1dHRvbj4KICA8YnV0dG9uIG9uY2xpY2s9ImxvY2F0aW9uLmhyZWY9Jy9hcGkvZG93bmxvYWQ/ZmlsZT0nK2N1ciI+RG93bmxvYWQ8L2J1dHRvbj4KPC9kaXY+CjxkaXYgaWQ9ImxvZ3ZpZXciPjwvZGl2Pgo8c2NyaXB0Pgpjb25zdCBMT0dTPXtmaWxlc19qc307CmxldCBjdXI9TE9HU1swXSxvZmY9MCxyYXc9W107CmZ1bmN0aW9uIGluaXQoKXt7CiAgY29uc3QgdD1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgndGFicycpOwogIExPR1MuZm9yRWFjaChmPT57ewogICAgY29uc3QgZD1kb2N1bWVudC5jcmVhdGVFbGVtZW50KCdkaXYnKTsKICAgIGQuY2xhc3NOYW1lPSd0YWInKyhmPT09Y3VyPycgYWN0aXZlJzonJyk7CiAgICBkLnRleHRDb250ZW50PWY7ZC5vbmNsaWNrPSgpPT5zdyhmKTt0LmFwcGVuZENoaWxkKGQpOwogIH19KTsKICBwb2xsKCk7c2V0SW50ZXJ2YWwocG9sbCwxNTAwKTsKfX0KZnVuY3Rpb24gc3coZil7e2N1cj1mO29mZj0wO3Jhdz1bXTsKICBkb2N1bWVudC5xdWVyeVNlbGVjdG9yQWxsKCcudGFiJykuZm9yRWFjaCh0PT50LmNsYXNzTmFtZT0ndGFiJysodC50ZXh0Q29udGVudD09PWY/JyBhY3RpdmUnOicnKSk7CiAgZG9jdW1lbnQuZ2V0RWxlbWVudEJ5SWQoJ2xvZ3ZpZXcnKS5pbm5lckhUTUw9Jyc7cG9sbCgpO319CmFzeW5jIGZ1bmN0aW9uIHBvbGwoKXt7CiAgdHJ5e3sKICAgIGNvbnN0IHI9YXdhaXQgZmV0Y2goJy9hcGkvbG9nP2ZpbGU9JytlbmNvZGVVUklDb21wb25lbnQoY3VyKSsnJm9mZnNldD0nK29mZik7CiAgICBpZighci5vaylyZXR1cm47CiAgICBjb25zdCBkPWF3YWl0IHIuanNvbigpO29mZj1kLnNpemU7CiAgICBpZihkLmxpbmVzJiZkLmxpbmVzLmxlbmd0aCl7e3Jhdz1yYXcuY29uY2F0KGQubGluZXMpO2lmKHJhdy5sZW5ndGg+NTAwMClyYXc9cmF3LnNsaWNlKC01MDAwKTthcHBseUZpbHRlcigpO319CiAgICBkb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnc3RhdHVzYmFyJykudGV4dENvbnRlbnQ9J1VwZGF0ZWQgJytuZXcgRGF0ZSgpLnRvTG9jYWxlVGltZVN0cmluZygpOwogIH19Y2F0Y2goZSl7e2RvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdzdGF0dXNiYXInKS50ZXh0Q29udGVudD0nRXJyb3I6ICcrZTt9fQp9fQpmdW5jdGlvbiBhcHBseUZpbHRlcigpe3sKICBjb25zdCBmdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnZmlsdGVyJykudmFsdWUudG9Mb3dlckNhc2UoKTsKICBjb25zdCBsdj1kb2N1bWVudC5nZXRFbGVtZW50QnlJZCgnbG9ndmlldycpOwogIGNvbnN0IHJvd3M9ZnY/cmF3LmZpbHRlcihsPT5sLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpKTpyYXc7CiAgbHYuaW5uZXJIVE1MPXJvd3MubWFwKGw9Pnt7CiAgICBjb25zdCBlPWwucmVwbGFjZSgvJi9nLCcmYW1wOycpLnJlcGxhY2UoLzwvZywnJmx0OycpLnJlcGxhY2UoLz4vZywnJmd0OycpOwogICAgY29uc3QgaD1mdj9lLnJlcGxhY2UobmV3IFJlZ0V4cChmdi5yZXBsYWNlKC9bLiorP14ke3t9fSgpfFtcXF1cXFxcXS9nLCdcXFxcJCYnKSwnZ2knKSxtPT4nPHNwYW4gY2xhc3M9ImhsIj4nK20rJzwvc3Bhbj4nKTplOwogICAgcmV0dXJuICc8ZGl2IGNsYXNzPSJsbicrKGZ2JiZsLnRvTG93ZXJDYXNlKCkuaW5jbHVkZXMoZnYpPycgbWF0Y2gnOicnKSsnIj4nK2grJzwvZGl2Pic7CiAgfX0pLmpvaW4oJycpOwogIGlmKGRvY3VtZW50LmdldEVsZW1lbnRCeUlkKCdhdXRvJykuY2hlY2tlZClsdi5zY3JvbGxUb3A9bHYuc2Nyb2xsSGVpZ2h0Owp9fQppbml0KCk7Cjwvc2NyaXB0Pgo8L2JvZHk+PC9odG1sPiIiIgoKUEFHRSA9IGJ1aWxkX3BhZ2UoKQoKY2xhc3MgSGFuZGxlcihCYXNlSFRUUFJlcXVlc3RIYW5kbGVyKToKICAgIGRlZiBsb2dfbWVzc2FnZShzZWxmLCBmbXQsICphcmdzKTogcGFzcwogICAgZGVmIGRvX0dFVChzZWxmKToKICAgICAgICBwID0gc2VsZi5wYXRoLnNwbGl0KCc/JylbMF0KICAgICAgICBpZiBwIGluICgnLycsICcvaW5kZXguaHRtbCcpOgogICAgICAgICAgICBib2R5ID0gUEFHRS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCd0ZXh0L2h0bWw7IGNoYXJzZXQ9dXRmLTgnKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LUxlbmd0aCcsIHN0cihsZW4oYm9keSkpKQogICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoYm9keSkKICAgICAgICBlbGlmIHAgPT0gJy9hcGkvbG9nJzoKICAgICAgICAgICAgcSA9IHBhcnNlX3FzKHVybHBhcnNlKHNlbGYucGF0aCkucXVlcnkpCiAgICAgICAgICAgIGZuYW1lID0gb3MucGF0aC5iYXNlbmFtZShxLmdldCgnZmlsZScsWyd0bHNwcm94eS5sb2cnXSlbMF0pCiAgICAgICAgICAgIG9mZnNldCA9IGludChxLmdldCgnb2Zmc2V0JyxbJzAnXSlbMF0pCiAgICAgICAgICAgIGZwYXRoID0gb3MucGF0aC5qb2luKExPR19ESVIsIGZuYW1lKQogICAgICAgICAgICBsaW5lcyA9IFtdOyBzaXplID0gb2Zmc2V0CiAgICAgICAgICAgIGlmIG9zLnBhdGguZXhpc3RzKGZwYXRoKToKICAgICAgICAgICAgICAgIHdpdGggb3BlbihmcGF0aCwncmInKSBhcyBmOgogICAgICAgICAgICAgICAgICAgIGYuc2VlayhvZmZzZXQpOyBjaHVuayA9IGYucmVhZCgpCiAgICAgICAgICAgICAgICAgICAgc2l6ZSA9IG9mZnNldCArIGxlbihjaHVuaykKICAgICAgICAgICAgICAgICAgICByYXdfdGV4dCA9IGNodW5rLmRlY29kZSgndXRmLTgnLCBlcnJvcnM9J3JlcGxhY2UnKQogICAgICAgICAgICAgICAgICAgIGlmIGZuYW1lLmVuZHN3aXRoKCcuanNvbicpOgogICAgICAgICAgICAgICAgICAgICAgICB0cnk6CiAgICAgICAgICAgICAgICAgICAgICAgICAgICBpbXBvcnQganNvbiBhcyBfanNvbgogICAgICAgICAgICAgICAgICAgICAgICAgICAgcGFyc2VkID0gX2pzb24ubG9hZHMob3BlbihmcGF0aCkucmVhZCgpKQogICAgICAgICAgICAgICAgICAgICAgICAgICAgcHJldHR5ID0gX2pzb24uZHVtcHMocGFyc2VkLCBpbmRlbnQ9MikKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzICA9IHByZXR0eS5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgICAgICAgICAgICAgICAgIHNpemUgICA9IG9zLnBhdGguZ2V0c2l6ZShmcGF0aCkKICAgICAgICAgICAgICAgICAgICAgICAgZXhjZXB0IEV4Y2VwdGlvbjoKICAgICAgICAgICAgICAgICAgICAgICAgICAgIGxpbmVzID0gcmF3X3RleHQuc3BsaXRsaW5lcygpCiAgICAgICAgICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgICAgICAgICAgbGluZXMgPSByYXdfdGV4dC5zcGxpdGxpbmVzKCkKICAgICAgICAgICAgYm9keSA9IGpzb24uZHVtcHMoeydsaW5lcyc6bGluZXMsJ3NpemUnOnNpemV9KS5lbmNvZGUoKQogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoMjAwKQogICAgICAgICAgICBzZWxmLnNlbmRfaGVhZGVyKCdDb250ZW50LVR5cGUnLCdhcHBsaWNhdGlvbi9qc29uJykKICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQWNjZXNzLUNvbnRyb2wtQWxsb3ctT3JpZ2luJywnKicpCiAgICAgICAgICAgIHNlbGYuZW5kX2hlYWRlcnMoKTsgc2VsZi53ZmlsZS53cml0ZShib2R5KQogICAgICAgIGVsaWYgcCA9PSAnL2FwaS9kb3dubG9hZCc6CiAgICAgICAgICAgIHEgPSBwYXJzZV9xcyh1cmxwYXJzZShzZWxmLnBhdGgpLnF1ZXJ5KQogICAgICAgICAgICBmbmFtZSA9IG9zLnBhdGguYmFzZW5hbWUocS5nZXQoJ2ZpbGUnLFsndGxzcHJveHkubG9nJ10pWzBdKQogICAgICAgICAgICBmcGF0aCA9IG9zLnBhdGguam9pbihMT0dfRElSLCBmbmFtZSkKICAgICAgICAgICAgaWYgb3MucGF0aC5leGlzdHMoZnBhdGgpOgogICAgICAgICAgICAgICAgd2l0aCBvcGVuKGZwYXRoLCdyYicpIGFzIGY6IGRhdGEgPSBmLnJlYWQoKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX3Jlc3BvbnNlKDIwMCkKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9oZWFkZXIoJ0NvbnRlbnQtVHlwZScsJ3RleHQvcGxhaW4nKQogICAgICAgICAgICAgICAgc2VsZi5zZW5kX2hlYWRlcignQ29udGVudC1EaXNwb3NpdGlvbicsJ2F0dGFjaG1lbnQ7IGZpbGVuYW1lPScrZm5hbWUpCiAgICAgICAgICAgICAgICBzZWxmLmVuZF9oZWFkZXJzKCk7IHNlbGYud2ZpbGUud3JpdGUoZGF0YSkKICAgICAgICAgICAgZWxzZToKICAgICAgICAgICAgICAgIHNlbGYuc2VuZF9yZXNwb25zZSg0MDQpOyBzZWxmLmVuZF9oZWFkZXJzKCkKICAgICAgICBlbHNlOgogICAgICAgICAgICBzZWxmLnNlbmRfcmVzcG9uc2UoNDA0KTsgc2VsZi5lbmRfaGVhZGVycygpCgpvcy5tYWtlZGlycyhMT0dfRElSLCBleGlzdF9vaz1UcnVlKQpwcmludChmIltsb2d2aWV3ZXJdIDAuMC4wLjA6e1BPUlR9ICBsb2dzPXtMT0dfRElSfSIpCkhUVFBTZXJ2ZXIoKCcwLjAuMC4wJywgUE9SVCksIEhhbmRsZXIpLnNlcnZlX2ZvcmV2ZXIoKQo=' | base64 -d > /opt/logviewer.py
+
+# ---------------------------------------------------------------------------
+# 6. install-ca.sh
+# Installs TLSDebug CA into:
+# (a) Debian/OS system trust store
+# (b) ~/.pki/nssdb — where Chromium/Chrome actually looks on Linux
+# (c) Chrome profile NSS db — belt-and-suspenders
+# ---------------------------------------------------------------------------
+RUN cat > /opt/install-ca.sh << 'SCRIPT'
+#!/bin/bash
+set -e
+CA_CERT="${1:-/opt/tlsdebug/proxy-ca.crt}"
+CA_NAME="TLSDebug CA"
+HOME_NSS="/root/.pki/nssdb"
+PROFILE_NSS="/opt/chrome-profile/nssdb"
+
+log() { echo "[CA] $*"; }
+
+if [ ! -f "${CA_CERT}" ]; then
+ log "ERROR: cert not found at ${CA_CERT}"; exit 1
+fi
+
+log "=== Installing TLSDebug CA ==="
+log "Subject : $(openssl x509 -noout -subject -in "${CA_CERT}" 2>/dev/null)"
+log "Expires : $(openssl x509 -noout -enddate -in "${CA_CERT}" 2>/dev/null)"
+
+# ------------------------------------------------------------------
+# (a) OS / system trust store
+# ------------------------------------------------------------------
+log "--- System trust store ---"
+cp "${CA_CERT}" /usr/local/share/ca-certificates/tlsdebug.crt
+update-ca-certificates --fresh
+log "System store updated."
+
+# ------------------------------------------------------------------
+# (b) ~/.pki/nssdb (THIS is where Chromium on Linux actually looks)
+# ------------------------------------------------------------------
+log "--- ~/.pki/nssdb (Chromium default location) ---"
+mkdir -p "${HOME_NSS}"
+if [ ! -f "${HOME_NSS}/cert9.db" ]; then
+ certutil -N -d "sql:${HOME_NSS}" --empty-password
+ log "Created new NSS db at ${HOME_NSS}"
+fi
+certutil -d "sql:${HOME_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true
+certutil -d "sql:${HOME_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}"
+log "Installed into ${HOME_NSS}"
+
+# Verify
+if certutil -d "sql:${HOME_NSS}" -L -n "${CA_NAME}" 2>/dev/null | grep -q "CT"; then
+ log "OK: ${HOME_NSS} verification passed."
+else
+ log "WARNING: cert not found in ${HOME_NSS} after install"
+fi
+
+# ------------------------------------------------------------------
+# (c) Chrome profile NSS db (belt-and-suspenders)
+# ------------------------------------------------------------------
+log "--- Chrome profile NSS db ---"
+mkdir -p "${PROFILE_NSS}"
+if [ ! -f "${PROFILE_NSS}/cert9.db" ]; then
+ certutil -N -d "sql:${PROFILE_NSS}" --empty-password
+ log "Created new NSS db at ${PROFILE_NSS}"
+fi
+certutil -d "sql:${PROFILE_NSS}" -D -n "${CA_NAME}" 2>/dev/null || true
+certutil -d "sql:${PROFILE_NSS}" -A -t "CT,C,C" -n "${CA_NAME}" -i "${CA_CERT}"
+log "Installed into ${PROFILE_NSS}"
+
+log "=== CA installation complete ==="
+SCRIPT
+RUN chmod +x /opt/install-ca.sh
+
+# ---------------------------------------------------------------------------
+# 6b. generate-novnc-cert.sh — Self-signed certificate for noVNC HTTPS
+# ---------------------------------------------------------------------------
+RUN cat > /opt/generate-novnc-cert.sh << 'SCRIPT'
+#!/bin/bash
+set -e
+
+CERT_DIR="${1:-/opt/novnc-certs}"
+CERT_FILE="${CERT_DIR}/novnc.pem"
+
+log() { echo "[cert] $*"; }
+
+mkdir -p "${CERT_DIR}"
+
+if [ -f "${CERT_FILE}" ]; then
+ log "Certificate already exists: ${CERT_FILE}"
+ log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null || echo 'unknown')"
+ exit 0
+fi
+
+log "Generating self-signed certificate for noVNC..."
+
+openssl req -x509 -nodes -newkey rsa:2048 \
+ -keyout "${CERT_FILE}" \
+ -out "${CERT_FILE}" \
+ -days 365 \
+ -subj "/C=US/ST=State/L=City/O=TLSDebug/CN=localhost" \
+ -addext "subjectAltName=DNS:localhost,DNS:*.local,IP:127.0.0.1" \
+ 2>/dev/null
+
+chmod 600 "${CERT_FILE}"
+
+log "Certificate generated: ${CERT_FILE}"
+log "Subject: $(openssl x509 -noout -subject -in "${CERT_FILE}" 2>/dev/null)"
+log "Expires: $(openssl x509 -noout -enddate -in "${CERT_FILE}" 2>/dev/null)"
+SCRIPT
+RUN chmod +x /opt/generate-novnc-cert.sh
+
+# 7. start-chrome.sh
+# ---------------------------------------------------------------------------
+RUN cat > /opt/start-chrome.sh << 'SCRIPT'
+#!/bin/bash
+export DISPLAY="${DISPLAY:-:99}"
+export HOME=/root
+
+PROXY_HOST="${PROXY_HOST:-127.0.0.1}"
+PROXY_PORT="${PROXY_PORT:-8080}"
+REMOTE_DEBUG_PORT="${CHROME_REMOTE_DEBUGGING_PORT:-9222}"
+START_URL="${START_URL:-https://www.google.com}"
+
+echo "[Browser] $(browser --version 2>/dev/null || echo unknown)"
+echo "[Browser] DISPLAY=${DISPLAY} proxy=${PROXY_HOST}:${PROXY_PORT}"
+
+exec browser \
+ --no-sandbox \
+ --disable-dev-shm-usage \
+ --disable-gpu \
+ --disable-software-rasterizer \
+ --use-gl=swiftshader \
+ --use-angle=swiftshader \
+ --enable-unsafe-swiftshader \
+ --in-process-gpu \
+ --disable-setuid-sandbox \
+ --user-data-dir="/opt/chrome-profile" \
+ --proxy-server="http://${PROXY_HOST}:${PROXY_PORT}" \
+ --ignore-certificate-errors \
+ --ignore-certificate-errors-spki-list \
+ --test-type \
+ --remote-debugging-port="${REMOTE_DEBUG_PORT}" \
+ --remote-debugging-address=0.0.0.0 \
+ --window-size=1920,1080 \
+ --start-fullscreen \
+ --kiosk \
+ --disable-extensions \
+ --no-first-run \
+ --no-default-browser-check \
+ --password-store=basic \
+ --use-mock-keychain \
+ --enable-logging=stderr \
+ --v=1 \
+ "${START_URL}" 2>&1
+SCRIPT
+RUN chmod +x /opt/start-chrome.sh
+
+
+# ---------------------------------------------------------------------------
+# 8. entrypoint.sh
+# ---------------------------------------------------------------------------
+RUN cat > /entrypoint.sh << 'SCRIPT'
+#!/bin/bash
+set -e
+
+export HOME=/root
+PROXY_PORT="${PROXY_PORT:-8080}"
+VNC_PORT="${VNC_PORT:-5900}"
+NOVNC_PORT="${NOVNC_PORT:-6080}"
+DISPLAY="${DISPLAY:-:99}"
+# LOG_DIR is bind-mounted to ./logs/ on the host.
+# CERTDIR is set to the same path so tlsproxy writes captured_tokens.json,
+# session files, and the CA cert directly into the host-visible logs folder.
+LOG_DIR="/var/log/tlsdebug"
+CERTDIR="${LOG_DIR}"
+NOVNC_WEB=$(cat /opt/novnc_path 2>/dev/null || echo "/usr/share/novnc")
+
+mkdir -p "${LOG_DIR}"
+
+log() { echo "[$(date '+%H:%M:%S')] $*"; }
+wait_port() {
+ local port=$1 label=$2 tries=${3:-30}
+ for i in $(seq 1 "${tries}"); do
+ if nc -z 127.0.0.1 "${port}" 2>/dev/null; then
+ log "[+] ${label} ready on port ${port}."
+ return 0
+ fi
+ sleep 0.5
+ done
+ log "[!] TIMEOUT waiting for ${label} on port ${port}."
+ return 1
+}
+
+log "============================================"
+log " TLSDebug + Headless Chrome Container"
+log " noVNC Web UI : http://localhost:${NOVNC_PORT}"
+log " TLS Proxy : localhost:${PROXY_PORT}"
+log " noVNC root : ${NOVNC_WEB}"
+log "============================================"
+
+# ---- Diagnostics -----------------------------------------------------------
+log "[diag] novnc web root contents:"
+ls -la "${NOVNC_WEB}" 2>&1 | head -20 || log " (none)"
+log "[diag] websockify location: $(which websockify 2>/dev/null || echo NOT FOUND)"
+log "[diag] x11vnc location: $(which x11vnc 2>/dev/null || echo NOT FOUND)"
+log "[diag] browser symlink: $(which browser 2>/dev/null || echo NOT FOUND) -> $(readlink /usr/local/bin/browser 2>/dev/null || echo none)"
+log "[diag] tlsproxy location: $(which tlsproxy 2>/dev/null || echo NOT FOUND)"
+
+# ---- 1. Xvfb ---------------------------------------------------------------
+log "[*] Starting Xvfb on ${DISPLAY} ..."
+rm -f /tmp/.X99-lock /tmp/.X11-unix/X99 2>/dev/null || true
+Xvfb "${DISPLAY}" \
+ -screen 0 1920x1080x24 \
+ -ac \
+ +extension GLX \
+ +extension RANDR \
+ +render \
+ -noreset \
+ >> "${LOG_DIR}/xvfb.log" 2>&1 &
+XVFB_PID=$!
+
+log "[*] Waiting for Xvfb ..."
+for i in $(seq 1 30); do
+ if DISPLAY="${DISPLAY}" xdpyinfo >/dev/null 2>&1; then
+ log "[+] Xvfb ready."
+ break
+ fi
+ sleep 0.5
+done
+export DISPLAY="${DISPLAY}"
+
+# ---- 2. Openbox window manager ---------------------------------------------
+log "[*] Starting openbox ..."
+openbox --config-file /root/.config/openbox/rc.xml --display "${DISPLAY}" >> "${LOG_DIR}/openbox.log" 2>&1 &
+sleep 1
+
+# ---- 3. TLSDebug proxy -----------------------------------------------------
+log "[*] Starting TLSDebug proxy on port ${PROXY_PORT} ..."
+cd "${CERTDIR}"
+tlsproxy \
+ -port "${PROXY_PORT}" \
+ -certdir "${CERTDIR}" \
+ -skip-install \
+ 2>&1 | grep -v 'SSLV3_ALERT_CERTIFICATE_UNKNOWN' | tee -a "${LOG_DIR}/tlsproxy.log" &
+PROXY_PID=$!
+
+log "[*] Waiting for CA certificate ..."
+for i in $(seq 1 30); do
+ if [ -f "${CERTDIR}/proxy-ca.crt" ]; then
+ log "[+] CA certificate ready."
+ break
+ fi
+ sleep 1
+done
+# Install CA before Chrome launches — must succeed
+if [ -f "${CERTDIR}/proxy-ca.crt" ]; then
+ if /opt/install-ca.sh "${CERTDIR}/proxy-ca.crt"; then
+ log "[+] CA trusted by OS and Chrome NSS."
+ else
+ log "[!] CA install failed — Chrome may show cert errors."
+ fi
+else
+ log "[!] proxy-ca.crt missing — Chrome may show cert errors."
+fi
+
+# ---- 4. x11vnc -------------------------------------------------------------
+log "[*] Starting x11vnc on port ${VNC_PORT} ..."
+x11vnc \
+ -display "${DISPLAY}" \
+ -forever \
+ -shared \
+ -nopw \
+ -rfbport "${VNC_PORT}" \
+ -noxdamage \
+ -noxfixes \
+ -noxcomposite \
+ -cursor arrow \
+ -loop \
+ -repeat \
+ -xkb \
+ >> "${LOG_DIR}/x11vnc.log" 2>&1 &
+
+sleep 2
+wait_port "${VNC_PORT}" "x11vnc" 40
+
+# ---- 5. noVNC --------------------------------------------------------------
+# Check for custom certificate or generate self-signed
+CERT_DIR="/opt/novnc-certs"
+CUSTOM_CERT="/certs/novnc.pem"
+CERT_FILE="${CERT_DIR}/novnc.pem"
+
+if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then
+ log "[*] HTTPS mode enabled"
+
+ # Use custom cert if provided, otherwise generate self-signed
+ if [ -f "${CUSTOM_CERT}" ]; then
+ log "[*] Using custom certificate: ${CUSTOM_CERT}"
+ mkdir -p "${CERT_DIR}"
+ cp "${CUSTOM_CERT}" "${CERT_FILE}"
+ chmod 600 "${CERT_FILE}"
+ else
+ log "[*] No custom cert found, generating self-signed..."
+ /opt/generate-novnc-cert.sh "${CERT_DIR}"
+ fi
+
+ NOVNC_PORT="${NOVNC_HTTPS_PORT:-6443}"
+ SSL_ARGS="--cert=${CERT_FILE} --ssl-only"
+ SCHEME="https"
+else
+ log "[*] HTTP mode (set NOVNC_ENABLE_HTTPS=true for HTTPS)"
+ SSL_ARGS=""
+ SCHEME="http"
+fi
+
+log "[*] Starting noVNC websockify on port ${NOVNC_PORT} (${SCHEME}) ..."
+log "[*] web root : ${NOVNC_WEB}"
+log "[*] target : 127.0.0.1:${VNC_PORT}"
+
+# note: SSL_ARGS may include --ssl-only; omit to allow fallbacks when certs are untrusted
+tmp_args="${SSL_ARGS}"
+# remove ssl-only if present to avoid 'non-SSL connection received but disallowed' errors
+tmp_args=$(echo "${tmp_args}" | sed 's/--ssl-only//g')
+websockify \
+ --web "${NOVNC_WEB}" \
+ --heartbeat 30 \
+ ${tmp_args} \
+ --log-file "${LOG_DIR}/novnc.log" \
+ "${NOVNC_PORT}" \
+ "127.0.0.1:${VNC_PORT}" &
+NOVNC_PID=$!
+
+wait_port "${NOVNC_PORT}" "noVNC" 40
+
+# Confirm websockify process
+log "[diag] websockify process: $(pgrep -a websockify 2>/dev/null || echo NOT RUNNING)"
+log "[diag] listening ports: $(ss -tlnp 2>/dev/null | grep -E '5900|6080|8080' || echo none)"
+
+# ---- 6. Chrome -------------------------------------------------------------
+# remove stale profile locks which can prevent Chrome from launching
+rm -f /opt/chrome-profile/Singleton* /opt/chrome-profile/Default/Singleton*
+
+log "[*] Starting Chrome ..."
+/opt/start-chrome.sh >> "${LOG_DIR}/chrome.log" 2>&1 &
+CHROME_PID=$!
+
+# ---- 7. Log viewer on port 4040 --------------------------------------------
+log "[*] Starting log viewer on port 4040 ..."
+python3 /opt/logviewer.py >> "${LOG_DIR}/logviewer.log" 2>&1 &
+LOGVIEWER_PID=$!
+wait_port 4040 "log viewer" 20
+
+log ""
+log "[+] All services started."
+log "[+] Browser (noVNC) : ${SCHEME}://localhost:${NOVNC_PORT}"
+log "[+] Log viewer : http://localhost:4040"
+log "[+] TLS proxy : localhost:${PROXY_PORT}"
+log "[+] Host logs dir : ${LOG_DIR} (mounted to ./logs/ on host)"
+if [ "${NOVNC_ENABLE_HTTPS}" = "true" ]; then
+ log "[+] Certificate : ${CERT_FILE}"
+fi
+log ""
+
+cleanup() {
+ log "[*] Shutting down ..."
+ kill "${CHROME_PID}" "${NOVNC_PID}" "${PROXY_PID}" "${XVFB_PID}" "${LOGVIEWER_PID}" 2>/dev/null || true
+ exit 0
+}
+trap cleanup SIGTERM SIGINT
+
+# Stream proxy traffic to docker logs so 'docker logs -f tlsdebug' shows live traffic
+tail -f "${LOG_DIR}/tlsproxy.log"
+SCRIPT
+RUN chmod +x /entrypoint.sh
+
+# ---------------------------------------------------------------------------
+# 9. Ports
+# ---------------------------------------------------------------------------
+EXPOSE 6080 6443 80 443 5900 8080 4040 9222
+
+WORKDIR /opt/tlsdebug
+ENTRYPOINT ["/entrypoint.sh"]
diff --git a/labs/remotebrowser_TLS/README.md b/labs/remotebrowser_TLS/README.md
index 8b13789..6ad042d 100644
--- a/labs/remotebrowser_TLS/README.md
+++ b/labs/remotebrowser_TLS/README.md
@@ -1 +1 @@
-
+### Allow Testing of Browser In Browser
diff --git a/labs/remotebrowser_TLS/docker-compose.yml b/labs/remotebrowser_TLS/docker-compose.yml
new file mode 100644
index 0000000..05a300a
--- /dev/null
+++ b/labs/remotebrowser_TLS/docker-compose.yml
@@ -0,0 +1,58 @@
+services:
+ tlsdebug:
+ build:
+ context: .
+ dockerfile: Dockerfile
+ image: tlsdebug:latest
+ container_name: tlsdebug
+ ports:
+ # noVNC web UI — HTTP on port 6080, HTTPS on port 6443
+ - "6080:6080" # noVNC HTTP (default)
+ - "6443:6443" # noVNC HTTPS (when NOVNC_ENABLE_HTTPS=true)
+ # - "80:6080" # Optional: expose HTTP on port 80
+ - "443:6443" # Optional: expose HTTPS on port 443
+ # Raw VNC (optional — use any VNC client against localhost:5900)
+ - "5900:5900"
+ # TLSDebug intercepting proxy (change host port if 8080 is taken)
+ - "8888:8080"
+ # TLSDebug built-in HTTP log viewer
+ - "4040:4040"
+ # Chrome remote debugging (optional)
+ - "9222:9222"
+ volumes:
+ # All logs, CA cert, captured_tokens.json and session files
+ # are written here — readable on the host at ./logs/
+ - ./logs:/var/log/tlsdebug
+ # Persist Chrome profile (bookmarks, saved state, etc.)
+ - chrome-profile:/opt/chrome-profile
+ # Optional: Mount custom noVNC certificate (PEM format)
+ # - ./certs/novnc.pem:/certs/novnc.pem:ro
+ environment:
+ PROXY_PORT: "8080"
+ VNC_PORT: "5900"
+ NOVNC_PORT: "6080"
+ NOVNC_HTTPS_PORT: "6443"
+ DISPLAY: ":99"
+ CHROME_REMOTE_DEBUGGING_PORT: "9222"
+ # Change START_URL to set the browser's home page
+ START_URL: "https://www.google.com"
+ # Enable HTTPS for noVNC (set to "true" to enable)
+ NOVNC_ENABLE_HTTPS: "true"
+ # When HTTPS is enabled:
+ # - Self-signed cert is auto-generated if no custom cert provided
+ # - Mount custom cert at /certs/novnc.pem to use your own
+ cap_add:
+ - SYS_ADMIN
+ security_opt:
+ - seccomp:unconfined
+ restart: unless-stopped
+ shm_size: "2gb"
+ healthcheck:
+ test: ["CMD", "sh", "-c", "[ \"${NOVNC_ENABLE_HTTPS}\" = \"true\" ] && curl -k -sf https://localhost:6443 || curl -sf http://localhost:6080"]
+ interval: 15s
+ timeout: 5s
+ retries: 5
+ start_period: 30s
+
+volumes:
+ chrome-profile:
\ No newline at end of file
From 3390caf9e939f74a1a086daa71ad4f244716a4bf Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Sun, 8 Mar 2026 20:10:03 -0600
Subject: [PATCH 5/9] Add files via upload
---
labs/remotebrowser_TLS/docker-compose.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/labs/remotebrowser_TLS/docker-compose.yml b/labs/remotebrowser_TLS/docker-compose.yml
index 05a300a..6cebb0b 100644
--- a/labs/remotebrowser_TLS/docker-compose.yml
+++ b/labs/remotebrowser_TLS/docker-compose.yml
@@ -35,7 +35,7 @@ services:
DISPLAY: ":99"
CHROME_REMOTE_DEBUGGING_PORT: "9222"
# Change START_URL to set the browser's home page
- START_URL: "https://www.google.com"
+ START_URL: "https://microsoft.com"
# Enable HTTPS for noVNC (set to "true" to enable)
NOVNC_ENABLE_HTTPS: "true"
# When HTTPS is enabled:
From 6c4e10281b200f7770381d0ed978038e5795be44 Mon Sep 17 00:00:00 2001
From: Casey Smith
Date: Sun, 8 Mar 2026 20:32:18 -0600
Subject: [PATCH 6/9] Update Dockerfile
---
labs/remotebrowser_TLS/Dockerfile | 21 +++++++++++++++++++--
1 file changed, 19 insertions(+), 2 deletions(-)
diff --git a/labs/remotebrowser_TLS/Dockerfile b/labs/remotebrowser_TLS/Dockerfile
index ee8c77a..158cbce 100644
--- a/labs/remotebrowser_TLS/Dockerfile
+++ b/labs/remotebrowser_TLS/Dockerfile
@@ -123,6 +123,14 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML
overflow:hidden; background:#000; }
#screen { position:fixed; top:0; left:0;
width:100vw; height:100vh; background:#000; }
+ /* Force the injected noVNC canvas to fill the container exactly */
+ #screen canvas, #noVNC_canvas {
+ display:block!important;
+ position:absolute!important;
+ top:0!important; left:0!important;
+ width:100%!important; height:100%!important;
+ border:none!important; outline:none!important;
+ }
/* Nuke every noVNC chrome element by ID and class */
#noVNC_control_bar_anchor, #noVNC_control_bar,
#noVNC_control_bar_handle, #noVNC_hint_anchor,
@@ -174,15 +182,24 @@ RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML
const url = proto + '//' + location.hostname + ':' + port;
rfb = new RFB(scrn, url, { credentials: {password:''} });
rfb.scaleViewport = true;
- rfb.resizeSession = false;
+ rfb.resizeSession = true;
rfb.clipViewport = false;
rfb.showDotCursor = false;
rfb.background = '#000000';
- rfb.addEventListener('connect', () => { strip(); msg('TLSDebug Active'); });
+ rfb.addEventListener('connect', () => {
+ strip();
+ msg('TLSDebug Active');
+ // Immediately sync session size to the actual viewport
+ rfb.resizeSession = true;
+ });
rfb.addEventListener('disconnect', () => { msg('Reconnecting...', true); setTimeout(connect, 3000); });
rfb.addEventListener('credentialsrequired', () => rfb.sendCredentials({password:''}));
}
+ // Keep session size in sync whenever the browser window is resized
+ new ResizeObserver(() => { if (rfb) rfb.resizeSession = true; })
+ .observe(document.documentElement);
+
connect();
-
- Connecting...
-
-
From 934664e5dff8bd3e635540fc3d79cfbf7168496b Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Tue, 10 Mar 2026 14:05:31 -0600
Subject: [PATCH 7/9] Revise README for TLSDebug Remote Browser Lab
Updated README to provide detailed setup instructions and features for TLSDebug Remote Browser Lab.
---
labs/remotebrowser/README.md | 66 +++++++++++++++++++++++++++++++++++-
1 file changed, 65 insertions(+), 1 deletion(-)
diff --git a/labs/remotebrowser/README.md b/labs/remotebrowser/README.md
index 6ad042d..3582fd2 100644
--- a/labs/remotebrowser/README.md
+++ b/labs/remotebrowser/README.md
@@ -1 +1,65 @@
-### Allow Testing of Browser In Browser
+# TLSDebug Remote Browser Lab
+
+Browser-in-browser setup for testing HTTPS interception with TLSDebug. All-in-one Docker container with Chrome, noVNC, and the TLS intercepting proxy pre-configured.
+
+## Quick Start
+
+```bash
+docker-compose up
+```
+
+Open your browser:
+- **http://localhost:6080** - Chrome browser interface (proxy already configured)
+- **http://localhost:4040** - Real-time traffic monitor
+
+Browse any HTTPS site in the remote Chrome - all traffic will be intercepted and logged.
+
+## What's Included
+
+- **Headless Chrome** - Full browser with TLS proxy pre-configured
+- **noVNC** - Web-based VNC viewer (no VNC client needed)
+- **TLSDebug Proxy** - Intercepts and logs all HTTPS traffic
+- **Debug Monitor** - Port 4040 shows live traffic, tokens, and sessions
+- **Auto CA Trust** - Proxy certificate automatically trusted in Chrome
+
+## Ports
+
+| Port | Service | Description |
+|------|---------|-------------|
+| 6080 | noVNC | Browser interface - start here |
+| 4040 | Monitor | Real-time traffic viewer |
+| 8888 | Proxy | TLS proxy (mapped from internal 8080) |
+| 5900 | VNC | Raw VNC access (optional) |
+| 9222 | DevTools | Chrome debugging protocol (optional) |
+
+## Debug Monitor (Port 4040)
+
+The monitor shows:
+- **All HTTP requests/responses** with full headers
+- **Session cookies** - Marked with `"session": true`
+- **JWT tokens** - Automatically decoded with claims visible
+- **OAuth tokens** - Access and refresh tokens
+- **POST data** - Form parameters and JSON payloads
+
+## Captured Data
+
+All logs and tokens are saved to `./logs/` on your host:
+- `proxy.log` - Full request/response logs
+- `EditThisCookie_Sessions.json` - All captured cookies and sessions
+- `captured_tokens.json` - JWT and OAuth tokens
+- `proxy-ca.crt` - CA certificate (if you need to trust it elsewhere)
+
+## Configuration
+
+Edit `docker-compose.yml` to customize:
+- `START_URL` - Set the browser's starting page
+- Port mappings - Change if ports conflict
+- `./logs` volume - Where captured data is saved
+
+## Use Cases
+
+- Debug OAuth/SAML flows
+- Inspect JWT token contents
+- Extract session cookies for testing
+- Analyze API request/response patterns
+- Troubleshoot TLS/HTTPS issues
From c0029d5a01ccdab7fe517a1c2f83532dd15e15b9 Mon Sep 17 00:00:00 2001
From: Casey <151179166+secdev02@users.noreply.github.com>
Date: Tue, 10 Mar 2026 14:55:34 -0600
Subject: [PATCH 8/9] Update Dockerfile
fix web socket - tls - false
---
labs/remotebrowser/Dockerfile | 594 ++++------------------------------
1 file changed, 58 insertions(+), 536 deletions(-)
diff --git a/labs/remotebrowser/Dockerfile b/labs/remotebrowser/Dockerfile
index 667255a..a5afa4b 100644
--- a/labs/remotebrowser/Dockerfile
+++ b/labs/remotebrowser/Dockerfile
@@ -1,536 +1,58 @@
-# =============================================================================
-# TLSDebug + Headless Chrome + noVNC — single self-contained Dockerfile
-# =============================================================================
-FROM debian:bookworm-slim
-
-ENV DEBIAN_FRONTEND=noninteractive \
- DISPLAY=:99 \
- VNC_PORT=5900 \
- NOVNC_PORT=6080 \
- PROXY_PORT=8080 \
- CHROME_REMOTE_DEBUGGING_PORT=9222 \
- PROXY_HOST=127.0.0.1
-
-# ---------------------------------------------------------------------------
-# 1. Base packages
-# ---------------------------------------------------------------------------
-RUN apt-get update && apt-get install -y --no-install-recommends \
- golang-go \
- git \
- xvfb \
- x11vnc \
- openbox \
- xterm \
- python3 \
- python3-pip \
- python3-websockify \
- novnc \
- wget \
- gnupg \
- ca-certificates \
- fonts-liberation \
- libasound2 \
- libatk-bridge2.0-0 \
- libatk1.0-0 \
- libcups2 \
- libdbus-1-3 \
- libgdk-pixbuf2.0-0 \
- libgtk-3-0 \
- libnspr4 \
- libnss3 \
- libnss3-tools \
- libx11-xcb1 \
- libxcomposite1 \
- libxdamage1 \
- libxfixes3 \
- libxkbcommon0 \
- libxrandr2 \
- libxshmfence1 \
- xdg-utils \
- openssl \
- curl \
- netcat-openbsd \
- procps \
- psmisc \
- x11-utils \
- unclutter \
- && rm -rf /var/lib/apt/lists/*
-
-# ---------------------------------------------------------------------------
-# ---------------------------------------------------------------------------
-# ---------------------------------------------------------------------------
-# ---------------------------------------------------------------------------
-# 2. Install browser
-# amd64 -> Google Chrome stable (official Google .deb)
-# arm64 -> Chromium from Debian bookworm (native deb, no snap)
-#
-# Base image is debian:bookworm-slim which has real chromium packages
-# on all architectures — no snap wrappers.
-# ---------------------------------------------------------------------------
-RUN ARCH=$(dpkg --print-architecture) && \
- if [ "${ARCH}" = "amd64" ]; then \
- echo "[browser] amd64: installing Google Chrome ..." && \
- wget -q -O /tmp/chrome.deb \
- "https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb" && \
- apt-get update && \
- apt-get install -y --no-install-recommends /tmp/chrome.deb && \
- rm /tmp/chrome.deb && \
- rm -rf /var/lib/apt/lists/* && \
- ln -sf /usr/bin/google-chrome /usr/local/bin/browser && \
- echo "[browser] installed: $(google-chrome --version)"; \
- elif [ "${ARCH}" = "arm64" ]; then \
- echo "[browser] arm64: installing Chromium from Debian bookworm ..." && \
- apt-get update && \
- apt-get install -y --no-install-recommends chromium && \
- rm -rf /var/lib/apt/lists/* && \
- ln -sf /usr/bin/chromium /usr/local/bin/browser && \
- echo "[browser] installed: $(chromium --version)"; \
- else \
- echo "[browser] ERROR: unsupported arch ${ARCH}" && exit 1; \
- fi
-
-# ---------------------------------------------------------------------------
-# 3. Build TLSDebug from source
-# AllTrafficModule is already active by default and logs every request,
-# response, headers and body — no source patching needed.
-# We set log flags via the LOG_FLAGS env var at runtime instead.
-# ---------------------------------------------------------------------------
-WORKDIR /build
-RUN git clone --depth 1 https://github.com/secdev02/TLSDebug.git . \
- && CGO_ENABLED=0 go build -ldflags "-s -w" -o /usr/local/bin/tlsproxy tlsproxy.go \
- && chmod +x /usr/local/bin/tlsproxy \
- && rm -rf /build
-
-# 4. Locate noVNC web root and write kiosk index.html
-# Kiosk mode: auto-connects, scales to fill browser window, hides toolbar,
-# no password prompt, no connection dialog — just the desktop.
-# ---------------------------------------------------------------------------
-RUN find /usr -name "vnc.html" 2>/dev/null | head -1 | xargs -I{} dirname {} > /opt/novnc_path \
- && NOVNC_WEB=$(cat /opt/novnc_path) \
- && echo "noVNC web root: ${NOVNC_WEB}" \
- && ls -la "${NOVNC_WEB}"
-
-RUN NOVNC_WEB=$(cat /opt/novnc_path) && cat > "${NOVNC_WEB}/index.html" << 'HTML'
-
-
-
-
-
TLSDebug
-
-
-
-