mirror of
https://github.com/tiennm99/goclaw.git
synced 2026-10-14 06:17:41 +00:00
Release: credential resolver, WhatsApp native, exec hardening, traces UI (#754)
* fix(ci): skip CI condition in semantic-release for main branch go-semantic-release auto-detects the default branch from GitHub API (which is dev), but releases are cut from main. The CI condition rejects runs on non-default branches. Use --no-ci to bypass this check since the workflow already gates on push to main. * docs: document CI/CD pipelines, release flow, and v2.66.0 changelog - CLAUDE.md: add CI/CD & Releases section with workflow table, tag patterns, Docker variants, beta/desktop release commands - CONTRIBUTING.md: expand Releases section with standard (auto), beta (manual tag), and desktop release workflows - docs/17-changelog.md: add v2.66.0 entry covering IDOR fix, BytePlus provider, per-agent grants, beta pipeline, and CI fixes * fix(telegram): handle group-to-supergroup migration seamlessly When a Telegram group upgrades to a supergroup, the chat ID changes and all existing references become stale. This caused send failures (400), orphaned sessions, and required manual re-pairing. Add dual-path migration handling: - Proactive: intercept inbound MigrateToChatID before isServiceMessage - Reactive: detect 400 + MigrateToChatID on send, migrate DB, retry DB migration updates in a single transaction (scoped by tenant + channel): - paired_devices: sender_id, chat_id - sessions: session_key, user_id - channel_contacts: sender_id - channel_pending_messages: history_key Also invalidates in-memory caches (approvedGroups, pairingReplySent, groupHistory) and handles media sends via migration retry in Send(). * fix(tools): quote-aware shell operator detection in credentialed exec (#700) (#702) * fix(tools): quote-aware shell operator detection in credentialed exec (#700) - Replace detectShellOperators with detectUnquotedShellOperators in credentialed exec path — respects single/double quoting so that characters like | inside argument values (e.g. --jq '.[0] | .name') are not falsely flagged as shell operators - Pass raw command string (preserving quotes) to executeCredentialed instead of reconstructing from parsed args - Downgrade "no credential found" log from Warn to Debug (fires for every non-credentialed command, too noisy at Warn) - Add extractUnquotedSegments() helper with comprehensive tests * fix(tools): handle backslash escape outside quotes in shell operator detection extractUnquotedSegments did not handle \ as an escape character outside of quotes, causing \" to incorrectly enter double-quote mode. This hid subsequent shell operators from detection (e.g. gh \"arg\" | env would not detect the unquoted pipe). Add backslash escape handling in the unquoted state to match go-shellwords parsing behavior. Both \ and the escaped character are emitted as unquoted content so operator detection still catches them. --------- Co-authored-by: viettranx <viettranx@gmail.com> * feat(infra): tracing recovery, browser cleanup, CLI fixes, UI workspace split (#709) - Tracing: recover stale running traces/spans on startup (PG + SQLite) - Browser: Chrome orphan cleanup via launcher PID, timeouts, Leakless - Claude CLI: WaitDelay 5s + context-cancel early exit - Agent loop: safety-net defer to finalize orphan root traces - UI: split workspace sharing into separate Memory and KG toggles - Minor: for-range idiom, min() builtin * fix(prompt): skip credentialed CLI context when exec tool is denied Agents with exec in their deny list cannot run CLI commands, so injecting wrangler/gh credential context is misleading — the LLM sees instructions for tools it cannot use. Gate the section on exec being present in the filtered tool list. * fix(ui): improve traces table layout and readability Compact columns: status as icon-only, merge time+duration into one column. Truncate long user IDs, clean <media:*> tags from preview, move badges to second line. * fix(security): harden exec path exemption matching - Add absolute path exemption for dataDir/skills-store/ (fixes skill scripts using absolute paths like /app/data/skills-store/ being denied) - Strip surrounding quotes before prefix matching (LLMs often quote paths) - Reject path traversal ("..") in exempt fields to prevent escape - Switch from "any field exempt → skip" to per-field matching: only exempt if ALL fields that match the deny pattern are individually exempt - Closes pipe/comment bypass vectors where an exempt path in one argument would exempt the entire command including non-exempt paths Includes 27 test cases covering: legitimate access, quoted paths, path traversal, unicode bypass, pipe/comment bypass, mixed args. * feat(whatsapp): add native WhatsApp channel with whatsmeow (#720) Replace Node.js Baileys bridge with native go.mau.fi/whatsmeow — zero external dependencies. QR auth, media support, markdown formatting, typing indicators, dual JID/LID identity, group policies, pairing. Resolves #703 * fix(chat): load message history on first conversation click (#730) * fix(chat): load message history when selecting existing conversation from clean state The skipNextHistoryRef was unconditionally set when sessionKey transitioned from empty to non-empty. This prevented loadHistory() from running when clicking an existing conversation from the initial /chat page. The skip was only intended for the new-chat send flow where the optimistic message is already displayed. Guard the skip with expectingRunRef so it only activates when a message send is in flight. Closes #729 * docs: add UI diff evidence for PR #730 Before/after screenshots and HTML comparison report showing first conversation click behavior fix. * fix(permissions): use cron-specific permission check for cron tool (#725) * fix(security): harden exec path exemption matching (#721) - Add absolute path exemption for dataDir/skills-store/ (fixes skill scripts using absolute paths like /app/data/skills-store/ being denied) - Strip surrounding quotes before prefix matching (LLMs often quote paths) - Reject path traversal ("..") in exempt fields to prevent escape - Switch from "any field exempt → skip" to per-field matching: only exempt if ALL fields that match the deny pattern are individually exempt - Closes pipe/comment bypass vectors where an exempt path in one argument would exempt the entire command including non-exempt paths Includes 27 test cases covering: legitimate access, quoted paths, path traversal, unicode bypass, pipe/comment bypass, mixed args. * fix(permissions): use cron-specific permission check for cron tool Cron tool was hardcoded to check `file_writer` configType via CheckFileWriterPermission(), ignoring the `cron` configType that the UI actually saves when granting cron permissions. This caused agents in group chats to be denied cron access even with correct permission configured. Add ConfigTypeCron constant and CheckCronPermission() that checks `cron` configType first, falling back to `file_writer`. --------- Co-authored-by: Viet Tran <viettranx@gmail.com> * fix(exec): allow uploaded files in active workspaces (#748) Shell-aware command parsing, dynamic workspace exemptions, and symlink canonicalization for exec path denial. Fixes #739. * refactor(exec): extract path exemption logic to separate file Move shell-aware parsing, dynamic workspace exemptions, path alias variants, and canonicalization functions from shell.go (688 LOC) to shell_path_exemption.go (284 LOC) for maintainability. * feat(agent): centralized tenant user identity resolution for credentials Add CredentialUserID context key that resolves channel contacts to merged tenant users for credential lookups (SecureCLI, MCP). Keeps UserID unchanged for session/workspace scoping. Resolves group senders, group contacts, and unresolved DMs via ContactStore with 60s TTL cache. * fix(ui): improve traces table column layout Add width constraints and whitespace-nowrap to prevent column wrapping on narrow viewports. Cherry-picked from dev-v3. * fix(ui): enable search filtering on knowledge graph view (#758) Search box on /knowledge-graph only filtered table view. Add useMemo client-side filtering of graph entities by name/type/description, only show relations where both endpoints match. Closes #759 * feat: add zoom controls to knowledge graph (#757) Add +/- buttons and percentage display to the knowledge graph stats bar so users can zoom without relying solely on mouse wheel. Uses existing react-force-graph-2D zoom() API with 1.5x step and 300ms animation. Closes #755 Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> * fix(providers): use DB name for ClaudeCLI, ACP, and Anthropic registration Provider Name() methods returned hardcoded strings, so DB-registered providers with custom names got wrong registry key — causing "provider not found" fallback. Add WithClaudeCLIName/WithACPName/WithAnthropicName options, pass p.Name from DB registration paths. Config-based paths keep hardcoded defaults. Closes #742 * fix(agent): correct soft-trim head/tail budget allocation when tail is important (#723) Co-authored-by: quxy5 <quxy5@outlook.com> --------- Co-authored-by: Duy /zuey/ <duy@wearetopgroup.com> Co-authored-by: Duc Nguyen <me@vanducng.dev> Co-authored-by: Kai (Tam Nhu) Tran <61256810+kaitranntt@users.noreply.github.com> Co-authored-by: Plateau Nguyen <nguyennlt.ncc@gmail.com> Co-authored-by: Reski Rukmantiyo <reski.rukmantio@lintasarta.co.id> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Co-authored-by: QuXiangyu <771744189@qq.com> Co-authored-by: quxy5 <quxy5@outlook.com>
This commit is contained in:
85 files changed
+4076
-605
No files matched your search
Binary file not shown.
|
After Width: | Height: | Size: 233 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 234 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 100 KiB |
@@ -0,0 +1,71 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PR #730 — fix(chat): first conversation click</title>
|
||||
<style>
|
||||
:root { --bg: #0d0d0d; --fg: #e8e4df; --card: #1a1714; --border: #2d2923; --accent: #ef4444; --muted: #6b6560; }
|
||||
.light { --bg: #f8f6f3; --fg: #1a1714; --card: #ffffff; --border: #e5e0da; }
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif; background: var(--bg); color: var(--fg); padding: 2rem; }
|
||||
.theme-toggle { position: fixed; top: 1rem; right: 1rem; background: var(--card); border: 1px solid var(--border); color: var(--fg); padding: 0.5rem 1rem; border-radius: 6px; cursor: pointer; z-index: 100; }
|
||||
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
||||
.subtitle { color: var(--muted); margin-bottom: 2rem; }
|
||||
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
||||
.summary-card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; }
|
||||
.summary-card h3 { font-size: 0.75rem; text-transform: uppercase; color: var(--muted); margin-bottom: 0.5rem; }
|
||||
.env { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 1rem; margin-bottom: 2rem; font-size: 0.85rem; color: var(--muted); }
|
||||
.comparison { margin-bottom: 3rem; }
|
||||
.comparison h2 { font-size: 1.1rem; margin-bottom: 1rem; border-bottom: 1px solid var(--border); padding-bottom: 0.5rem; }
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } }
|
||||
.col-header { font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 0.5rem; }
|
||||
.col-header.before { color: var(--muted); }
|
||||
.col-header.after { color: var(--accent); }
|
||||
.grid img { width: 100%; border: 1px solid var(--border); border-radius: 6px; }
|
||||
.desc { font-size: 0.85rem; color: var(--muted); margin-top: 0.5rem; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button class="theme-toggle" onclick="document.documentElement.classList.toggle('light'); this.textContent = document.documentElement.classList.contains('light') ? '● Dark' : '○ Light'">○ Light</button>
|
||||
|
||||
<h1>fix(chat): load message history on first conversation click</h1>
|
||||
<p class="subtitle">PR #730 — Closes #729</p>
|
||||
|
||||
<div class="summary">
|
||||
<div class="summary-card">
|
||||
<h3>Bug</h3>
|
||||
<p>First conversation click from clean state shows empty placeholder instead of messages</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<h3>Root Cause</h3>
|
||||
<p><code>skipNextHistoryRef</code> unconditionally set on empty→non-empty transition, skipping <code>loadHistory()</code></p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<h3>Fix</h3>
|
||||
<p>Guard skip with <code>expectingRunRef.current</code> — only active during new-chat send flow</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="env">
|
||||
Same instance (claw:3000 backend), same user (kai/Master), same agent (glm thục bô), same conversation (Chat a95c8ffa), light theme. Callout borders mark the review area.
|
||||
</div>
|
||||
|
||||
<div class="comparison">
|
||||
<h2>Chat — First Click from Clean State</h2>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div class="col-header before">BEFORE</div>
|
||||
<img src="before-chat-first-click.png" alt="Before: empty placeholder on first click">
|
||||
<p class="desc">Before: Clicking "Chat a95c8ffa" from initial state — conversation highlighted but main area shows "Start a conversation" placeholder. Messages never load.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-header after">AFTER</div>
|
||||
<img src="after-chat-first-click.png" alt="After: messages load on first click">
|
||||
<p class="desc">After: Same click — conversation messages load immediately. The 5-message history is displayed correctly on first click.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,25 @@
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths
|
||||
2026/04/07 21:24:13 INFO memory + knowledge graph tools registered (PG-backed)
|
||||
2026/04/07 21:24:13 INFO web_search tool enabled
|
||||
2026/04/07 21:24:13 INFO web_fetch tool enabled policy="" blocked=0
|
||||
2026/04/07 21:24:13 INFO tts enabled provider=edge auto=off
|
||||
2026/04/07 21:24:13 INFO tool rate limiting enabled per_hour=150
|
||||
2026/04/07 21:24:13 INFO exec approval enabled security=full ask=off
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed
|
||||
2026/04/07 21:24:13 WARN secure_cli.lookup: store is nil, skipping credentialed exec command="printf '%s' file=@\"/var/folders/t9/0cyfylls7d1gdy_6qp8kn2qc0000gn/T/TestSetupToolRegistryExecWorkspacePaths3385685647/001/personal/.uploads/Quarterly Report.png\""
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed
|
||||
2026/04/07 21:24:13 WARN secure_cli.lookup: store is nil, skipping credentialed exec command="printf '%s' --input=\"/var/folders/t9/0cyfylls7d1gdy_6qp8kn2qc0000gn/T/TestSetupToolRegistryExecWorkspacePaths3385685647/001/teams/team-123/Quarterly Report.png\""
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed
|
||||
2026/04/07 21:24:13 WARN secure_cli.lookup: store is nil, skipping credentialed exec command="cp \"/var/folders/t9/0cyfylls7d1gdy_6qp8kn2qc0000gn/T/TestSetupToolRegistryExecWorkspacePaths3385685647/001/.goclaw/goclaw-workspace/ws/system/uploads/Quarterly Report.png\" \"/var/folders/t9/0cyfylls7d1gdy_6qp8kn2qc0000gn/T/TestSetupToolRegistryExecWorkspacePaths3385685647/002/partner.png\""
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_symlink_escape_denied
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/unrelated_data_dir_path_denied
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/workspace_local_dotgoclaw_denied
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths (0.02s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed (0.01s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed (0.01s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/team_workspace_symlink_escape_denied (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/unrelated_data_dir_path_denied (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/workspace_local_dotgoclaw_denied (0.00s)
|
||||
PASS
|
||||
ok github.com/nextlevelbuilder/goclaw/cmd 0.041s
|
||||
@@ -0,0 +1,94 @@
|
||||
go: downloading github.com/golang-migrate/migrate/v4 v4.19.1
|
||||
go: downloading github.com/jackc/pgx/v5 v5.6.0
|
||||
go: downloading github.com/google/uuid v1.6.0
|
||||
go: downloading github.com/gorilla/websocket v1.5.4-0.20250319132907-e064f32e3674
|
||||
go: downloading github.com/charmbracelet/huh v0.8.0
|
||||
go: downloading github.com/spf13/cobra v1.10.2
|
||||
go: downloading github.com/bwmarrin/discordgo v0.29.0
|
||||
go: downloading golang.org/x/sync v0.19.0
|
||||
go: downloading github.com/slack-go/slack v0.19.0
|
||||
go: downloading github.com/mattn/go-runewidth v0.0.16
|
||||
go: downloading github.com/mymmrac/telego v1.6.0
|
||||
go: downloading github.com/fsnotify/fsnotify v1.9.0
|
||||
go: downloading github.com/titanous/json5 v1.0.0
|
||||
go: downloading github.com/disintegration/imaging v1.6.2
|
||||
go: downloading golang.org/x/image v0.27.0
|
||||
go: downloading golang.org/x/time v0.14.0
|
||||
go: downloading github.com/mark3labs/mcp-go v0.44.0
|
||||
go: downloading github.com/adhocore/gronx v1.19.6
|
||||
go: downloading github.com/go-rod/rod v0.116.2
|
||||
go: downloading github.com/lib/pq v1.10.9
|
||||
go: downloading github.com/mattn/go-shellwords v1.0.12
|
||||
go: downloading golang.org/x/net v0.49.0
|
||||
go: downloading golang.org/x/text v0.33.0
|
||||
go: downloading github.com/rivo/uniseg v0.4.7
|
||||
go: downloading github.com/catppuccin/go v0.3.0
|
||||
go: downloading github.com/charmbracelet/bubbles v0.21.1-0.20250623103423-23b8fd6302d7
|
||||
go: downloading github.com/charmbracelet/bubbletea v1.3.6
|
||||
go: downloading github.com/charmbracelet/lipgloss v1.1.0
|
||||
go: downloading github.com/charmbracelet/x/cellbuf v0.0.13
|
||||
go: downloading github.com/charmbracelet/x/exp/strings v0.0.0-20240722160745-212f7b056ed0
|
||||
go: downloading github.com/mitchellh/hashstructure/v2 v2.0.2
|
||||
go: downloading github.com/spf13/pflag v1.0.9
|
||||
go: downloading golang.org/x/crypto v0.47.0
|
||||
go: downloading golang.org/x/sys v0.42.0
|
||||
go: downloading github.com/valyala/fasthttp v1.69.0
|
||||
go: downloading github.com/invopop/jsonschema v0.13.0
|
||||
go: downloading github.com/spf13/cast v1.7.1
|
||||
go: downloading github.com/yosida95/uritemplate/v3 v3.0.2
|
||||
go: downloading github.com/dustin/go-humanize v1.0.1
|
||||
go: downloading github.com/charmbracelet/x/ansi v0.9.3
|
||||
go: downloading github.com/charmbracelet/x/term v0.2.1
|
||||
go: downloading github.com/atotto/clipboard v0.1.4
|
||||
go: downloading github.com/grbit/go-json v0.11.0
|
||||
go: downloading github.com/valyala/fastjson v1.6.7
|
||||
go: downloading github.com/muesli/ansi v0.0.0-20230316100256-276c6243b2f6
|
||||
go: downloading github.com/muesli/cancelreader v0.2.2
|
||||
go: downloading github.com/muesli/termenv v0.16.0
|
||||
go: downloading github.com/wk8/go-ordered-map/v2 v2.1.8
|
||||
go: downloading github.com/ysmood/goob v0.4.0
|
||||
go: downloading github.com/ysmood/got v0.40.0
|
||||
go: downloading github.com/ysmood/gson v0.7.3
|
||||
go: downloading github.com/ysmood/fetchup v0.2.3
|
||||
go: downloading github.com/ysmood/leakless v0.9.0
|
||||
go: downloading github.com/andybalholm/brotli v1.2.0
|
||||
go: downloading github.com/klauspost/compress v1.18.2
|
||||
go: downloading github.com/valyala/bytebufferpool v1.0.0
|
||||
go: downloading github.com/jackc/pgpassfile v1.0.0
|
||||
go: downloading github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761
|
||||
go: downloading github.com/jackc/puddle/v2 v2.2.2
|
||||
go: downloading github.com/lucasb-eyer/go-colorful v1.2.0
|
||||
go: downloading github.com/bahlo/generic-list-go v0.2.0
|
||||
go: downloading github.com/buger/jsonparser v1.1.1
|
||||
go: downloading github.com/mailru/easyjson v0.7.7
|
||||
go: downloading gopkg.in/yaml.v3 v3.0.1
|
||||
go: downloading github.com/aymanbagabas/go-osc52/v2 v2.0.1
|
||||
go: downloading github.com/mattn/go-isatty v0.0.20
|
||||
go: downloading github.com/charmbracelet/colorprofile v0.2.3-0.20250311203215-f60798e515dc
|
||||
go: downloading github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths
|
||||
2026/04/08 01:24:40 INFO memory + knowledge graph tools registered (PG-backed)
|
||||
2026/04/08 01:24:40 INFO web_search tool enabled
|
||||
2026/04/08 01:24:40 INFO web_fetch tool enabled policy="" blocked=0
|
||||
2026/04/08 01:24:40 INFO tts enabled provider=edge auto=off
|
||||
2026/04/08 01:24:40 INFO tool rate limiting enabled per_hour=150
|
||||
2026/04/08 01:24:40 INFO exec approval enabled security=full ask=off
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed
|
||||
gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths1657441769/001
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed
|
||||
gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths1657441769/001
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed
|
||||
gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths1657441769/001
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_symlink_escape_denied
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/unrelated_data_dir_path_denied
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/workspace_local_dotgoclaw_denied
|
||||
--- FAIL: TestSetupToolRegistryExecWorkspacePaths (0.00s)
|
||||
--- FAIL: TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed (0.00s)
|
||||
--- FAIL: TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed (0.00s)
|
||||
--- FAIL: TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/team_workspace_symlink_escape_denied (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/unrelated_data_dir_path_denied (0.00s)
|
||||
--- PASS: TestSetupToolRegistryExecWorkspacePaths/workspace_local_dotgoclaw_denied (0.00s)
|
||||
FAIL
|
||||
FAIL github.com/nextlevelbuilder/goclaw/cmd 0.019s
|
||||
FAIL
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 183 KiB |
@@ -0,0 +1,228 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PR #748 — fix(exec): allow uploaded files in active workspaces</title>
|
||||
<style>
|
||||
:root {
|
||||
--bg: #0d0d0d;
|
||||
--fg: #e8e4df;
|
||||
--card: #1a1714;
|
||||
--border: #2d2923;
|
||||
--accent: #ef4444;
|
||||
--good: #22c55e;
|
||||
--muted: #8a837d;
|
||||
--code: #151210;
|
||||
}
|
||||
.light {
|
||||
--bg: #f8f6f3;
|
||||
--fg: #1a1714;
|
||||
--card: #ffffff;
|
||||
--border: #e5e0da;
|
||||
--accent: #dc2626;
|
||||
--good: #15803d;
|
||||
--muted: #6b6560;
|
||||
--code: #f3f0eb;
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--fg);
|
||||
padding: 2rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.theme-toggle {
|
||||
position: fixed;
|
||||
top: 1rem;
|
||||
right: 1rem;
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
color: var(--fg);
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
z-index: 100;
|
||||
}
|
||||
h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
|
||||
.subtitle { color: var(--muted); margin-bottom: 2rem; }
|
||||
.summary { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: 1rem; margin-bottom: 2rem; }
|
||||
.summary-card, .env, .comparison, .supporting {
|
||||
background: var(--card);
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 10px;
|
||||
}
|
||||
.summary-card { padding: 1rem; }
|
||||
.summary-card h3 {
|
||||
font-size: 0.75rem;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 0.5rem;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
.env, .supporting, .comparison { padding: 1.25rem; margin-bottom: 1.5rem; }
|
||||
.env { color: var(--muted); font-size: 0.92rem; }
|
||||
.comparison h2, .supporting h2 {
|
||||
font-size: 1.05rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.grid { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; }
|
||||
@media (max-width: 980px) { .grid { grid-template-columns: 1fr; } }
|
||||
.col-header {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
.before { color: var(--muted); }
|
||||
.after { color: var(--accent); }
|
||||
.panel {
|
||||
border: 1px solid var(--border);
|
||||
border-radius: 8px;
|
||||
overflow: hidden;
|
||||
background: var(--code);
|
||||
}
|
||||
.panel img {
|
||||
display: block;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
.terminal {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.45;
|
||||
white-space: pre-wrap;
|
||||
overflow-wrap: anywhere;
|
||||
padding: 1rem;
|
||||
min-height: 320px;
|
||||
}
|
||||
.hl-bad {
|
||||
display: inline-block;
|
||||
background: rgba(239, 68, 68, 0.14);
|
||||
border-left: 3px solid var(--accent);
|
||||
padding: 0.05rem 0.35rem;
|
||||
}
|
||||
.hl-good {
|
||||
display: inline-block;
|
||||
background: rgba(34, 197, 94, 0.14);
|
||||
border-left: 3px solid var(--good);
|
||||
padding: 0.05rem 0.35rem;
|
||||
}
|
||||
.desc {
|
||||
color: var(--muted);
|
||||
font-size: 0.88rem;
|
||||
margin-top: 0.65rem;
|
||||
}
|
||||
code {
|
||||
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
|
||||
background: var(--code);
|
||||
padding: 0.12rem 0.32rem;
|
||||
border-radius: 4px;
|
||||
}
|
||||
ul { margin-left: 1.1rem; color: var(--muted); }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<button class="theme-toggle" onclick="document.documentElement.classList.toggle('light'); this.textContent = document.documentElement.classList.contains('light') ? '● Dark' : '○ Light'">○ Light</button>
|
||||
|
||||
<h1>fix(exec): allow uploaded files in active workspaces</h1>
|
||||
<p class="subtitle">PR #748 — visual proof pack for issue #739</p>
|
||||
|
||||
<div class="summary">
|
||||
<div class="summary-card">
|
||||
<h3>Bug</h3>
|
||||
<p>Active workspace files under deny-roots were rejected by <code>exec</code> even when they belonged to the current upload/team scope.</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<h3>Root Cause</h3>
|
||||
<p>Path exemptions were static and non-shell-aware, so quoted, prefixed, and legacy screenshot-era upload paths still matched the deny root.</p>
|
||||
</div>
|
||||
<div class="summary-card">
|
||||
<h3>Fix</h3>
|
||||
<p>Shell-aware parsing + canonical path checks now exempt the active personal <code>.uploads</code> subtree, the legacy <code>uploads</code> subtree, and the active team workspace.</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="env">
|
||||
Before source: stable <code>claw:3000</code> backed by the unfixed <code>/opt/goclaw</code> deployment (<code>v2.7.1</code>). After source: temporary PR deployment on <code>claw:3001</code>, backed by <code>goclaw-pr748:local</code> built from the fixed code commit <code>83fa1342</code>. Both UI captures use fresh chats with the same user and <code>GLM thục bô</code>. Callout border marks the reviewed <code>exec</code> row.
|
||||
</div>
|
||||
|
||||
<div class="comparison">
|
||||
<h2>UI Before / After</h2>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div class="col-header before">Before</div>
|
||||
<div class="panel">
|
||||
<img src="pr748-before-absolute-goclaw-blocked.png" alt="Before: stable claw blocks absolute .goclaw upload path">
|
||||
</div>
|
||||
<p class="desc">Stable <code>claw</code> release, fresh chat with <code>GLM thục bô</code>: the agent runs the exact absolute <code>/app/.goclaw/.../.uploads/...jpg</code> copy command and the <code>exec</code> row fails with the safety-policy <code>.goclaw/</code> denial.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-header after">After</div>
|
||||
<div class="panel">
|
||||
<img src="pr748-after-absolute-goclaw-allowed.png" alt="After: fixed temporary claw deployment allows absolute .goclaw upload path">
|
||||
</div>
|
||||
<p class="desc">Temporary fixed deployment on <code>claw:3001</code>, fresh chat with the same agent: the same absolute <code>/app/.goclaw/.../.uploads/...jpg</code> copy command now completes successfully and returns <code>/tmp/partner-abs.png</code>.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="comparison">
|
||||
<h2>Gateway Reproduction</h2>
|
||||
<div class="grid">
|
||||
<div>
|
||||
<div class="col-header before">Before</div>
|
||||
<div class="panel">
|
||||
<pre class="terminal">$ ssh claw 'docker run --rm -v /tmp/goclaw-739-before:/src -w /src golang:1.26 sh -lc "/usr/local/go/bin/go test ./cmd -run TestSetupToolRegistryExecWorkspacePaths -count=1 -v"'
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed
|
||||
<span class="hl-bad">gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths.../001</span>
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed
|
||||
<span class="hl-bad">gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths.../001</span>
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed
|
||||
<span class="hl-bad">gateway_setup_exec_workspace_test.go:133: denied = true, want false; output = command denied by safety policy: matches pattern /tmp/TestSetupToolRegistryExecWorkspacePaths.../001</span>
|
||||
FAIL</pre>
|
||||
</div>
|
||||
<p class="desc">Baseline source on <code>claw</code>: the exact gateway wiring still rejects all three representative workspace-path forms.</p>
|
||||
</div>
|
||||
<div>
|
||||
<div class="col-header after">After</div>
|
||||
<div class="panel">
|
||||
<pre class="terminal">$ go test ./cmd -run TestSetupToolRegistryExecWorkspacePaths -count=1 -v
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/personal_workspace_prefixed_uploads_allowed
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/team_workspace_quoted_input_allowed
|
||||
=== RUN TestSetupToolRegistryExecWorkspacePaths/legacy_dotgoclaw_uploads_layout_allowed
|
||||
<span class="hl-good">--- PASS: TestSetupToolRegistryExecWorkspacePaths (0.01s)</span>
|
||||
<span class="hl-good"> --- PASS: personal_workspace_prefixed_uploads_allowed</span>
|
||||
<span class="hl-good"> --- PASS: team_workspace_quoted_input_allowed</span>
|
||||
<span class="hl-good"> --- PASS: legacy_dotgoclaw_uploads_layout_allowed</span>
|
||||
PASS</pre>
|
||||
</div>
|
||||
<p class="desc">Current worktree: the same gateway path handling now passes for personal uploads, team workspace paths, and the screenshot-era legacy <code>.goclaw/.../uploads</code> layout.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="supporting">
|
||||
<h2>Supporting Live Recreation on <code>claw</code></h2>
|
||||
<div class="panel">
|
||||
<img src="claw-live-glm-thuc-bo-before.png" alt="Live claw recreation with GLM thục bô">
|
||||
</div>
|
||||
<p class="desc">
|
||||
This earlier live run documents the same production agent and workspace context on stable <code>claw</code>. The report’s primary Before/After pair above is the clean isolated proof you asked for; this screenshot is kept as supporting context for the reproduction journey.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="comparison">
|
||||
<h2>What This Proves</h2>
|
||||
<ul>
|
||||
<li>Quoted/prefixed upload arguments now survive the deny matcher when they point into the active personal <code>.uploads</code> subtree, the legacy <code>.goclaw/.../uploads</code> subtree, or the active team workspace.</li>
|
||||
<li>Symlink escapes, unrelated <code>dataDir</code> files, and workspace-local <code>.goclaw/*</code> remain denied.</li>
|
||||
<li>The visual behavior difference is now shown on two real frontends: stable <code>claw:3000</code> Before and temporary fixed <code>claw:3001</code> After, both using fresh conversations.</li>
|
||||
</ul>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 142 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 230 KiB |
Reference in new issue
Block a user