From c23932da7e0f24341f5e7d5c4f52a9fe00519139 Mon Sep 17 00:00:00 2001
From: jd316
Date: Thu, 5 Mar 2026 00:05:59 +0530
Subject: [PATCH 1/2] chore(release): verify archive layout for nullclaw CLI
compatibility
---
.github/workflows/ci.yml | 6 +++
.github/workflows/release.yml | 3 ++
docs/operations.md | 7 ++--
scripts/verify-release-package.sh | 70 +++++++++++++++++++++++++++++++
4 files changed, 83 insertions(+), 3 deletions(-)
create mode 100755 scripts/verify-release-package.sh
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 64de277..4ea8d16 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -29,3 +29,9 @@ jobs:
- name: Build
run: npm run build
+
+ - name: Package release artifacts (smoke)
+ run: bash ./scripts/package-release.sh "v0.0.0-ci" release
+
+ - name: Verify release artifact layout
+ run: bash ./scripts/verify-release-package.sh "v0.0.0-ci" release
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index 5245cd5..3ce92f3 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -33,6 +33,9 @@ jobs:
- name: Package release archives
run: bash ./scripts/package-release.sh "${GITHUB_REF_NAME}" release
+ - name: Verify release archive layout
+ run: bash ./scripts/verify-release-package.sh "${GITHUB_REF_NAME}" release
+
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
diff --git a/docs/operations.md b/docs/operations.md
index 47377fb..2827071 100644
--- a/docs/operations.md
+++ b/docs/operations.md
@@ -50,9 +50,10 @@ Security expectations:
3. Manual verification of pairing, chat, approvals, logout.
4. Build production bundle.
5. Verify CLI startup: `npm run cli -- run --host 127.0.0.1 --port 4173`.
-6. Tag a release: `git tag vYYYY.M.D && git push origin vYYYY.M.D`.
-7. Confirm GitHub Actions `Release` workflow attached `.tar.gz` and `.zip` assets.
-8. Confirm diagnostics panel reflects actual E2E/runtime details.
+6. Validate release archive layout: `bash ./scripts/package-release.sh vX.Y.Z release && bash ./scripts/verify-release-package.sh vX.Y.Z release`.
+7. Tag a release: `git tag vYYYY.M.D && git push origin vYYYY.M.D`.
+8. Confirm GitHub Actions `Release` workflow attached `.tar.gz` and `.zip` assets.
+9. Confirm diagnostics panel reflects actual E2E/runtime details.
## GitHub Release Artifacts
diff --git a/scripts/verify-release-package.sh b/scripts/verify-release-package.sh
new file mode 100755
index 0000000..4fcdb38
--- /dev/null
+++ b/scripts/verify-release-package.sh
@@ -0,0 +1,70 @@
+#!/usr/bin/env bash
+set -euo pipefail
+
+VERSION="${1:-}"
+OUTDIR="${2:-release}"
+
+if [[ -z "${VERSION}" ]]; then
+ echo "Usage: $0 [output-dir]"
+ echo "Example: $0 v2026.3.5 release"
+ exit 1
+fi
+
+TAR_PATH="${OUTDIR}/nullclaw-chat-ui-${VERSION}.tar.gz"
+ZIP_PATH="${OUTDIR}/nullclaw-chat-ui-${VERSION}.zip"
+
+require_cmd() {
+ local cmd="$1"
+ if ! command -v "${cmd}" >/dev/null 2>&1; then
+ echo "Missing required command: ${cmd}" >&2
+ exit 1
+ fi
+}
+
+assert_file() {
+ local path="$1"
+ if [[ ! -f "${path}" ]]; then
+ echo "Expected file not found: ${path}" >&2
+ exit 1
+ fi
+}
+
+assert_contains_tar() {
+ local entry="$1"
+ if ! tar -tzf "${TAR_PATH}" | grep -Fx -- "${entry}" >/dev/null; then
+ echo "Tar archive missing entry: ${entry}" >&2
+ exit 1
+ fi
+}
+
+assert_contains_zip() {
+ local entry="$1"
+ if ! unzip -Z1 "${ZIP_PATH}" | grep -Fx -- "${entry}" >/dev/null; then
+ echo "Zip archive missing entry: ${entry}" >&2
+ exit 1
+ fi
+}
+
+require_cmd tar
+require_cmd unzip
+
+assert_file "${TAR_PATH}"
+assert_file "${ZIP_PATH}"
+
+required_entries=(
+ "nullclaw-chat-ui/README.md"
+ "nullclaw-chat-ui/package.json"
+ "nullclaw-chat-ui/nullclaw-chat-ui"
+ "nullclaw-chat-ui/nullclaw-chat-ui.cmd"
+ "nullclaw-chat-ui/bin/nullclaw-chat-ui.js"
+ "nullclaw-chat-ui/build/index.html"
+)
+
+for entry in "${required_entries[@]}"; do
+ assert_contains_tar "${entry}"
+ assert_contains_zip "${entry}"
+done
+
+echo "Verified release artifacts:"
+echo " ${TAR_PATH}"
+echo " ${ZIP_PATH}"
From bb8d002c2b40beff406aa46e3fa09b4fb6e7c4d7 Mon Sep 17 00:00:00 2001
From: Igor Somov
Date: Wed, 4 Mar 2026 19:46:04 -0300
Subject: [PATCH 2/2] fix(ui): redact ws auth token in chat banner
---
src/lib/components/ChatScreen.svelte | 7 ++++++-
src/lib/components/ChatScreen.test.ts | 24 ++++++++++++++++++++++++
2 files changed, 30 insertions(+), 1 deletion(-)
create mode 100644 src/lib/components/ChatScreen.test.ts
diff --git a/src/lib/components/ChatScreen.svelte b/src/lib/components/ChatScreen.svelte
index 49cd933..430a7a0 100644
--- a/src/lib/components/ChatScreen.svelte
+++ b/src/lib/components/ChatScreen.svelte
@@ -4,6 +4,7 @@
ToolCall,
ApprovalRequest,
} from "$lib/stores/session.svelte";
+ import { redactWebSocketAuthToken } from "$lib/protocol/ws-url";
import MessageBubble from "./MessageBubble.svelte";
import ToolCallBlock from "./ToolCallBlock.svelte";
import ApprovalPrompt from "./ApprovalPrompt.svelte";
@@ -54,6 +55,10 @@
return items;
});
+ const safeEndpointUrl = $derived(
+ redactWebSocketAuthToken(endpointUrl) ?? endpointUrl,
+ );
+
function handleSubmit(e: Event) {
e.preventDefault();
const trimmed = input.trim();
@@ -105,7 +110,7 @@
>> ESTABLISHING SECURE E2E CHANNEL... [OK]
- >> CONNECTED TO ENDPOINT: {endpointUrl}
+ >> CONNECTED TO ENDPOINT: {safeEndpointUrl}
{#if initComplete}
diff --git a/src/lib/components/ChatScreen.test.ts b/src/lib/components/ChatScreen.test.ts
new file mode 100644
index 0000000..324a66f
--- /dev/null
+++ b/src/lib/components/ChatScreen.test.ts
@@ -0,0 +1,24 @@
+import { describe, expect, it, vi } from "vitest";
+import { render } from "@testing-library/svelte";
+import ChatScreen from "./ChatScreen.svelte";
+
+describe("ChatScreen", () => {
+ it("redacts websocket auth token in endpoint banner", () => {
+ const { container } = render(ChatScreen, {
+ props: {
+ messages: [],
+ toolCalls: [],
+ approvals: [],
+ error: null,
+ isStreaming: false,
+ endpointUrl: "wss://host.example/ws?token=super-secret&foo=bar",
+ onSend: vi.fn(),
+ onApproval: vi.fn(),
+ },
+ });
+
+ const text = container.textContent ?? "";
+ expect(text).toContain("wss://host.example/ws?token=***&foo=bar");
+ expect(text).not.toContain("super-secret");
+ });
+});