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"); + }); +});