From 497d92f353d59a290ed22e48b1c12b0b20c67fcd Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Wed, 12 Aug 2026 16:26:26 +0800 Subject: [PATCH 1/6] Add product PRD with M1 trust-and-gates roadmap. Captures current v1.3 scope, requirements, and prioritized bugs/enhancements so milestone work can be tracked as issues. --- PRD.md | 238 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 238 insertions(+) create mode 100644 PRD.md diff --git a/PRD.md b/PRD.md new file mode 100644 index 0000000..365390a --- /dev/null +++ b/PRD.md @@ -0,0 +1,238 @@ +# CloudTerm — Product Requirements Document + +| Field | Value | +|-------|-------| +| **Product** | CloudTerm | +| **Owner** | CloudBlast | +| **Version covered** | 1.3.0 (as shipped) | +| **Status** | Living document — current product + next roadmap | +| **License** | Fair-code (free to use/modify/share; commercial resale needs a CloudBlast license) | +| **Repo** | https://github.com/BradPerbs/cloudterm | + +--- + +## 1. Vision + +CloudTerm is a **desktop multi-protocol terminal workspace**: one window for SSH, SFTP, Telnet, serial, RDP, VNC, and BMC/IPMI, with encrypted local vault storage, optional CloudBlast cloud sync, and a local AI agent that can act through live sessions (with approval). + +**North star:** Replace tool sprawl (PuTTY + WinSCP + RDP client + VNC + serial terminal + snippet managers) with a single, trustworthy, modern app that travels with the user’s setup across machines. + +--- + +## 2. Problem + +| Pain | Today without CloudTerm | +|------|-------------------------| +| Tool sprawl | Separate apps per protocol; repeated logins and context switching | +| Setup doesn’t travel | Hosts, keys, snippets, known hosts differ per laptop | +| AI is paste-driven | Assistants don’t see the live terminal; users copy/paste riskily | +| CloudBlast customers | Manually re-enter VPS details instead of seeing servers ready to connect | + +--- + +## 3. Goals & non-goals + +### Goals + +1. **One workspace** for every common remote-access protocol on Windows, macOS, and Linux. +2. **Secrets stay local** — vault-locked encrypted storage; cloud sync encrypts before upload. +3. **CloudBlast differentiation** — free encrypted sync for everyone; auto host list for CloudBlast VPS customers. +4. **Session-aware AI** — local CLIs (Claude Code / Codex / OpenCode) with approval gates; no credential tools. +5. **Trustworthy distribution** — signed installs, predictable updates, clear fair-code terms. + +### Non-goals (near term) + +- Becoming a full IDE or browser-based terminal SaaS +- Replacing enterprise PAM / jump-box products +- Storing plaintext credentials in the cloud +- Shipping AI that can extract or export vault secrets +- Guaranteeing recovery if the vault password is forgotten + +--- + +## 4. Personas + +| Persona | Needs | Success looks like | +|---------|--------|-------------------| +| **Sysadmin / SRE** | Many hosts, jump hosts, tunnels, monitoring | Folders/tags/search; reconnect; uptime checks; activity log | +| **Developer** | SSH + SFTP + occasional RDP/VNC | Fast connect, split panes, snippets, remote edit | +| **CloudBlast customer** | Servers appear without manual entry | Sign in → VPS hosts ready | +| **Multi-machine user** | Same setup on laptop + desktop | Encrypted cloud snapshot restore on sign-in | +| **AI-assisted operator** | Agent reads session and runs approved commands | Clear approval UX; safe defaults | + +--- + +## 5. Current product (v1.3) + +### 5.1 User-facing capabilities + +| Area | Capability | +|------|------------| +| **Sessions** | SSH, Telnet, serial; jump hosts; SOCKS/HTTP proxies; tabs & split panes (max 8); themes; find; broadcast; screenshots; session log | +| **Files** | SFTP (list/mkdir/delete/rename, transfers, resume, conflicts, drag-and-drop); remote edit → upload on save | +| **Networking** | Local / remote / dynamic port forwarding with traffic counters | +| **Desktops** | RDP (IronRDP WASM), VNC (noVNC, SSH-tunnelled), BMC/IPMI web UI (sandboxed webview + auto-login) | +| **Inventory** | Hosts, folders, tags, search, quick-connect; keychain (generate/import, Windows Hello/TPM); snippets with `{{placeholders}}` | +| **Import** | `~/.ssh/config`, PuTTY, MobaXterm | +| **Security** | App lock / vault (password or OS keystore); encrypted backup export/restore | +| **Cloud** | CloudBlast OAuth (PKCE, system browser); VPS server sync; encrypted cross-device snapshot | +| **Ops** | Host TCP monitoring + notifications; activity log; auto-update from GitHub Releases (winget on Windows) | +| **AI** | Claude Code / Codex / OpenCode; tools: hosts, sessions, terminal read/write, files, connect/disconnect — approval modes | +| **i18n** | In-app: en, zh, vi, pt, ru (README also offers Español — see gap below) | + +### 5.2 Architecture (summary) + +``` +Renderer (React + xterm / IronRDP / noVNC) + ↕ IPC + MessagePort (session bytes) +Main (Electron): store, vault, transport (ssh|telnet|serial), + sftp, tunnels, rdp, vnc, bmc, account, AI, monitor +``` + +- **Security defaults:** `contextIsolation`, no `nodeIntegration`, `sandbox`; secrets resolved in main; vault lock gates IPC. +- **Persistence:** `sessions.json`, `vault.json`, window state, cloud snapshot under Electron `userData`; UI prefs in `localStorage`. + +### 5.3 Platforms & distribution + +| Channel | Notes | +|---------|--------| +| GitHub Releases | NSIS + portable (Win), DMG/zip (macOS), AppImage (Linux) | +| winget | `CloudBlast.CloudTerm` — see `docs/winget.md` | +| Signing | **Not code-signed today** — SmartScreen / enterprise friction | + +--- + +## 6. Requirements + +### 6.1 Functional (must remain true) + +| ID | Requirement | +|----|-------------| +| F1 | User can open SSH / Telnet / serial sessions from a host record without leaving the app | +| F2 | Credentials and private keys never leave the main process (RDP CredSSP path is the documented exception) | +| F3 | Vault lock blocks sensitive IPC until unlocked | +| F4 | SFTP and tunnels can reuse an active SSH connection | +| F5 | Cloud snapshot is encrypted client-side before upload | +| F6 | AI cannot read vault secrets; destructive/session-changing tools require approval unless user opts into a looser mode | +| F7 | Auto-update can be disabled (`CLOUDBLAST_UPDATE_DISABLED`) for air-gapped use | + +### 6.2 Non-functional + +| ID | Requirement | Current gap | +|----|-------------|-------------| +| N1 | PR builds run automated tests | Release-only CI; no PR test workflow | +| N2 | Installers code-signed on Windows (and ideally macOS) | Unsigned | +| N3 | Core paths (SSH connect, SFTP, lock/unlock) covered by automated smoke | Unit tests only; no E2E | +| N4 | Cold start acceptable for SSH-only users | RDP/VNC/WASM always in bundle weight | +| N5 | CJK IME composition usable in terminal | Known TODO in `xterm.css` | +| N6 | Marketing locales match in-app locales | Spanish README without in-app `es` | + +--- + +## 7. Success metrics (suggested) + +| Metric | Target (directional) | +|--------|----------------------| +| Crash-free sessions / week | Track via optional anonymous telemetry later; start with GitHub issue rate | +| Time-to-first-connect (fresh install → SSH) | < 2 minutes with import or CloudBlast sync | +| Update adoption (7-day) | Majority on latest within a week of release (post-signing) | +| Test gate | `npm test` green on every PR before merge | +| AI incident rate | Near-zero reports of unexpected `run_command` without approval on default settings | + +--- + +## 8. Roadmap — bugs, enhancements, optimizations + +Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · **P2** polish & scale. + +### 8.1 Bug fixes & reliability (P0–P1) + +| Priority | Item | Rationale | Notes | +|----------|------|-----------|-------| +| **P0** | Add CI workflow that runs `npm test` on PRs | Regressions can ship with release-only CI | Mirror local `npm test` script | +| **P0** | Harden AI approval defaults + first-run warning | “Never ask” + `run_command` is high risk | Prefer ask-before-run; document modes clearly | +| **P0** | Audit RDP password lifetime in renderer/WASM | Documented secret exception | Zero after auth; no logs; teardown tests | +| **P0** | Fix or quarantine IME composition positioning | Blocks CJK users; zh locale is shipped | `src/renderer/xterm.css` TODO | +| **P1** | Session restore reconnect policy | Tabs restore without live sockets | Explicit “reconnect all” or per-tab option | +| **P1** | Update download UX | `autoDownload = true` may surprise corporate users | Optional “ask before download” | +| **P1** | Align Spanish: add `es` locale **or** remove es README until ready | Docs/product mismatch | | + +### 8.2 Enhancements (P1–P2) + +| Priority | Item | Rationale | +|----------|------|-----------| +| **P1** | Code-sign Windows (and ideally macOS) builds | SmartScreen, winget, enterprise adoption | +| **P1** | Split oversized modules (`ipc.js`, `store.js`, `App.jsx`, `TerminalView.jsx`) by domain | Safer reviews; fewer regressions | +| **P1** | E2E smoke: mock SSH → SFTP → lock/unlock | Catch IPC/UI wiring unit tests miss | +| **P1** | SSH/SFTP integration tests (container or mock) | Core path under-tested vs edge protocols | +| **P1** | True 1024² app icon (replace upscaled soft cloud) | Documented in `resources/README.md` | +| **P1** | Rename npm package / clarify branding (`cloudblast-ssh` → `cloudterm`) | Contributor & packaging clarity | +| **P2** | Accessibility pass on dialogs + terminal chrome | Enterprise procurement | +| **P2** | Portable winget package parity | Mentioned as future in `docs/winget.md` | +| **P2** | Document env vars & `userData` layout under `docs/` | Support / IT / contributors | +| **P2** | Gradual TypeScript on `store` / `vault` / `ai/tools` | Catch IPC/secret-field shape bugs | + +### 8.3 Optimizations (P2) + +| Priority | Item | Rationale | +|----------|------|-----------| +| **P2** | Lazy-load RDP / VNC / noVNC chunks | Faster cold start for SSH-only users | +| **P2** | Cap / paginate activity & AI event retention more aggressively | Long-lived processes; large event buffers | +| **P2** | Parallelize `npm test` (today sequential `&&`) | Faster local + CI feedback | +| **P2** | Profile WebGL terminals under multi-pane load | Cap is 8 panes; validate GPU cost | + +### 8.4 Explicitly out of scope for this PRD cycle + +- Vault password recovery without user-held secret +- Server-side decryption of cloud snapshots +- AI tools that return private keys or vault DEK material + +--- + +## 9. Security & threat notes (product constraints) + +| Topic | Stance | +|-------|--------| +| Vault | Protects against disk theft; forgotten password = data unrecoverable by design | +| Cloud snapshot | Protects DB theft in transit/at rest; **not** a compromised CloudBlast app server acting as a malicious client | +| AI | Local CLIs under user accounts; approval modes are the control plane | +| BMC webview | Sandboxed but still untrusted vendor JS on LAN — treat as higher risk surface | +| RDP | Password must reach WASM for CredSSP — minimize lifetime and logging | +| OAuth | System browser + PKCE; client id `cloudblast-desktop` | + +--- + +## 10. Milestones (suggested) + +| Milestone | Scope | Exit criteria | +|-----------|--------|---------------| +| **M1 — Trust & gates** | PR CI for tests; AI default hardening; RDP password audit; IME fix plan | Tests on every PR; safer AI defaults; documented RDP secret handling | +| **M2 — Distribution** | Code signing; icon quality; optional update consent | Signed Win (+ mac if feasible); reduced SmartScreen friction | +| **M3 — Quality depth** | Module splits; E2E smoke; SSH/SFTP integration tests | Reviewable IPC surfaces; smoke green in CI | +| **M4 — Polish** | Locale alignment; lazy-load remoting; docs for env/`userData`; a11y pass | Marketing ↔ app parity; measurable startup win | + +--- + +## 11. Open questions + +1. Should cloud sync remain forever-free for non-CloudBlast users, or become tiered later? +2. Is code signing budgeted for Windows only first, or Win+mac together? +3. Should “never ask” AI mode remain available, or be removed / hidden behind an advanced unlock? +4. Telemetry: opt-in crash/usage metrics, or stay fully silent? +5. Rename `appId` / package name in a major version, or keep `com.cloudblast.ssh` for upgrade continuity? + +--- + +## 12. References + +| Doc | Path / URL | +|-----|------------| +| User README | `README.md` (+ zh-CN, es, ru) | +| Winget publish | `docs/winget.md` | +| Resources / icon notes | `resources/README.md` | +| License | `LICENSE` | +| Product site | https://cloudblast.io | + +--- + +*This PRD describes the product as of v1.3.0 and a prioritized improvement backlog. Update milestones when releases ship.* From 21ee134abb8edbae83f22f78daf49266dc3c9a57 Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Wed, 12 Aug 2026 16:46:42 +0800 Subject: [PATCH 2/6] Ship M1 trust gates: PR CI, safer AI approvals, RDP secret lifetime, IME plan. --- .github/workflows/ci.yml | 29 +++++ docs/assistant-approvals.md | 29 +++++ docs/ime-composition.md | 58 ++++++++++ docs/rdp-credentials.md | 41 +++++++ src/main/activity.js | 5 +- src/main/ai/settings.js | 7 ++ src/renderer/components/RdpView.jsx | 60 +++++----- .../components/assistant/ApprovalMenu.jsx | 105 ++++++++++++------ .../components/assistant/AssistantPanel.jsx | 31 +++++- .../components/settings/AssistantSection.jsx | 26 ++++- src/renderer/i18n/locales/en.js | 11 +- src/renderer/i18n/locales/pt.js | 11 +- src/renderer/i18n/locales/ru.js | 10 +- src/renderer/i18n/locales/vi.js | 11 +- src/renderer/i18n/locales/zh.js | 9 +- src/renderer/xterm.css | 3 +- test/assistant-approval.test.js | 19 +++- test/rdp.test.js | 46 ++++++++ 18 files changed, 423 insertions(+), 88 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 docs/assistant-approvals.md create mode 100644 docs/ime-composition.md create mode 100644 docs/rdp-credentials.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..925825f --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,29 @@ +# Run the same unit suite developers run locally on every PR and on main. +# Packaging stays in release.yml; these tests are plain Node and do not need +# Electron or a matrix of OS runners. + +name: CI + +on: + pull_request: + push: + branches: + - main + +permissions: + contents: read + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 + cache: npm + + - run: npm ci + + - run: npm test diff --git a/docs/assistant-approvals.md b/docs/assistant-approvals.md new file mode 100644 index 0000000..ce3340a --- /dev/null +++ b/docs/assistant-approvals.md @@ -0,0 +1,29 @@ +# Assistant approval modes + +The local AI agent can read live terminals and run tools against saved hosts. +Approval modes decide when those tools stop for a person. + +| Mode | Value | Behaviour | +|------|-------|-----------| +| Ask every time | `always` | Every tool waits, including reads | +| Ask before changes | `writes` | **Default.** Reads run freely; mutating tools ask | +| Never ask | `never` | Nothing asks (except the block list) | + +## Defaults and hardening + +- New installs ship with `writes` and `allowLocalTools: false`. +- The first time the assistant panel opens, a short warning explains the modes + and that tools act on live sessions. Dismissing it sets + `acknowledgedApprovalWarning` in `assistant.json`. +- Switching to **Never ask** from the composer chip or Settings requires an + explicit confirm dialog. The block list (`blockedCommands`, seeded with + `rm -rf`) still refuses matching commands under every mode, including never. + +## Where it lives + +- Persistence: `userData/assistant.json` via `src/main/ai/settings.js` +- Policy: `src/main/ai/tools.js` (`isAutoApproved`, `blockedReason`) +- UI: composer `ApprovalMenu`, Settings → Assistant + +Settings for the assistant stay machine-local on purpose and do not sync with +cloud snapshots. diff --git a/docs/ime-composition.md b/docs/ime-composition.md new file mode 100644 index 0000000..ead9a63 --- /dev/null +++ b/docs/ime-composition.md @@ -0,0 +1,58 @@ +# IME composition positioning (CJK) + +CloudTerm ships Chinese (`zh`) and other locales, but CJK Input Method Editor +(IME) candidate windows can appear mis-positioned in the terminal. This is a +known limitation tracked as GitHub +[#7](https://github.com/BradPerbs/cloudterm/issues/7). + +## Symptom + +While composing CJK (or other IME) text in an SSH/Telnet/serial pane, the +composition / candidate UI may not sit on the caret. English and dead-key Latin +input are unaffected. + +## Current code + +- Terminal: `src/renderer/components/TerminalView.jsx` (`@xterm/xterm` ^5.4) +- Styles: vendored `src/renderer/xterm.css` (upstream still carries + `/* TODO: Composition position got messed up somewhere */` on + `.xterm .composition-view`) +- No app-level `compositionstart` / IME hooks — positioning is entirely xterm’s + `CompositionHelper` + CSS + +Likely aggravators to test (not yet proven): + +- Pane mount uses `p-3` + `overflow-hidden` on the open target +- WebGL addon vs DOM renderer +- Non-default `lineHeight` / `letterSpacing` +- Hardcoded black/white `.composition-view` vs themed terminal + +## Repro matrix (for a later fix) + +| Dimension | Values | +|-----------|--------| +| OS | Windows, macOS | +| IME | zh / ja / ko system IMEs | +| Renderer | WebGL on, WebGL off | +| Typography | default vs custom lineHeight / letterSpacing | + +Compare against a stock xterm.js demo on the same Electron version to separate +CloudTerm layout from upstream bugs. + +## Ranked options + +1. **CSS / layout-only** — ensure composition view is not clipped; theme + `.composition-view`; reduce padding/`overflow` interaction on the mount node. +2. **Bump `@xterm/xterm`** — evaluate 6.x for upstream IME fixes vs breakage in + addons / WebGL. +3. **App composition sync** — listen for composition events and reposition + (last resort; fights the library). +4. **Quarantine (this milestone)** — document the limitation, keep #7 open, + cross-link the CSS TODO. Full N5 fix is out of M1 exit criteria. + +## Quarantine stance (M1) + +Until a verified fix lands: treat CJK IME in the terminal as best-effort. +Prefer composing in an external editor for critical CJK input if positioning +blocks work. Do not close #7 until zh QA passes on Windows and macOS with WebGL +on and off. diff --git a/docs/rdp-credentials.md b/docs/rdp-credentials.md new file mode 100644 index 0000000..d2c4cef --- /dev/null +++ b/docs/rdp-credentials.md @@ -0,0 +1,41 @@ +# RDP credentials (CredSSP exception) + +Most secrets in CloudTerm never leave the main process. RDP is the documented +exception: Network Level Authentication (CredSSP) runs inside IronRDP’s +WebAssembly client in the renderer, so the decrypted password must reach that +client once when a session opens. + +## Lifetime + +1. **Vault / store** — `rdpPassword` is encrypted at rest like other host + secrets (`src/main/store.js`). +2. **Open** — `rdp.open` decrypts via `resolveDesktop`, returns + `{ username, domain, password, … }` once over IPC, and does **not** keep the + password on the live session object or in `snapshot()`. +3. **Renderer** — `RdpView` loads IronRDP **before** calling `open`, so the + secret does not sit on the IPC result through WASM compile. It passes the + password into `SessionBuilder`, then clears `result.password` in a `finally` + that covers every post-open exit (missing canvas, cancel, connect error). +4. **Reconnect** — always re-calls `open`; nothing retains the password for + reuse. + +## Logging and redaction + +- Host list IPC redacts `rdpPassword` (exposes `hasRdpPassword` only). +- Activity change diffs treat `rdpPassword` and `bmcPassword` as secrets + (`secret: true`, empty `from`/`to`). +- Error paths describe rejected credentials without echoing the value. + +## Residual risk + +JavaScript strings are immutable: clearing a property drops the reference; the +engine may retain the buffer until GC. IronRDP’s WASM heap is opaque to the +app — there is no upstream API to zeroize credentials after NLA. Treat the +renderer process as a higher-trust surface for the duration of CredSSP, then +minimize lifetime as above. + +## Contrast with VNC / BMC + +VNC authenticates in the main process; the viewer never receives the password. +BMC uses a sandboxed webview with its own auto-login path — also not this +CredSSP handoff. diff --git a/src/main/activity.js b/src/main/activity.js index 620a4f7..87daa9e 100644 --- a/src/main/activity.js +++ b/src/main/activity.js @@ -280,7 +280,9 @@ function clear() { * Change detection * ------------------------------------------------------------------ */ -const SECRET_FIELDS = new Set(['password', 'privateKey', 'passphrase', 'vncPassword']); +const SECRET_FIELDS = new Set([ + 'password', 'privateKey', 'passphrase', 'vncPassword', 'rdpPassword', 'bmcPassword', +]); /** * Bookkeeping the app writes on its own. `lastConnectedAt` alone is written on @@ -290,6 +292,7 @@ const SECRET_FIELDS = new Set(['password', 'privateKey', 'passphrase', 'vncPassw const NOISE_FIELDS = new Set([ 'id', 'lastConnectedAt', 'os', 'distro', 'hasPassword', 'hasPrivateKey', 'hasPassphrase', 'hasVncPassword', + 'hasRdpPassword', 'hasBmcPassword', ]); /** How many of a list of words are worth naming before it becomes a paragraph. */ diff --git a/src/main/ai/settings.js b/src/main/ai/settings.js index 45954d2..c6a4587 100644 --- a/src/main/ai/settings.js +++ b/src/main/ai/settings.js @@ -95,6 +95,10 @@ const DEFAULTS = { // of things nobody here asked for is filler, and the useful ones are the // ones a particular person asks their particular fleet over and over. quickPrompts: [], + // Whether the first-run warning about approval modes has been dismissed. + // False until the person has seen that tools act on live sessions and that + // "never ask" runs mutating tools without prompts. + acknowledgedApprovalWarning: false, }; const stateFile = () => path.join(app.getPath('userData'), 'assistant.json'); @@ -148,6 +152,9 @@ function sanitize(raw) { .filter(Boolean) .slice(0, 12); } + if ('acknowledgedApprovalWarning' in raw) { + next.acknowledgedApprovalWarning = Boolean(raw.acknowledgedApprovalWarning); + } } return next; } diff --git a/src/renderer/components/RdpView.jsx b/src/renderer/components/RdpView.jsx index 10cddc7..ab6f3e6 100644 --- a/src/renderer/components/RdpView.jsx +++ b/src/renderer/components/RdpView.jsx @@ -260,22 +260,10 @@ function RdpView({ paneId, host, isActive, isFocused, isLive, toolbarHost = null // about what the last one was told this machine had copied. warnedRef.current = false; lastSentRef.current = null; - setStatus('opening'); - const result = await open(host?.id); - if (!current()) return; - - if (!result?.success) { - setStatus('error'); - setError(result?.message || 'Could not open the desktop'); - return; - } - - // From here the password is in hand. Everything between this line and - // the builder call is deliberately short and deliberately synchronous - // where it can be. + // Load WASM before asking main for the password, so the secret does not + // sit on the IPC result through a multi-megabyte compile. setStatus('loading'); - let ironrdp; try { ironrdp = await loadIronRdp(); @@ -283,27 +271,42 @@ function RdpView({ paneId, host, isActive, isFocused, isLive, toolbarHost = null if (!current()) return; setStatus('error'); setError(`The RDP module failed to load: ${loadError.message}`); - close(host?.id); return; } if (!current()) return; ironrdpRef.current = ironrdp; - const canvas = canvasRef.current; - const frame = frameRef.current; - if (!canvas || !frame) return; - - setScaling(SCALING[result.scaling] ? result.scaling : 'fit'); - setStatus('connecting'); + setStatus('opening'); + const result = await open(host?.id); + if (!current()) { + if (result) result.password = ''; + return; + } - const ratio = window.devicePixelRatio || 1; - const scaleFactor = scaleFactorFor(ratio); - const width = clampWidth((frame.clientWidth || MIN_WIDTH) * ratio); - const height = clampHeight((frame.clientHeight || MIN_HEIGHT) * ratio); + if (!result?.success) { + setStatus('error'); + setError(result?.message || 'Could not open the desktop'); + return; + } + // From here the password is in hand. Everything between this line and + // the builder call is deliberately short; the wipe in `finally` covers + // every exit, including cancel, missing canvas, and connect errors. let live; try { + const canvas = canvasRef.current; + const frame = frameRef.current; + if (!canvas || !frame) return; + + setScaling(SCALING[result.scaling] ? result.scaling : 'fit'); + setStatus('connecting'); + + const ratio = window.devicePixelRatio || 1; + const scaleFactor = scaleFactorFor(ratio); + const width = clampWidth((frame.clientWidth || MIN_WIDTH) * ratio); + const height = clampHeight((frame.clientHeight || MIN_HEIGHT) * ratio); + const builder = new ironrdp.SessionBuilder(); builder.username(result.username || ''); builder.password(result.password || ''); @@ -409,7 +412,8 @@ function RdpView({ paneId, host, isActive, isFocused, isLive, toolbarHost = null return; } finally { // The builder has copied what it needs. Nothing here should keep a - // reference to the credential after this point. + // reference to the credential after this point — including early + // exits above that never reached connect. result.password = ''; } @@ -428,8 +432,8 @@ function RdpView({ paneId, host, isActive, isFocused, isLive, toolbarHost = null setSize(agreedSize(live)); setStatus('connected'); - detachInputRef.current = attachInput(canvas, () => sessionRef.current, ironrdp); - if (isActive && isFocused) canvas.focus(); + detachInputRef.current = attachInput(canvasRef.current, () => sessionRef.current, ironrdp); + if (isActive && isFocused) canvasRef.current?.focus(); // `run` resolves when the session ends, however it ends. Anything other // than a shutdown we asked for is worth showing, because the pane diff --git a/src/renderer/components/assistant/ApprovalMenu.jsx b/src/renderer/components/assistant/ApprovalMenu.jsx index 88900f6..eb5de84 100644 --- a/src/renderer/components/assistant/ApprovalMenu.jsx +++ b/src/renderer/components/assistant/ApprovalMenu.jsx @@ -1,5 +1,7 @@ +import { useState } from 'react'; import { ArrowDown01Icon, FlashIcon, SecurityCheckIcon, Shield01Icon } from 'hugeicons-react'; import PanelMenu from './PanelMenu'; +import ConfirmDialog from '../ui/ConfirmDialog'; /** * What the assistant will do before asking, as a chip in the composer. @@ -14,6 +16,10 @@ import PanelMenu from './PanelMenu'; * Icon only. It is a standing state rather than an action, and the composer row * has one label on it already; two competing bits of small text is what makes * that row look like a toolbar. + * + * Switching to "never ask" is gated behind a confirm. One click from a chip is + * otherwise how someone turns off every approval without reading what that + * means for deletes and restarts on live hosts. */ const APPROVALS = [ @@ -31,13 +37,14 @@ const APPROVALS = [ }, { value: 'never', - label: 'Yolo Mode', + label: 'Never ask', hint: 'Nothing stops, deletes included', icon: , }, ]; export default function ApprovalMenu({ settings, onChange }) { + const [confirmNever, setConfirmNever] = useState(false); const current = APPROVALS.find(option => option.value === settings.approval) || APPROVALS[1]; // The one state worth colouring. Somebody who has turned approvals off and @@ -45,43 +52,67 @@ export default function ApprovalMenu({ settings, onChange }) { // and the chip is the only place in the panel that can tell them. const loud = current.value === 'never'; + const pick = (value) => { + if (value === 'never' && settings.approval !== 'never') { + setConfirmNever(true); + return; + } + onChange({ approval: value }); + }; + return ( - onChange({ approval: value }), - options: APPROVALS, - }, - ]} - trigger={({ open, toggle }) => ( - + <> + ( + + )} + /> + + {confirmNever && ( + { + setConfirmNever(false); + onChange({ approval: 'never' }); + }} + onCancel={() => setConfirmNever(false)} + /> )} - /> + ); } diff --git a/src/renderer/components/assistant/AssistantPanel.jsx b/src/renderer/components/assistant/AssistantPanel.jsx index 7a4889d..4264ee4 100644 --- a/src/renderer/components/assistant/AssistantPanel.jsx +++ b/src/renderer/components/assistant/AssistantPanel.jsx @@ -4,6 +4,7 @@ import { PlusSignIcon, ArrowUp01Icon, StopCircleIcon, + Alert02Icon, } from 'hugeicons-react'; import Tooltip from '../ui/Tooltip'; import AgentMark from './AgentMark'; @@ -17,6 +18,7 @@ import ScopeMenu from './ScopeMenu'; import ModelMenu from './ModelMenu'; import ApprovalMenu from './ApprovalMenu'; import HistoryMenu from './HistoryMenu'; +import Dialog, { DialogButton } from '../ui/Dialog'; import { GLOBAL, describe, @@ -478,7 +480,8 @@ function AssistantConversation({

It reads this terminal, runs commands on their own channel, and can work - across every host you have saved. + across every host you have saved. Approvals default to ask-before-changes; + switch modes from the composer chip or Settings.

@@ -687,12 +690,32 @@ function AssistantConversation({ + + {settings && !settings.acknowledgedApprovalWarning && ( + changeSettings({ acknowledgedApprovalWarning: true })} + icon={ + + + + } + footer={ + changeSettings({ acknowledgedApprovalWarning: true })} + > + Got it + + } + /> + )} ); } - -/** - * The column the assistant lives in, open or shut. * * One element that changes width, not two swapped for each other: a swap has * nothing to animate between, and the rail is the same column with everything diff --git a/src/renderer/components/settings/AssistantSection.jsx b/src/renderer/components/settings/AssistantSection.jsx index 513b1c8..bba1274 100644 --- a/src/renderer/components/settings/AssistantSection.jsx +++ b/src/renderer/components/settings/AssistantSection.jsx @@ -7,6 +7,7 @@ import Toggle from './ui/Toggle'; import Slider from './ui/Slider'; import SegmentedControl from '../ui/SegmentedControl'; import Button from '../ui/Button'; +import ConfirmDialog from '../ui/ConfirmDialog'; import { useT } from '../../i18n'; /** @@ -70,6 +71,7 @@ export default function AssistantSection() { const [commands, setCommands] = useState(''); const [blocked, setBlocked] = useState(''); const [prompts, setPrompts] = useState(''); + const [confirmNever, setConfirmNever] = useState(false); useEffect(() => { let cancelled = false; @@ -99,6 +101,14 @@ export default function AssistantSection() { return next; }, []); + const pickApproval = useCallback((value) => { + if (value === 'never' && settings?.approval !== 'never') { + setConfirmNever(true); + return; + } + update({ approval: value }); + }, [settings?.approval, update]); + const saveKey = useCallback(async () => { const result = await window.api.ai.setApiKey(keyValue); if (result?.success) { @@ -217,7 +227,7 @@ export default function AssistantSection() { label: t(`settings.assistant.approval.${value}`), }))} value={settings.approval} - onChange={(value) => update({ approval: value })} + onChange={pickApproval} /> @@ -444,6 +454,20 @@ export default function AssistantSection() { + + {confirmNever && ( + { + setConfirmNever(false); + update({ approval: 'never' }); + }} + onCancel={() => setConfirmNever(false)} + /> + )} ); } diff --git a/src/renderer/i18n/locales/en.js b/src/renderer/i18n/locales/en.js index 4139086..56ab1ee 100644 --- a/src/renderer/i18n/locales/en.js +++ b/src/renderer/i18n/locales/en.js @@ -492,13 +492,18 @@ export default { 'settings.assistant.approval': 'Ask before running', 'settings.assistant.approval.always': 'Every action', 'settings.assistant.approval.writes': 'Changes only', - 'settings.assistant.approval.never': 'Never', + 'settings.assistant.approval.never': 'Never ask', 'settings.assistant.approval.always.note': 'Every tool call waits for you, including reading a ' + 'file or the terminal. Thorough, but a long investigation becomes a lot of clicking.', 'settings.assistant.approval.writes.note': 'Reading runs freely. Anything that changes a system ' - + 'stops and shows you the exact command and the host it would run on.', + + 'stops and shows you the exact command and the host it would run on. This is the default.', 'settings.assistant.approval.never.note': 'Nothing stops for approval, including commands that ' - + 'delete data or restart services. Only sensible for hosts you can afford to break.', + + 'delete data or restart services. Only sensible for hosts you can afford to break. ' + + 'Turning this on asks for confirmation first.', + 'settings.assistant.approval.neverConfirmTitle': 'Turn off approvals?', + 'settings.assistant.approval.neverConfirmMessage': 'Never ask runs every tool without stopping — ' + + 'including commands that delete data or restart services on live hosts. Blocked commands ' + + 'are still refused.', 'settings.assistant.localTools': 'Allow tools on this computer', 'settings.assistant.localToolsDesc': 'Lets the assistant read and write local files and run ' + 'local commands. Off by default: the panel is for managing servers, and your own machine ' diff --git a/src/renderer/i18n/locales/pt.js b/src/renderer/i18n/locales/pt.js index df7bcd7..95f0614 100644 --- a/src/renderer/i18n/locales/pt.js +++ b/src/renderer/i18n/locales/pt.js @@ -452,15 +452,20 @@ export default { 'settings.assistant.approval': 'Perguntar antes de correr', 'settings.assistant.approval.always': 'Todas as ações', 'settings.assistant.approval.writes': 'Só alterações', - 'settings.assistant.approval.never': 'Nunca', + 'settings.assistant.approval.never': 'Nunca perguntar', 'settings.assistant.approval.always.note': 'Todas as chamadas a ferramentas esperam por si, ' + 'incluindo ler um ficheiro ou o terminal. É minucioso, mas uma investigação longa ' + 'transforma-se em muitos cliques.', 'settings.assistant.approval.writes.note': 'A leitura corre à vontade. Tudo o que altere um ' - + 'sistema para e mostra-lhe o comando exato e o anfitrião onde correria.', + + 'sistema para e mostra-lhe o comando exato e o anfitrião onde correria. Esta é a ' + + 'predefinição.', 'settings.assistant.approval.never.note': 'Nada para à espera de aprovação, incluindo comandos ' + 'que apagam dados ou reiniciam serviços. Só faz sentido para anfitriões que pode dar-se ' - + 'ao luxo de estragar.', + + 'ao luxo de estragar. Ativar isto pede confirmação primeiro.', + 'settings.assistant.approval.neverConfirmTitle': 'Desligar aprovações?', + 'settings.assistant.approval.neverConfirmMessage': 'Nunca perguntar corre todas as ferramentas ' + + 'sem parar — incluindo comandos que apagam dados ou reiniciam serviços em anfitriões ' + + 'ativos. Os comandos bloqueados continuam a ser recusados.', 'settings.assistant.localTools': 'Permitir ferramentas neste computador', 'settings.assistant.localToolsDesc': 'Deixa o assistente ler e escrever ficheiros locais e ' + 'correr comandos locais. Desligado por predefinição: o painel serve para gerir ' diff --git a/src/renderer/i18n/locales/ru.js b/src/renderer/i18n/locales/ru.js index cb90469..9fae417 100644 --- a/src/renderer/i18n/locales/ru.js +++ b/src/renderer/i18n/locales/ru.js @@ -467,15 +467,19 @@ export default { 'settings.assistant.approval': 'Спрашивать перед запуском', 'settings.assistant.approval.always': 'Любое действие', 'settings.assistant.approval.writes': 'Только изменения', - 'settings.assistant.approval.never': 'Никогда', + 'settings.assistant.approval.never': 'Не спрашивать', 'settings.assistant.approval.always.note': 'Каждый вызов инструмента ждёт вас, включая чтение ' + 'файла или терминала. Надёжно, но долгий разбор превращается в множество нажатий.', 'settings.assistant.approval.writes.note': 'Чтение выполняется свободно. Всё, что меняет ' + 'систему, останавливается и показывает вам точную команду и хост, на котором она ' - + 'выполнилась бы.', + + 'выполнилась бы. Это значение по умолчанию.', 'settings.assistant.approval.never.note': 'Ничто не останавливается ради подтверждения, включая ' + 'команды, которые удаляют данные или перезапускают службы. Разумно только для хостов, ' - + 'которые вы можете позволить себе сломать.', + + 'которые вы можете позволить себе сломать. Включение сначала запрашивает подтверждение.', + 'settings.assistant.approval.neverConfirmTitle': 'Отключить подтверждения?', + 'settings.assistant.approval.neverConfirmMessage': 'Режим «не спрашивать» запускает каждый ' + + 'инструмент без остановки — включая команды, которые удаляют данные или перезапускают ' + + 'службы на рабочих хостах. Заблокированные команды по-прежнему отклоняются.', 'settings.assistant.localTools': 'Разрешить инструменты на этом компьютере', 'settings.assistant.localToolsDesc': 'Позволяет ассистенту читать и записывать локальные файлы ' + 'и выполнять локальные команды. По умолчанию выключено: панель нужна для управления ' diff --git a/src/renderer/i18n/locales/vi.js b/src/renderer/i18n/locales/vi.js index 6b5a9b8..2cbd5f5 100644 --- a/src/renderer/i18n/locales/vi.js +++ b/src/renderer/i18n/locales/vi.js @@ -438,15 +438,20 @@ export default { 'settings.assistant.approval': 'Hỏi trước khi chạy', 'settings.assistant.approval.always': 'Mọi thao tác', 'settings.assistant.approval.writes': 'Chỉ khi thay đổi', - 'settings.assistant.approval.never': 'Không bao giờ', + 'settings.assistant.approval.never': 'Không bao giờ hỏi', 'settings.assistant.approval.always.note': 'Mọi lần gọi công cụ đều chờ bạn, kể cả khi chỉ đọc ' + 'một tệp hay đọc terminal. Rất chắc chắn, nhưng một cuộc điều tra dài sẽ thành rất nhiều ' + 'lần bấm.', 'settings.assistant.approval.writes.note': 'Thao tác đọc chạy tự do. Bất cứ thứ gì thay đổi hệ ' - + 'thống đều dừng lại và cho bạn xem đúng lệnh đó cùng máy chủ nó sẽ chạy trên.', + + 'thống đều dừng lại và cho bạn xem đúng lệnh đó cùng máy chủ nó sẽ chạy trên. Đây là ' + + 'mặc định.', 'settings.assistant.approval.never.note': 'Không có gì dừng lại chờ phê duyệt, kể cả các lệnh ' + 'xoá dữ liệu hay khởi động lại dịch vụ. Chỉ nên dùng với máy chủ mà bạn chấp nhận được ' - + 'việc nó hỏng.', + + 'việc nó hỏng. Bật chế độ này sẽ hỏi xác nhận trước.', + 'settings.assistant.approval.neverConfirmTitle': 'Tắt phê duyệt?', + 'settings.assistant.approval.neverConfirmMessage': 'Không bao giờ hỏi sẽ chạy mọi công cụ mà ' + + 'không dừng — kể cả lệnh xoá dữ liệu hoặc khởi động lại dịch vụ trên máy đang dùng. Lệnh ' + + 'trong danh sách chặn vẫn bị từ chối.', 'settings.assistant.localTools': 'Cho phép dùng công cụ trên máy này', 'settings.assistant.localToolsDesc': 'Cho phép trợ lý đọc ghi tệp cục bộ và chạy lệnh cục bộ. ' + 'Mặc định tắt: bảng này dùng để quản lý máy chủ, còn máy của chính bạn là một phạm vi ' diff --git a/src/renderer/i18n/locales/zh.js b/src/renderer/i18n/locales/zh.js index 07013a5..8d3410e 100644 --- a/src/renderer/i18n/locales/zh.js +++ b/src/renderer/i18n/locales/zh.js @@ -414,13 +414,16 @@ export default { 'settings.assistant.approval': '运行前询问', 'settings.assistant.approval.always': '每一步操作', 'settings.assistant.approval.writes': '仅更改类操作', - 'settings.assistant.approval.never': '从不', + 'settings.assistant.approval.never': '从不询问', 'settings.assistant.approval.always.note': '每一次工具调用都会等你确认,包括读取文件或终端。' + '这样很稳妥,但一次长时间的排查会变成大量点击。', 'settings.assistant.approval.writes.note': '读取操作自由进行。任何会改变系统的操作都会停下来,' - + '并向你展示确切的命令以及将要运行它的主机。', + + '并向你展示确切的命令以及将要运行它的主机。这是默认设置。', 'settings.assistant.approval.never.note': '任何操作都不会停下来等待批准,包括删除数据或重启服务的命令。' - + '只有在你能承受把主机弄坏时才适合使用。', + + '只有在你能承受把主机弄坏时才适合使用。开启前会先要求确认。', + 'settings.assistant.approval.neverConfirmTitle': '关闭批准确认?', + 'settings.assistant.approval.neverConfirmMessage': '“从不询问”会让每个工具直接运行而不停下来——' + + '包括在活动主机上删除数据或重启服务的命令。拦截列表中的命令仍会被拒绝。', 'settings.assistant.localTools': '允许在这台电脑上使用工具', 'settings.assistant.localToolsDesc': '允许助手读写本地文件并运行本地命令。默认关闭:' + '这个面板是用来管理服务器的,而你自己的机器是比这大得多的一片范围。', diff --git a/src/renderer/xterm.css b/src/renderer/xterm.css index e97b643..8cb753b 100644 --- a/src/renderer/xterm.css +++ b/src/renderer/xterm.css @@ -77,7 +77,8 @@ } .xterm .composition-view { - /* TODO: Composition position got messed up somewhere */ + /* TODO: Composition position got messed up somewhere (upstream xterm). + CloudTerm quarantine / fix plan: docs/ime-composition.md, GitHub #7. */ background: #000; color: #FFF; display: none; diff --git a/test/assistant-approval.test.js b/test/assistant-approval.test.js index 16c1b87..a7193e3 100644 --- a/test/assistant-approval.test.js +++ b/test/assistant-approval.test.js @@ -74,6 +74,19 @@ console.log('\nassistant approvals'); check('the default is to ask before anything that changes a system', () => { assert.strictEqual(defaults.approval, 'writes'); assert.strictEqual(defaults.allowLocalTools, false, 'local tools are off out of the box'); + assert.strictEqual( + defaults.acknowledgedApprovalWarning, false, + 'the first-run approval warning is pending until dismissed' + ); +}); + +check('the first-run warning flag sanitizes and persists', () => { + assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, false); + settingsModule.set({ acknowledgedApprovalWarning: true }); + assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, true); + settingsModule.set({ acknowledgedApprovalWarning: 0 }); + assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, false); + // Leave it false for later checks that read the live settings object. }); check('every tool declares whether it only reads', () => { @@ -266,7 +279,11 @@ check('an unknown tool is never auto approved', () => { }); check('"never ask" really does mean never', () => { - assert.strictEqual(tools.isAutoApproved('run_command', { command: 'rm -rf /' }, open), true); + // Blocked commands stay refused under never; see the check above. What + // "never" means is everything else runs without a prompt — including + // mutating tools and tools the catalog does not know yet. + assert.strictEqual(tools.isAutoApproved('run_command', { command: 'rm /tmp/one-file' }, open), true); + assert.strictEqual(tools.isAutoApproved('send_input', { text: 'systemctl restart nginx' }, open), true); assert.strictEqual(tools.isAutoApproved('some_new_tool', {}, open), true); }); diff --git a/test/rdp.test.js b/test/rdp.test.js index c6e5a11..785450c 100644 --- a/test/rdp.test.js +++ b/test/rdp.test.js @@ -493,6 +493,52 @@ async function run() { assert.ok(text.includes('rdpPassword'), 'the change itself should still be logged'); }); + check('activity diffs mark rdpPassword as a secret with empty from/to', () => { + const activity = require(path.join(ROOT, 'activity.js')); + const changes = activity.diff( + { rdpPassword: 'cipher-a', name: 'win' }, + { rdpPassword: 'cipher-b', name: 'win' } + ); + const secret = changes.find(entry => entry.field === 'rdpPassword'); + assert.ok(secret, 'rdpPassword must appear as a change'); + assert.strictEqual(secret.secret, true); + assert.strictEqual(secret.from, ''); + assert.strictEqual(secret.to, ''); + }); + + check('RdpView wipes the password on every post-open path', () => { + const view = fs.readFileSync( + path.join(__dirname, '..', 'src', 'renderer', 'components', 'RdpView.jsx'), 'utf8' + ); + // WASM must load before open so the secret is not held through compile. + const loadAt = view.indexOf('await loadIronRdp()'); + const openAt = view.indexOf('await open(host?.id)'); + assert.ok(loadAt >= 0 && openAt >= 0 && loadAt < openAt, 'IronRDP loads before open'); + // A finally that clears result.password covers success, missing canvas, + // cancel, and connect errors alike. + assert.ok( + /finally\s*\{[\s\S]*?result\.password\s*=\s*''/.test(view), + 'post-open finally must clear result.password' + ); + // Cancel after a successful open still drops the credential. + assert.ok( + view.includes("if (result) result.password = ''"), + 'cancelled open must wipe the password' + ); + }); + + check('session snapshots never carry the password', () => { + const source = fs.readFileSync(path.join(ROOT, 'rdp.js'), 'utf8'); + // snapshot() builds the live view the renderer polls; a password field + // here would re-expose CredSSP material after the one-shot open. + const snapshot = source.match(/function snapshot\([\s\S]*?\n\}/); + assert.ok(snapshot, 'snapshot helper must exist'); + assert.strictEqual( + /\bpassword\b/.test(snapshot[0]), false, + 'snapshot must not mention password' + ); + }); + console.log(`\n${passed} checks passed`); } From 6a3009a9296425de1cefba00cee702f5a25138e1 Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Wed, 12 Aug 2026 16:52:47 +0800 Subject: [PATCH 3/6] Fix session log path race so retention tests can utime immediately. Open transcript files synchronously before returning their path from start(). --- src/main/session-log.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/session-log.js b/src/main/session-log.js index 79672a2..344da1e 100644 --- a/src/main/session-log.js +++ b/src/main/session-log.js @@ -291,9 +291,13 @@ function start(tabId, { hostName = '', address = '', hostId = '', protocol = '', try { fs.mkdirSync(directory, { recursive: true }); + // Open synchronously so a returned path already exists on disk. The + // stream open is otherwise async, and callers (and the retention + // tests) treat the path as ready the moment start() returns. // `a`, not `w`: two sessions to the same host within the same second // would otherwise have the second silently truncate the first. - const stream = fs.createWriteStream(filePath, { flags: 'a', encoding: 'utf8' }); + const fd = fs.openSync(filePath, 'a'); + const stream = fs.createWriteStream(null, { fd, encoding: 'utf8' }); stream.on('error', (error) => { console.error(`Session log write failed for ${tabId}:`, error.message); // Drop the entry rather than keep handing bytes to a dead stream. From f625ef5f22a5353bb8b96fb89df98167ba2a5254 Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Wed, 12 Aug 2026 17:00:59 +0800 Subject: [PATCH 4/6] Document M1 trust gates in README, CHANGELOG, and PRD. Add release history and a docs index so contributors can find approval, RDP credential, and IME notes alongside npm test and CI guidance. --- CHANGELOG.md | 68 +++++++++++++++++++++++++++++++++++++++++++++++++ PRD.md | 19 ++++++++------ README.es.md | 13 ++++++++-- README.md | 36 ++++++++++++++++++++++++-- README.ru.md | 14 ++++++++-- README.zh-CN.md | 10 +++++++- docs/README.md | 14 ++++++++++ 7 files changed, 159 insertions(+), 15 deletions(-) create mode 100644 CHANGELOG.md create mode 100644 docs/README.md diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..2528efb --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,68 @@ +# Changelog + +All notable changes to CloudTerm are documented here. The format follows +[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions align +with [GitHub Releases](https://github.com/BradPerbs/cloudterm/releases). + +## [Unreleased] + +### Added + +- GitHub Actions CI (`.github/workflows/ci.yml`) runs `npm test` on every pull + request and on pushes to `main`. +- First-run assistant warning explaining that tools act on live sessions and + what each approval mode means. +- Confirm dialog before switching the assistant to **Never ask**. +- Contributor docs: [assistant approval modes](docs/assistant-approvals.md), + [RDP credential lifetime](docs/rdp-credentials.md), and + [IME composition plan](docs/ime-composition.md). + +### Changed + +- Default assistant approval mode is **Ask before changes** (`writes`): reads + run freely; mutating tools stop for approval. +- New installs ship with `allowLocalTools: false`. +- RDP passwords are cleared from the renderer as soon as CredSSP completes; + reconnect always re-fetches credentials from the vault. See + [RDP credentials](docs/rdp-credentials.md). +- Activity log redacts `rdpPassword` and `bmcPassword` in change diffs. + +### Fixed + +- Session log transcript files are opened synchronously in `start()` so + retention tests (and immediate `utime`) do not race the path. + +## [1.3.0] - 2026-08-10 + +### Added + +- Host monitoring with desktop notifications when a saved host stops answering. +- Launch CloudTerm at sign-in (Windows). +- Check for updates on every launch. +- BMC/IPMI web UI opens in a tab with auto-login. +- App icon on every platform (`build/icon.png`). + +### Changed + +- Host cards fit every protocol kind on one row. +- Distro icons halved to match on-screen draw size. +- Renderer dependencies kept out of the packaged app bundle. +- Monitor events kept out of the notifications bell. + +## [1.2.1] - 2026-08-06 + +### Added + +- Quick connect: type an address in the host picker and connect immediately. +- Windows install and update via `winget install CloudBlast.CloudTerm`. See + [docs/winget.md](docs/winget.md). + +## [1.2.0] + +Earlier releases are listed on +[GitHub Releases](https://github.com/BradPerbs/cloudterm/releases). + +[Unreleased]: https://github.com/BradPerbs/cloudterm/compare/v1.3.0...HEAD +[1.3.0]: https://github.com/BradPerbs/cloudterm/releases/tag/v1.3.0 +[1.2.1]: https://github.com/BradPerbs/cloudterm/releases/tag/v1.2.1 +[1.2.0]: https://github.com/BradPerbs/cloudterm/releases/tag/v1.2.0 diff --git a/PRD.md b/PRD.md index 365390a..bed35da 100644 --- a/PRD.md +++ b/PRD.md @@ -120,11 +120,11 @@ Main (Electron): store, vault, transport (ssh|telnet|serial), | ID | Requirement | Current gap | |----|-------------|-------------| -| N1 | PR builds run automated tests | Release-only CI; no PR test workflow | +| N1 | PR builds run automated tests | **Shipped (M1):** `.github/workflows/ci.yml` on PRs and `main` | | N2 | Installers code-signed on Windows (and ideally macOS) | Unsigned | | N3 | Core paths (SSH connect, SFTP, lock/unlock) covered by automated smoke | Unit tests only; no E2E | | N4 | Cold start acceptable for SSH-only users | RDP/VNC/WASM always in bundle weight | -| N5 | CJK IME composition usable in terminal | Known TODO in `xterm.css` | +| N5 | CJK IME composition usable in terminal | Known limitation — documented in `docs/ime-composition.md`; fix tracked in #7 | | N6 | Marketing locales match in-app locales | Spanish README without in-app `es` | --- @@ -149,10 +149,10 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * | Priority | Item | Rationale | Notes | |----------|------|-----------|-------| -| **P0** | Add CI workflow that runs `npm test` on PRs | Regressions can ship with release-only CI | Mirror local `npm test` script | -| **P0** | Harden AI approval defaults + first-run warning | “Never ask” + `run_command` is high risk | Prefer ask-before-run; document modes clearly | -| **P0** | Audit RDP password lifetime in renderer/WASM | Documented secret exception | Zero after auth; no logs; teardown tests | -| **P0** | Fix or quarantine IME composition positioning | Blocks CJK users; zh locale is shipped | `src/renderer/xterm.css` TODO | +| **P0** | Add CI workflow that runs `npm test` on PRs | Regressions can ship with release-only CI | **Done** — `.github/workflows/ci.yml` | +| **P0** | Harden AI approval defaults + first-run warning | “Never ask” + `run_command` is high risk | **Done** — default `writes`, warning, confirm for `never`; see `docs/assistant-approvals.md` | +| **P0** | Audit RDP password lifetime in renderer/WASM | Documented secret exception | **Done** — see `docs/rdp-credentials.md`; tests in `test/rdp.test.js` | +| **P0** | Fix or quarantine IME composition positioning | Blocks CJK users; zh locale is shipped | **Quarantined (M1)** — `docs/ime-composition.md`; #7 open | | **P1** | Session restore reconnect policy | Tabs restore without live sockets | Explicit “reconnect all” or per-tab option | | **P1** | Update download UX | `autoDownload = true` may surprise corporate users | Optional “ask before download” | | **P1** | Align Spanish: add `es` locale **or** remove es README until ready | Docs/product mismatch | | @@ -206,7 +206,7 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * | Milestone | Scope | Exit criteria | |-----------|--------|---------------| -| **M1 — Trust & gates** | PR CI for tests; AI default hardening; RDP password audit; IME fix plan | Tests on every PR; safer AI defaults; documented RDP secret handling | +| **M1 — Trust & gates** | PR CI for tests; AI default hardening; RDP password audit; IME fix plan | **Shipped** — CI on PRs; safer AI defaults; RDP docs + tests; IME quarantined in `docs/ime-composition.md` | | **M2 — Distribution** | Code signing; icon quality; optional update consent | Signed Win (+ mac if feasible); reduced SmartScreen friction | | **M3 — Quality depth** | Module splits; E2E smoke; SSH/SFTP integration tests | Reviewable IPC surfaces; smoke green in CI | | **M4 — Polish** | Locale alignment; lazy-load remoting; docs for env/`userData`; a11y pass | Marketing ↔ app parity; measurable startup win | @@ -228,6 +228,8 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * | Doc | Path / URL | |-----|------------| | User README | `README.md` (+ zh-CN, es, ru) | +| Changelog | `CHANGELOG.md` | +| Contributor docs | `docs/README.md` | | Winget publish | `docs/winget.md` | | Resources / icon notes | `resources/README.md` | | License | `LICENSE` | @@ -235,4 +237,5 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * --- -*This PRD describes the product as of v1.3.0 and a prioritized improvement backlog. Update milestones when releases ship.* +*This PRD describes the product as of v1.3.0 plus the M1 trust-and-gates +work on `feat/m1-trust-gates`. Update milestones when releases ship.* diff --git a/README.es.md b/README.es.md index c6cb83c..666534f 100644 --- a/README.es.md +++ b/README.es.md @@ -121,8 +121,9 @@ para leerlo y modificarlo. - **Trabaja en el terminal que ves**: los comandos se escriben en el panel y la salida se queda en tu historial, o se ejecutan en un canal oculto si lo prefieres -- **Pregunta antes de cambiar nada**, con una lista de comandos que solo miran - y un modo más estricto o más suelto cuando lo quieras +- **Pregunta antes de cambiar nada** — tres modos de aprobación (cada + herramienta / solo cambios / nunca), aviso al primer uso y confirmación antes + de **Nunca preguntar**. Ver [docs/assistant-approvals.md](docs/assistant-approvals.md) (inglés) - **Apuntado donde tú digas**: la sesión que tienes delante, una que fijes, o todos los hosts que tengas guardados - **Herramientas en vez de suposiciones**: conectar un host guardado, leer y @@ -300,6 +301,14 @@ Compila un ejecutable portable en `dist/`: npm run build ``` +Antes de abrir un pull request, ejecuta las pruebas unitarias: + +```bash +npm test +``` + +Más documentación en [CHANGELOG.md](CHANGELOG.md) y [docs/README.md](docs/README.md) (inglés). + ### Atajos | | | | | diff --git a/README.md b/README.md index 94fd26a..ea02115 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,8 @@ everyone, and the whole source is here to read and change. - [Features](#features) - [Screenshots](#screenshots) - [Getting started](#getting-started) +- [Documentation](#documentation) +- [Contributing](#contributing) - [Community](#community) - [Contributors](#contributors) - [Tech stack](#tech-stack) @@ -135,8 +137,9 @@ Whichever you choose, the agent: one it answers, without you pasting anything - **Works in the terminal you can see**: commands are typed into the pane and the output stays in your scrollback, or run on a hidden channel if you prefer -- **Asks before it changes anything**, with an allow list for the commands that - only look, and a stricter or looser mode when you want one +- **Asks before it changes anything** — three approval modes (every tool / + changes only / never), a first-run warning, and a confirm step before + **Never ask**. See [assistant approvals](docs/assistant-approvals.md). - **Pointed where you like**: the session in front, one you pin, or every host you have saved - **Tools instead of guesses**: connect a saved host, read and write files, @@ -316,6 +319,14 @@ Build a portable executable into `dist/`: npm run build ``` +Run the unit test suite before opening a pull request: + +```bash +npm test +``` + +The same suite runs on every pull request via GitHub Actions (`.github/workflows/ci.yml`). + ### Shortcuts | | | | | @@ -326,6 +337,27 @@ npm run build | `Ctrl+Shift+C` / `V` | Copy and paste | `Ctrl+Shift+W` | Close pane | | `Ctrl+Shift+A` | AI agent | `Alt+Arrows` | Move between panes | + +## Documentation + +| Doc | What it covers | +| --- | --- | +| [CHANGELOG.md](CHANGELOG.md) | Release history | +| [PRD.md](PRD.md) | Product scope and roadmap | +| [docs/assistant-approvals.md](docs/assistant-approvals.md) | AI approval modes and defaults | +| [docs/rdp-credentials.md](docs/rdp-credentials.md) | RDP password handling (CredSSP exception) | +| [docs/ime-composition.md](docs/ime-composition.md) | CJK IME positioning (known limitation) | +| [docs/winget.md](docs/winget.md) | Publishing to Windows Package Manager | + +Full index: [docs/README.md](docs/README.md). + + +## Contributing + +1. Fork and clone the repo, then `npm install` and `npm run dev`. +2. Run `npm test` — CI runs the same suite on every pull request. +3. Open a pull request against `main`. + ## Community diff --git a/README.ru.md b/README.ru.md index bf19726..15d5f16 100644 --- a/README.ru.md +++ b/README.ru.md @@ -121,8 +121,10 @@ CloudTerm собирает все способы подключения к се - **Работает в терминале, который вы видите**: команды печатаются в панели, а вывод остаётся в вашем буфере. Или выполняются в скрытом канале, если так удобнее -- **Спрашивает, прежде чем что-то менять**, со списком команд, которые только - смотрят, и режимом строже или свободнее, когда он нужен +- **Спрашивает, прежде чем что-то менять** — три режима одобрения (каждый + инструмент / только изменения / никогда), предупреждение при первом запуске и + подтверждение перед **Никогда не спрашивать**. См. + [docs/assistant-approvals.md](docs/assistant-approvals.md) (англ.) - **Направлен туда, куда скажете**: сеанс перед вами, закреплённый сеанс или все сохранённые хосты - **Инструменты вместо догадок**: подключиться к сохранённому хосту, читать и @@ -297,6 +299,14 @@ npm run dev npm run build ``` +Перед pull request запустите модульные тесты: + +```bash +npm test +``` + +Документация: [CHANGELOG.md](CHANGELOG.md) и [docs/README.md](docs/README.md) (англ.). + ### Горячие клавиши | | | | | diff --git a/README.zh-CN.md b/README.zh-CN.md index fbb0ee0..3dd0a27 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -105,7 +105,7 @@ Windows 主机,或者任意服务商上的服务器。CloudTerm 由 VPS 云主

- **使用本机已有的 Claude Code、Codex 或 OpenCode**,沿用你自己的账号和配置 -- **读取当前会话并操作远程服务器**,执行更改前会先征求你的同意 +- **读取当前会话并操作远程服务器**,执行更改前会先征求你的同意;提供三种审批模式,详见 [docs/assistant-approvals.md](docs/assistant-approvals.md)(英文) - **每个对话可单独选择模型和推理强度**,并在运行时显示用量 ### 终端 @@ -268,6 +268,14 @@ npm run dev npm run build ``` +提交 pull request 前请运行单元测试: + +```bash +npm test +``` + +更多文档见 [CHANGELOG.md](CHANGELOG.md) 与 [docs/README.md](docs/README.md)(英文)。 + ### 快捷键 | | | | | diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..b865864 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,14 @@ +# CloudTerm documentation + +Operator and contributor notes live here. User-facing marketing and setup +instructions are in the root [README](../README.md). + +| Doc | Audience | Summary | +|-----|----------|---------| +| [Assistant approval modes](assistant-approvals.md) | Users, contributors | How `always` / `writes` / `never` approval works, defaults, and block list | +| [RDP credentials](rdp-credentials.md) | Contributors, security review | CredSSP exception: password lifetime, logging, residual risk | +| [IME composition](ime-composition.md) | Contributors | CJK IME candidate positioning — known limitation and fix plan | +| [Winget publishing](winget.md) | Maintainers | First-time and automated Windows package updates | + +Product scope, roadmap, and milestones are in [PRD.md](../PRD.md). Release +history is in [CHANGELOG.md](../CHANGELOG.md). From bd6b5feaac800b78e3145152271aa62db2123a28 Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Wed, 12 Aug 2026 17:18:23 +0800 Subject: [PATCH 5/6] Harden dependencies and document pending code signing. Run npm audit fix and block high-severity production advisories in CI. Wire optional Authenticode signing in release.yml (secrets not yet configured). Add docs/security.md and update PRD, README, and winget docs to state that the Windows certificate is pending procurement. Fix a broken comment in AssistantPanel.jsx that blocked production builds. --- .github/workflows/ci.yml | 4 + .github/workflows/release.yml | 12 +- CHANGELOG.md | 12 +- PRD.md | 10 +- README.md | 7 + docs/README.md | 1 + docs/security.md | 105 ++ docs/winget.md | 10 +- package-lock.json | 933 ++++++++++-------- package.json | 5 +- .../components/assistant/AssistantPanel.jsx | 3 +- 11 files changed, 696 insertions(+), 406 deletions(-) create mode 100644 docs/security.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 925825f..16420ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -26,4 +26,8 @@ jobs: - run: npm ci + # Production deps only: dev-only tools (Vite/esbuild) may still report + # advisories that do not affect the packaged app. + - run: npm audit --omit=dev --audit-level=high + - run: npm test diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bf7f9de..6947f53 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -128,9 +128,15 @@ jobs: - name: Package run: npx electron-builder ${{ matrix.args }} --publish never env: - # There is no signing certificate on a CI runner. Without this, - # electron-builder hunts for one and fails the macOS build. - CSC_IDENTITY_AUTO_DISCOVERY: false + # When WIN_CSC_LINK / CSC_LINK secrets are set, electron-builder signs + # the Windows installer and portable exe (or the macOS bundle). Without + # them, builds stay unsigned — see docs/security.md. + WIN_CSC_LINK: ${{ secrets.WIN_CSC_LINK }} + WIN_CSC_KEY_PASSWORD: ${{ secrets.WIN_CSC_KEY_PASSWORD }} + CSC_LINK: ${{ secrets.CSC_LINK }} + CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }} + # Only hunt the macOS keychain when a Developer ID cert is supplied. + CSC_IDENTITY_AUTO_DISCOVERY: ${{ secrets.CSC_LINK != '' && 'true' || 'false' }} # The agent SDK carries the Claude CLI as an optional platform package, # a 253MB binary that the app deliberately does not ship: the provider diff --git a/CHANGELOG.md b/CHANGELOG.md index 2528efb..03a5f89 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,12 +4,20 @@ All notable changes to CloudTerm are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and versions align with [GitHub Releases](https://github.com/BradPerbs/cloudterm/releases). -## [Unreleased] +## [1.3.1] - 2026-08-12 ### Added - GitHub Actions CI (`.github/workflows/ci.yml`) runs `npm test` on every pull request and on pushes to `main`. +- CI runs `npm audit --omit=dev` to block high-severity vulnerabilities in + production dependencies. +- Optional Authenticode / macOS code signing in release builds when + `WIN_CSC_LINK` / `CSC_LINK` repository secrets are configured (certificate + **pending procurement** — builds stay unsigned until secrets are added). See + [docs/security.md](docs/security.md). +- [Security documentation](docs/security.md): VirusTotal/SmartScreen, signing, + dependency audit, vulnerability reporting. - First-run assistant warning explaining that tools act on live sessions and what each approval mode means. - Confirm dialog before switching the assistant to **Never ask**. @@ -19,6 +27,8 @@ with [GitHub Releases](https://github.com/BradPerbs/cloudterm/releases). ### Changed +- npm production and dev dependencies updated (`npm audit fix`); esbuild + overridden to a patched release for the dev toolchain. - Default assistant approval mode is **Ask before changes** (`writes`): reads run freely; mutating tools stop for approval. - New installs ship with `allowLocalTools: false`. diff --git a/PRD.md b/PRD.md index bed35da..44182cb 100644 --- a/PRD.md +++ b/PRD.md @@ -98,7 +98,7 @@ Main (Electron): store, vault, transport (ssh|telnet|serial), |---------|--------| | GitHub Releases | NSIS + portable (Win), DMG/zip (macOS), AppImage (Linux) | | winget | `CloudBlast.CloudTerm` — see `docs/winget.md` | -| Signing | **Not code-signed today** — SmartScreen / enterprise friction | +| Signing | **Unsigned** — Windows Authenticode cert **pending procurement**; CI ready when secrets are set — see [docs/security.md](docs/security.md) | --- @@ -121,7 +121,7 @@ Main (Electron): store, vault, transport (ssh|telnet|serial), | ID | Requirement | Current gap | |----|-------------|-------------| | N1 | PR builds run automated tests | **Shipped (M1):** `.github/workflows/ci.yml` on PRs and `main` | -| N2 | Installers code-signed on Windows (and ideally macOS) | Unsigned | +| N2 | Installers code-signed on Windows (and ideally macOS) | **Pending** — cert procurement in progress; release CI wired for signing | | N3 | Core paths (SSH connect, SFTP, lock/unlock) covered by automated smoke | Unit tests only; no E2E | | N4 | Cold start acceptable for SSH-only users | RDP/VNC/WASM always in bundle weight | | N5 | CJK IME composition usable in terminal | Known limitation — documented in `docs/ime-composition.md`; fix tracked in #7 | @@ -161,7 +161,7 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * | Priority | Item | Rationale | |----------|------|-----------| -| **P1** | Code-sign Windows (and ideally macOS) builds | SmartScreen, winget, enterprise adoption | +| **P1** | Code-sign Windows (and ideally macOS) builds | SmartScreen, winget, enterprise adoption | **In progress** — Windows cert pending; `release.yml` ready | | **P1** | Split oversized modules (`ipc.js`, `store.js`, `App.jsx`, `TerminalView.jsx`) by domain | Safer reviews; fewer regressions | | **P1** | E2E smoke: mock SSH → SFTP → lock/unlock | Catch IPC/UI wiring unit tests miss | | **P1** | SSH/SFTP integration tests (container or mock) | Core path under-tested vs edge protocols | @@ -207,7 +207,7 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * | Milestone | Scope | Exit criteria | |-----------|--------|---------------| | **M1 — Trust & gates** | PR CI for tests; AI default hardening; RDP password audit; IME fix plan | **Shipped** — CI on PRs; safer AI defaults; RDP docs + tests; IME quarantined in `docs/ime-composition.md` | -| **M2 — Distribution** | Code signing; icon quality; optional update consent | Signed Win (+ mac if feasible); reduced SmartScreen friction | +| **M2 — Distribution** | Code signing; icon quality; optional update consent | Signed Win (+ mac if feasible); reduced SmartScreen friction — **cert pending** | | **M3 — Quality depth** | Module splits; E2E smoke; SSH/SFTP integration tests | Reviewable IPC surfaces; smoke green in CI | | **M4 — Polish** | Locale alignment; lazy-load remoting; docs for env/`userData`; a11y pass | Marketing ↔ app parity; measurable startup win | @@ -216,7 +216,7 @@ Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · * ## 11. Open questions 1. Should cloud sync remain forever-free for non-CloudBlast users, or become tiered later? -2. Is code signing budgeted for Windows only first, or Win+mac together? +2. Code signing: **Windows certificate procurement in progress**; macOS Developer ID deferred until after first signed Windows release. 3. Should “never ask” AI mode remain available, or be removed / hidden behind an advanced unlock? 4. Telemetry: opt-in crash/usage metrics, or stay fully silent? 5. Rename `appId` / package name in a major version, or keep `com.cloudblast.ssh` for upgrade continuity? diff --git a/README.md b/README.md index ea02115..b9dde5f 100644 --- a/README.md +++ b/README.md @@ -300,6 +300,12 @@ winget install CloudBlast.CloudTerm Or browse [all GitHub releases](https://github.com/BradPerbs/cloudterm/releases). +> **Windows SmartScreen / VirusTotal:** Installers are **unsigned** while a +> code-signing certificate is being procured. This can trigger SmartScreen or +> heuristic scanner warnings (often 1–3 vendors on VirusTotal). See +> [docs/security.md](docs/security.md) for details and what to expect after +> signing is enabled. + ### Build from source ```bash @@ -348,6 +354,7 @@ The same suite runs on every pull request via GitHub Actions (`.github/workflows | [docs/rdp-credentials.md](docs/rdp-credentials.md) | RDP password handling (CredSSP exception) | | [docs/ime-composition.md](docs/ime-composition.md) | CJK IME positioning (known limitation) | | [docs/winget.md](docs/winget.md) | Publishing to Windows Package Manager | +| [docs/security.md](docs/security.md) | Signing status, SmartScreen/VirusTotal, npm audit | Full index: [docs/README.md](docs/README.md). diff --git a/docs/README.md b/docs/README.md index b865864..363f6a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,6 +9,7 @@ instructions are in the root [README](../README.md). | [RDP credentials](rdp-credentials.md) | Contributors, security review | CredSSP exception: password lifetime, logging, residual risk | | [IME composition](ime-composition.md) | Contributors | CJK IME candidate positioning — known limitation and fix plan | | [Winget publishing](winget.md) | Maintainers | First-time and automated Windows package updates | +| [Security](security.md) | Users, maintainers, IT | Signing status (cert pending), SmartScreen/VirusTotal, npm audit, reporting | Product scope, roadmap, and milestones are in [PRD.md](../PRD.md). Release history is in [CHANGELOG.md](../CHANGELOG.md). diff --git a/docs/security.md b/docs/security.md new file mode 100644 index 0000000..5bbc3e2 --- /dev/null +++ b/docs/security.md @@ -0,0 +1,105 @@ +# Security + +CloudTerm holds SSH keys, passwords, and live server sessions. This page +covers distribution trust (SmartScreen, VirusTotal), dependency hygiene, and +where to report issues. + +## Code signing status + +| Item | Status | +|------|--------| +| Windows Authenticode certificate | **Pending procurement** | +| macOS Developer ID certificate | **Not started** (Windows first) | +| CI signing wiring (`release.yml`) | **Ready** — signs automatically once secrets are set | +| Current release builds | **Unsigned** | + +A Windows code-signing certificate is being procured. Until `WIN_CSC_LINK` and +`WIN_CSC_KEY_PASSWORD` are added as repository secrets, tagged releases continue +to ship unsigned installers and portable executables. See [Enabling code signing in CI](#enabling-code-signing-in-ci) below for the steps once the certificate arrives. + +## VirusTotal and SmartScreen (unsigned builds) + +Release builds are **not code-signed today** (certificate pending). On Windows that means: + +- **SmartScreen** may warn on first download ("Windows protected your PC"). +- **VirusTotal** and some enterprise scanners may flag the portable exe + (`CloudTerm-x64.exe`) or the NSIS installer with heuristic detections + (often 1–3 vendors out of 60+). This is common for unsigned Electron apps + packaged with NSIS; it does not mean the published GitHub release binary + contains malware. + +What actually reduces those warnings: + +1. **Authenticode signing** — sign the app binary and the installer with an + Extended Validation or standard code-signing certificate. +2. **Reputation** — signed builds gain SmartScreen reputation over time as + more users install them. +3. **False-positive review** — submit the file hash to vendors that flagged it + for whitelisting (use the full VirusTotal report to see which engines). + +### Enabling code signing in CI + +**Prerequisite:** a code-signing certificate must be procured and converted to +the secrets below. Windows certificate procurement is **in progress**; macOS +Developer ID is planned after Windows. + +When repository secrets are configured, [release.yml](../.github/workflows/release.yml) +passes them to electron-builder: + +| Secret | Platform | Purpose | +|--------|----------|---------| +| `WIN_CSC_LINK` | Windows | Base64-encoded `.pfx` or path to cert | +| `WIN_CSC_KEY_PASSWORD` | Windows | PFX password | +| `CSC_LINK` | macOS | Developer ID Application cert (`.p12`) | +| `CSC_KEY_PASSWORD` | macOS | P12 password | + +Obtain certificates from a public CA (e.g. DigiCert, Sectigo). Store the PFX +as base64: + +```powershell +[Convert]::ToBase64String([IO.File]::ReadAllBytes('cloudterm.pfx')) | Set-Clipboard +``` + +Paste the result into `WIN_CSC_LINK`. After the next tagged release, installers +and the portable exe are signed automatically. + +Unsigned builds continue to work when these secrets are absent. + +## npm dependencies + +CI runs `npm audit --omit=dev --audit-level=high` on every pull request and +push to `main`. That checks **production** dependencies only — the libraries +shipped inside the packaged app. + +Dev-only tools (Vite, esbuild, electron-builder) may still show advisories +locally when you run `npm audit` without `--omit=dev`. Those affect the +development server, not end users. + +To refresh production deps after an advisory: + +```bash +npm audit fix +npm audit --omit=dev +npm test +``` + +## Application security model (summary) + +| Area | Behaviour | +|------|-----------| +| Renderer | Sandboxed; no Node integration; context isolation | +| Secrets | Passwords and keys stay in the main process; vault lock blocks sensitive IPC | +| RDP | Password cleared from renderer after CredSSP — see [rdp-credentials.md](rdp-credentials.md) | +| Assistant | Default approval mode asks before mutating tools — see [assistant-approvals.md](assistant-approvals.md) | +| External links | Only `http:` and `https:` via `open-external` IPC | +| Updates | Auto-install only when platform signature checks apply; unsigned mac builds use notify-only mode | +| BMC webview | Guest pages sandboxed; only `http(s)` in `bmc-*` partitions | + +## Reporting a vulnerability + +Open a [GitHub Security Advisory](https://github.com/BradPerbs/cloudterm/security/advisories/new) +or email the maintainers listed in the repository. Include steps to reproduce +and affected version. + +Do **not** file public issues for undisclosed credential theft or RCE paths +before a fix is available. diff --git a/docs/winget.md b/docs/winget.md index 5b8618a..8c71f8b 100644 --- a/docs/winget.md +++ b/docs/winget.md @@ -97,10 +97,12 @@ may come back with questions on the pull request itself. Two things that come up: -- The installer is not code signed. That is allowed, and plenty of packages in - winget are the same, but SmartScreen and occasionally the pipeline's own - scanner have opinions about unsigned installers and the review can stall on - it. Reply on the pull request if it does. +- The installer is not code signed (**Windows certificate pending procurement**). + CI is wired to sign automatically once `WIN_CSC_LINK` is configured — see + [security.md](security.md). That is allowed in winget, and plenty of packages + are the same, but SmartScreen and occasionally the pipeline's own scanner + have opinions about unsigned installers and the review can stall on it. Reply + on the pull request if it does. - The publisher name has to be one you can claim. `CloudBlast` is fine here. It is also permanent in practice: the identifier is the directory name, the thing people type, and what every already-installed copy is matched against, diff --git a/package-lock.json b/package-lock.json index 40b1082..477b9f7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "cloudblast-ssh", - "version": "1.0.0", + "version": "1.3.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "cloudblast-ssh", - "version": "1.0.0", + "version": "1.3.0", "hasInstallScript": true, "license": "SEE LICENSE IN LICENSE", "dependencies": { @@ -43,7 +43,7 @@ "patch-package": "^8.0.1", "postcss": "^8.5.6", "tailwindcss": "^3.4.17", - "vite": "^5.0.12", + "vite": "^5.4.21", "wait-on": "^7.2.0" } }, @@ -201,13 +201,13 @@ } }, "node_modules/@babel/code-frame": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.28.6.tgz", - "integrity": "sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -216,9 +216,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.28.6.tgz", - "integrity": "sha512-2lfu57JtzctfIrcGMz992hyLlByuzgIk58+hhGCxjKZ3rWI82NnVLjXcaTqkI2NvlcvOskZaiZ5kjUALo3Lpxg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -226,21 +226,21 @@ } }, "node_modules/@babel/core": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.28.6.tgz", - "integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -257,14 +257,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.6.tgz", - "integrity": "sha512-lOoVRwADj8hjf7al89tvQ2a1lf53Z+7tiXMgpZJL3maQPDxh0DgLMN62B2MKUOFcoodBHLMbDM6WAbKgNy5Suw==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.8.tgz", + "integrity": "sha512-gZbepsdh3WDtgZKWL+vTPh71LSBrm/Y4/QDZBVCcYfmeTEEuoOYwlSy+G1StfJg+/Zy550u/3TATbm7qDbbMtg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/parser": "^7.29.8", + "@babel/types": "^7.29.8", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -274,14 +274,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -308,9 +308,9 @@ "license": "ISC" }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -318,29 +318,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -360,9 +360,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -370,9 +370,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -380,9 +380,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -390,27 +390,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz", - "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.6.tgz", - "integrity": "sha512-TeR9zWR18BvbfPmGbLampPMW+uW1NZnJlRuuHso8i87QZNq2JRF9i6RgxRqtEq+wQGsS19NNTWr2duhnE49mfQ==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.8.tgz", + "integrity": "sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.28.6" + "@babel/types": "^7.29.8" }, "bin": { "parser": "bin/babel-parser.js" @@ -461,33 +461,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.6.tgz", - "integrity": "sha512-fgWX62k02qtjqdSNTAGxmKYY/7FSL9WAS1o2Hu5+I5m9T0yxZzr4cnrfXQ/MX0rIifthCSs6FKTlzYbJcPtMNg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.8.tgz", + "integrity": "sha512-I5z7H3bf/41ktsNVLtpN0wAa336HkqIHQ5BuPLEhTkt1jVSyZpeNKIzTgEWmlxjdg81R0IgUCcaE+Ok3NvrfZg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/generator": "^7.28.6", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.28.6", - "@babel/template": "^7.28.6", - "@babel/types": "^7.28.6", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.8", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.8", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.8", "debug": "^4.3.1" }, "engines": { @@ -495,14 +495,14 @@ } }, "node_modules/@babel/types": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.6.tgz", - "integrity": "sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==", + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -535,10 +535,11 @@ } }, "node_modules/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -965,9 +966,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", - "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", "cpu": [ "ppc64" ], @@ -978,13 +979,13 @@ "aix" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", - "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", "cpu": [ "arm" ], @@ -995,13 +996,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", - "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", "cpu": [ "arm64" ], @@ -1012,13 +1013,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/android-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", - "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", "cpu": [ "x64" ], @@ -1029,13 +1030,13 @@ "android" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", - "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", "cpu": [ "arm64" ], @@ -1046,13 +1047,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", - "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", "cpu": [ "x64" ], @@ -1063,13 +1064,13 @@ "darwin" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", - "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", "cpu": [ "arm64" ], @@ -1080,13 +1081,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", - "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", "cpu": [ "x64" ], @@ -1097,13 +1098,13 @@ "freebsd" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", - "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", "cpu": [ "arm" ], @@ -1114,13 +1115,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", - "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", "cpu": [ "arm64" ], @@ -1131,13 +1132,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", - "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", "cpu": [ "ia32" ], @@ -1148,13 +1149,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", - "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", "cpu": [ "loong64" ], @@ -1165,13 +1166,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", - "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", "cpu": [ "mips64el" ], @@ -1182,13 +1183,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", - "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", "cpu": [ "ppc64" ], @@ -1199,13 +1200,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", - "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", "cpu": [ "riscv64" ], @@ -1216,13 +1217,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", - "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", "cpu": [ "s390x" ], @@ -1233,13 +1234,13 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/linux-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", - "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", "cpu": [ "x64" ], @@ -1250,13 +1251,30 @@ "linux" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", - "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", "cpu": [ "x64" ], @@ -1267,13 +1285,30 @@ "netbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", - "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", "cpu": [ "x64" ], @@ -1284,13 +1319,30 @@ "openbsd" ], "engines": { - "node": ">=12" + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", - "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", "cpu": [ "x64" ], @@ -1301,13 +1353,13 @@ "sunos" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", - "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", "cpu": [ "arm64" ], @@ -1318,13 +1370,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", - "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", "cpu": [ "ia32" ], @@ -1335,13 +1387,13 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@esbuild/win32-x64": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", - "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", "cpu": [ "x64" ], @@ -1352,7 +1404,7 @@ "win32" ], "engines": { - "node": ">=12" + "node": ">=18" } }, "node_modules/@fontsource/inter": { @@ -1576,6 +1628,26 @@ } } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@noble/hashes": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", @@ -1822,9 +1894,9 @@ "license": "MIT" }, "node_modules/@rollup/rollup-android-arm-eabi": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.56.0.tgz", - "integrity": "sha512-LNKIPA5k8PF1+jAFomGe3qN3bbIgJe/IlpDBwuVjrDKrJhVWywgnJvflMt/zkbVNLFtF1+94SljYQS6e99klnw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", "cpu": [ "arm" ], @@ -1836,9 +1908,9 @@ ] }, "node_modules/@rollup/rollup-android-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.56.0.tgz", - "integrity": "sha512-lfbVUbelYqXlYiU/HApNMJzT1E87UPGvzveGg2h0ktUNlOCxKlWuJ9jtfvs1sKHdwU4fzY7Pl8sAl49/XaEk6Q==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", "cpu": [ "arm64" ], @@ -1850,9 +1922,9 @@ ] }, "node_modules/@rollup/rollup-darwin-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.56.0.tgz", - "integrity": "sha512-EgxD1ocWfhoD6xSOeEEwyE7tDvwTgZc8Bss7wCWe+uc7wO8G34HHCUH+Q6cHqJubxIAnQzAsyUsClt0yFLu06w==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", "cpu": [ "arm64" ], @@ -1864,9 +1936,9 @@ ] }, "node_modules/@rollup/rollup-darwin-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.56.0.tgz", - "integrity": "sha512-1vXe1vcMOssb/hOF8iv52A7feWW2xnu+c8BV4t1F//m9QVLTfNVpEdja5ia762j/UEJe2Z1jAmEqZAK42tVW3g==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", "cpu": [ "x64" ], @@ -1878,9 +1950,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.56.0.tgz", - "integrity": "sha512-bof7fbIlvqsyv/DtaXSck4VYQ9lPtoWNFCB/JY4snlFuJREXfZnm+Ej6yaCHfQvofJDXLDMTVxWscVSuQvVWUQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", "cpu": [ "arm64" ], @@ -1892,9 +1964,9 @@ ] }, "node_modules/@rollup/rollup-freebsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.56.0.tgz", - "integrity": "sha512-KNa6lYHloW+7lTEkYGa37fpvPq+NKG/EHKM8+G/g9WDU7ls4sMqbVRV78J6LdNuVaeeK5WB9/9VAFbKxcbXKYg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", "cpu": [ "x64" ], @@ -1906,13 +1978,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-gnueabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.56.0.tgz", - "integrity": "sha512-E8jKK87uOvLrrLN28jnAAAChNq5LeCd2mGgZF+fGF5D507WlG/Noct3lP/QzQ6MrqJ5BCKNwI9ipADB6jyiq2A==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1920,13 +1995,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm-musleabihf": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.56.0.tgz", - "integrity": "sha512-jQosa5FMYF5Z6prEpTCCmzCXz6eKr/tCBssSmQGEeozA9tkRUty/5Vx06ibaOP9RCrW1Pvb8yp3gvZhHwTDsJw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", "cpu": [ "arm" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1934,13 +2012,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.56.0.tgz", - "integrity": "sha512-uQVoKkrC1KGEV6udrdVahASIsaF8h7iLG0U0W+Xn14ucFwi6uS539PsAr24IEF9/FoDtzMeeJXJIBo5RkbNWvQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1948,13 +2029,16 @@ ] }, "node_modules/@rollup/rollup-linux-arm64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.56.0.tgz", - "integrity": "sha512-vLZ1yJKLxhQLFKTs42RwTwa6zkGln+bnXc8ueFGMYmBTLfNu58sl5/eXyxRa2RarTkJbXl8TKPgfS6V5ijNqEA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1962,13 +2046,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.56.0.tgz", - "integrity": "sha512-FWfHOCub564kSE3xJQLLIC/hbKqHSVxy8vY75/YHHzWvbJL7aYJkdgwD/xGfUlL5UV2SB7otapLrcCj2xnF1dg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1976,13 +2063,16 @@ ] }, "node_modules/@rollup/rollup-linux-loong64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.56.0.tgz", - "integrity": "sha512-z1EkujxIh7nbrKL1lmIpqFTc/sr0u8Uk0zK/qIEFldbt6EDKWFk/pxFq3gYj4Bjn3aa9eEhYRlL3H8ZbPT1xvA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", "cpu": [ "loong64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1990,13 +2080,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.56.0.tgz", - "integrity": "sha512-iNFTluqgdoQC7AIE8Q34R3AuPrJGJirj5wMUErxj22deOcY7XwZRaqYmB6ZKFHoVGqRcRd0mqO+845jAibKCkw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2004,13 +2097,16 @@ ] }, "node_modules/@rollup/rollup-linux-ppc64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.56.0.tgz", - "integrity": "sha512-MtMeFVlD2LIKjp2sE2xM2slq3Zxf9zwVuw0jemsxvh1QOpHSsSzfNOTH9uYW9i1MXFxUSMmLpeVeUzoNOKBaWg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2018,13 +2114,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.56.0.tgz", - "integrity": "sha512-in+v6wiHdzzVhYKXIk5U74dEZHdKN9KH0Q4ANHOTvyXPG41bajYRsy7a8TPKbYPl34hU7PP7hMVHRvv/5aCSew==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2032,13 +2131,16 @@ ] }, "node_modules/@rollup/rollup-linux-riscv64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.56.0.tgz", - "integrity": "sha512-yni2raKHB8m9NQpI9fPVwN754mn6dHQSbDTwxdr9SE0ks38DTjLMMBjrwvB5+mXrX+C0npX0CVeCUcvvvD8CNQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", "cpu": [ "riscv64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2046,13 +2148,16 @@ ] }, "node_modules/@rollup/rollup-linux-s390x-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.56.0.tgz", - "integrity": "sha512-zhLLJx9nQPu7wezbxt2ut+CI4YlXi68ndEve16tPc/iwoylWS9B3FxpLS2PkmfYgDQtosah07Mj9E0khc3Y+vQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2060,13 +2165,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.56.0.tgz", - "integrity": "sha512-MVC6UDp16ZSH7x4rtuJPAEoE1RwS8N4oK9DLHy3FTEdFoUTCFVzMfJl/BVJ330C+hx8FfprA5Wqx4FhZXkj2Kw==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -2074,13 +2182,16 @@ ] }, "node_modules/@rollup/rollup-linux-x64-musl": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.56.0.tgz", - "integrity": "sha512-ZhGH1eA4Qv0lxaV00azCIS1ChedK0V32952Md3FtnxSqZTBTd6tgil4nZT5cU8B+SIw3PFYkvyR4FKo2oyZIHA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -2088,9 +2199,9 @@ ] }, "node_modules/@rollup/rollup-openbsd-x64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.56.0.tgz", - "integrity": "sha512-O16XcmyDeFI9879pEcmtWvD/2nyxR9mF7Gs44lf1vGGx8Vg2DRNx11aVXBEqOQhWb92WN4z7fW/q4+2NYzCbBA==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", "cpu": [ "x64" ], @@ -2102,9 +2213,9 @@ ] }, "node_modules/@rollup/rollup-openharmony-arm64": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.56.0.tgz", - "integrity": "sha512-LhN/Reh+7F3RCgQIRbgw8ZMwUwyqJM+8pXNT6IIJAqm2IdKkzpCh/V9EdgOMBKuebIrzswqy4ATlrDgiOwbRcQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", "cpu": [ "arm64" ], @@ -2116,9 +2227,9 @@ ] }, "node_modules/@rollup/rollup-win32-arm64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.56.0.tgz", - "integrity": "sha512-kbFsOObXp3LBULg1d3JIUQMa9Kv4UitDmpS+k0tinPBz3watcUiV2/LUDMMucA6pZO3WGE27P7DsfaN54l9ing==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", "cpu": [ "arm64" ], @@ -2130,9 +2241,9 @@ ] }, "node_modules/@rollup/rollup-win32-ia32-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.56.0.tgz", - "integrity": "sha512-vSSgny54D6P4vf2izbtFm/TcWYedw7f8eBrOiGGecyHyQB9q4Kqentjaj8hToe+995nob/Wv48pDqL5a62EWtg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", "cpu": [ "ia32" ], @@ -2144,9 +2255,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-gnu": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.56.0.tgz", - "integrity": "sha512-FeCnkPCTHQJFbiGG49KjV5YGW/8b9rrXAM2Mz2kiIoktq2qsJxRD5giEMEOD2lPdgs72upzefaUvS+nc8E3UzQ==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", "cpu": [ "x64" ], @@ -2158,9 +2269,9 @@ ] }, "node_modules/@rollup/rollup-win32-x64-msvc": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.56.0.tgz", - "integrity": "sha512-H8AE9Ur/t0+1VXujj90w0HrSOuv0Nq9r1vSZF2t5km20NTfosQsGGUXDaKdQZzwuLts7IyL1fYT4hM95TI9c4g==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", "cpu": [ "x64" ], @@ -2526,9 +2637,9 @@ } }, "node_modules/@types/estree": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", - "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", "dev": true, "license": "MIT" }, @@ -2694,6 +2805,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -2882,10 +3006,11 @@ } }, "node_modules/app-builder-lib/node_modules/brace-expansion": { - "version": "5.0.8", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz", - "integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==", + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" }, @@ -3151,15 +3276,16 @@ "dev": true }, "node_modules/axios": { - "version": "1.13.3", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.3.tgz", - "integrity": "sha512-ERT8kdX7DZjtUm7IitEyV7InTHAF42iJuMArIiDIV5YtPanJkgw4hw5Dyg9fh0mihdWNn1GKaeIWErfe56UQ1g==", + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.19.0.tgz", + "integrity": "sha512-ht/iuYZXEjFxLH/Hkezgd7m6JKlHHXEUSneaDz8uZe1Gj5QZtCnpyDsckvAiEnT89OEbCLmnte4R4sn7P0EKFw==", "dev": true, "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.6", - "form-data": "^4.0.4", - "proxy-from-env": "^1.1.0" + "follow-redirects": "^1.16.0", + "form-data": "^4.0.6", + "https-proxy-agent": "^5.0.1", + "proxy-from-env": "^2.1.0" } }, "node_modules/balanced-match": { @@ -3272,9 +3398,9 @@ "optional": true }, "node_modules/brace-expansion": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz", - "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", + "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", "dev": true, "license": "MIT", "dependencies": { @@ -3701,15 +3827,15 @@ "license": "MIT" }, "node_modules/concurrently": { - "version": "9.2.1", - "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.1.tgz", - "integrity": "sha512-fsfrO0MxV64Znoy8/l1vVIjjHa29SZyyqPgQBwhiDcaW8wJc2W3XWVOGx4M3oJBnv/zdUZIIp1gDeS98GzP8Ng==", + "version": "9.2.4", + "resolved": "https://registry.npmjs.org/concurrently/-/concurrently-9.2.4.tgz", + "integrity": "sha512-TZ0CEhyzvFjgtAvHTusDMgj7wNdihCh7LLLrzdUOXIhdlnL2JBBGA9eJxR24rtqgmdjh3OA3hrN1rCHj6HM8qA==", "dev": true, "license": "MIT", "dependencies": { "chalk": "4.1.2", "rxjs": "7.8.2", - "shell-quote": "1.8.3", + "shell-quote": "1.9.0", "supports-color": "8.1.1", "tree-kill": "1.2.2", "yargs": "17.7.2" @@ -3995,10 +4121,11 @@ } }, "node_modules/dir-compare/node_modules/brace-expansion": { - "version": "1.1.16", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.16.tgz", - "integrity": "sha512-IDw48K2/2kRkg9LdJxurvq3lV3aBgq0REY89duEqFRthjlPdXHKMj7EnQOXVckxzgisinf3nHfrcE2FufFLXMw==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -4407,9 +4534,9 @@ "optional": true }, "node_modules/esbuild": { - "version": "0.21.5", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", - "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -4417,32 +4544,35 @@ "esbuild": "bin/esbuild" }, "engines": { - "node": ">=12" + "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.21.5", - "@esbuild/android-arm": "0.21.5", - "@esbuild/android-arm64": "0.21.5", - "@esbuild/android-x64": "0.21.5", - "@esbuild/darwin-arm64": "0.21.5", - "@esbuild/darwin-x64": "0.21.5", - "@esbuild/freebsd-arm64": "0.21.5", - "@esbuild/freebsd-x64": "0.21.5", - "@esbuild/linux-arm": "0.21.5", - "@esbuild/linux-arm64": "0.21.5", - "@esbuild/linux-ia32": "0.21.5", - "@esbuild/linux-loong64": "0.21.5", - "@esbuild/linux-mips64el": "0.21.5", - "@esbuild/linux-ppc64": "0.21.5", - "@esbuild/linux-riscv64": "0.21.5", - "@esbuild/linux-s390x": "0.21.5", - "@esbuild/linux-x64": "0.21.5", - "@esbuild/netbsd-x64": "0.21.5", - "@esbuild/openbsd-x64": "0.21.5", - "@esbuild/sunos-x64": "0.21.5", - "@esbuild/win32-arm64": "0.21.5", - "@esbuild/win32-ia32": "0.21.5", - "@esbuild/win32-x64": "0.21.5" + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" } }, "node_modules/escalade": { @@ -4632,9 +4762,9 @@ "peer": true }, "node_modules/fast-uri": { - "version": "3.1.4", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.4.tgz", - "integrity": "sha512-8JnbkQ4juDyvYs4mgFGQqg4yCYtFDtUtmp2QIQq11ZZe5CFQ5wcqm1rqDgAh/QdMySuBnPzMUiJUNZG5N/AiQw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.5.tgz", + "integrity": "sha512-gHwA1O9LDIcKunMKhObS/HimwtehO1nPUECKAu5TpKgaO19fcWEl4bliWe1jWxVFvIXztJjjQ4L8XQ1EU9f7Jw==", "funding": [ { "type": "github", @@ -4644,7 +4774,8 @@ "type": "opencollective", "url": "https://opencollective.com/fastify" } - ] + ], + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.20.1", @@ -4708,9 +4839,9 @@ } }, "node_modules/follow-redirects": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", - "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", + "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", "dev": true, "funding": [ { @@ -4729,17 +4860,17 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "dev": true, "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -4928,9 +5059,9 @@ } }, "node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", - "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "version": "1.1.18", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", + "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", "dev": true, "license": "MIT", "dependencies": { @@ -4939,9 +5070,9 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", - "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "dev": true, "license": "ISC", "dependencies": { @@ -5103,9 +5234,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -5115,9 +5246,10 @@ } }, "node_modules/hono": { - "version": "4.12.32", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.32.tgz", - "integrity": "sha512-XcuyW9qE2kJn07PkecMOBd5Vq/hMy7mmGw+idz1yblbg9N17ijJODrvPkn7/dwL3Kulj8LcRJ69DLOWf91dRUg==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.1.tgz", + "integrity": "sha512-kdJoFVv2xmayw6cY09H7AbMJMt8Jn5jdlEdXsP7AGBdF2DIptVlKlOLKXP41yPip4/a3yQPv9gVcJYI8YY04dw==", + "license": "MIT", "engines": { "node": ">=16.9.0" } @@ -5174,6 +5306,20 @@ "node": ">=10.19.0" } }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "dev": true, + "license": "MIT", + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/hugeicons-react": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/hugeicons-react/-/hugeicons-react-0.4.0.tgz", @@ -5397,9 +5543,9 @@ } }, "node_modules/joi": { - "version": "17.13.3", - "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.3.tgz", - "integrity": "sha512-otDA4ldcIx+ZXsKHWmp0YizCweVRZG96J10b0FevjfuncLO1oX59THoAmHkNubYJ+9gWsYsp5k8v4ib6oDv1fA==", + "version": "17.13.4", + "resolved": "https://registry.npmjs.org/joi/-/joi-17.13.4.tgz", + "integrity": "sha512-1RuuER6kmt8K8I3nIWvPZKi5RQCb568ZPyY4Pwjlua+yo+63ZTmIwxLZH0heBmiKN4uxjvCiarDrjaeH84xicQ==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -5425,9 +5571,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.0.tgz", - "integrity": "sha512-1td788aAnnZ5qs7V2QIRl1owjtYpbKt749Y3xauqQgwIIGF/xXWz1wMTEBx5O3LK3lXLVuqXPdPxj2BoFHaW9Q==", + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.3.1.tgz", + "integrity": "sha512-CY6crGq313MX8GkwvB7tzgp99vjQxY1++5y10/BKN/GUfHqWaOGQMNZkBvqSzsZKWk/ijwHlWzzkLulsGHhjWQ==", "funding": [ { "type": "github", @@ -5438,6 +5584,7 @@ "url": "https://github.com/sponsors/nodeca" } ], + "license": "MIT", "dependencies": { "argparse": "^2.0.1" }, @@ -5598,9 +5745,9 @@ "license": "MIT" }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -5764,9 +5911,9 @@ } }, "node_modules/minimatch": { - "version": "5.1.6", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", - "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", + "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", "dev": true, "license": "ISC", "dependencies": { @@ -5812,9 +5959,9 @@ "optional": true }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", "dev": true, "funding": [ { @@ -6110,9 +6257,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", - "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", + "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", "dev": true, "license": "MIT", "engines": { @@ -6194,9 +6341,9 @@ } }, "node_modules/postcss": { - "version": "8.5.6", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", - "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", "dev": true, "funding": [ { @@ -6214,7 +6361,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.17", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -6439,11 +6586,14 @@ } }, "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=10" + } }, "node_modules/pump": { "version": "3.0.3", @@ -6752,13 +6902,13 @@ } }, "node_modules/rollup": { - "version": "4.56.0", - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.56.0.tgz", - "integrity": "sha512-9FwVqlgUHzbXtDg9RCMgodF3Ua4Na6Gau+Sdt9vyCN4RhHfVKX2DCHy3BjMLTDd47ITDhYAnTwGulWTblJSDLg==", + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", "dev": true, "license": "MIT", "dependencies": { - "@types/estree": "1.0.8" + "@types/estree": "1.0.9" }, "bin": { "rollup": "dist/bin/rollup" @@ -6768,31 +6918,32 @@ "npm": ">=8.0.0" }, "optionalDependencies": { - "@rollup/rollup-android-arm-eabi": "4.56.0", - "@rollup/rollup-android-arm64": "4.56.0", - "@rollup/rollup-darwin-arm64": "4.56.0", - "@rollup/rollup-darwin-x64": "4.56.0", - "@rollup/rollup-freebsd-arm64": "4.56.0", - "@rollup/rollup-freebsd-x64": "4.56.0", - "@rollup/rollup-linux-arm-gnueabihf": "4.56.0", - "@rollup/rollup-linux-arm-musleabihf": "4.56.0", - "@rollup/rollup-linux-arm64-gnu": "4.56.0", - "@rollup/rollup-linux-arm64-musl": "4.56.0", - "@rollup/rollup-linux-loong64-gnu": "4.56.0", - "@rollup/rollup-linux-loong64-musl": "4.56.0", - "@rollup/rollup-linux-ppc64-gnu": "4.56.0", - "@rollup/rollup-linux-ppc64-musl": "4.56.0", - "@rollup/rollup-linux-riscv64-gnu": "4.56.0", - "@rollup/rollup-linux-riscv64-musl": "4.56.0", - "@rollup/rollup-linux-s390x-gnu": "4.56.0", - "@rollup/rollup-linux-x64-gnu": "4.56.0", - "@rollup/rollup-linux-x64-musl": "4.56.0", - "@rollup/rollup-openbsd-x64": "4.56.0", - "@rollup/rollup-openharmony-arm64": "4.56.0", - "@rollup/rollup-win32-arm64-msvc": "4.56.0", - "@rollup/rollup-win32-ia32-msvc": "4.56.0", - "@rollup/rollup-win32-x64-gnu": "4.56.0", - "@rollup/rollup-win32-x64-msvc": "4.56.0", + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", "fsevents": "~2.3.2" } }, @@ -7068,9 +7219,9 @@ } }, "node_modules/shell-quote": { - "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.9.0.tgz", + "integrity": "sha512-Iov+JwFv/2HcTpcwNMKd8+IWNb8tboQJNQTkAY/LLVK7gGH9jy+LGkVqPxfekHl+yMmiqXszdGWXgkfml7hjqA==", "dev": true, "license": "MIT", "engines": { @@ -7539,9 +7690,9 @@ } }, "node_modules/tinyglobby/node_modules/picomatch": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", - "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -7991,9 +8142,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.2", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", - "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index 840b284..0cb7d46 100644 --- a/package.json +++ b/package.json @@ -172,7 +172,7 @@ "patch-package": "^8.0.1", "postcss": "^8.5.6", "tailwindcss": "^3.4.17", - "vite": "^5.0.12", + "vite": "^5.4.21", "wait-on": "^7.2.0" }, "dependencies": { @@ -199,5 +199,8 @@ "ssh2": "^1.15.0", "ws": "^8.21.1", "zod": "^4.0.0" + }, + "overrides": { + "esbuild": "^0.25.0" } } diff --git a/src/renderer/components/assistant/AssistantPanel.jsx b/src/renderer/components/assistant/AssistantPanel.jsx index 4264ee4..246e955 100644 --- a/src/renderer/components/assistant/AssistantPanel.jsx +++ b/src/renderer/components/assistant/AssistantPanel.jsx @@ -716,7 +716,8 @@ function AssistantConversation({ ); } - * + +/** * One element that changes width, not two swapped for each other: a swap has * nothing to animate between, and the rail is the same column with everything * but the button clipped off. That is how the sidebar opens on the other side From 8e11a913876982bf011ddd9920733b1a734aedad Mon Sep 17 00:00:00 2001 From: Kyros Koh Date: Thu, 13 Aug 2026 10:45:10 +0800 Subject: [PATCH 6/6] Address CodeRabbit review on M1 trust gates. Harden CI credentials, unique session logs, strict approval acknowledgement, and restore localized approval UI before upstream merge. Fixes #5 Fixes #6 Fixes #8 Addresses #7 --- .github/workflows/ci.yml | 3 + .gitignore | 3 + CHANGELOG.md | 7 +- PRD.md | 241 ------------------ README.es.md | 7 +- README.md | 7 +- README.ru.md | 9 +- README.zh-CN.md | 2 +- docs/assistant-approvals.md | 6 +- src/main/ai/settings.js | 4 +- src/main/session-log.js | 24 +- .../components/assistant/ApprovalMenu.jsx | 27 +- .../components/assistant/AssistantPanel.jsx | 14 +- src/renderer/i18n/locales/en.js | 13 +- src/renderer/i18n/locales/pt.js | 11 +- src/renderer/i18n/locales/ru.js | 11 +- src/renderer/i18n/locales/vi.js | 11 +- src/renderer/i18n/locales/zh.js | 12 +- test/assistant-approval.test.js | 13 +- test/session-log.test.js | 16 +- 20 files changed, 147 insertions(+), 294 deletions(-) delete mode 100644 PRD.md diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 925825f..e638952 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -18,6 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + # PR code runs npm ci / npm test; do not leave GITHUB_TOKEN in git config. + persist-credentials: false - uses: actions/setup-node@v4 with: diff --git a/.gitignore b/.gitignore index 3510368..3a89e5e 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,6 @@ releases/ # Local working notes and agent config, not part of the project CLAUDE.md .claude/ + +# Local product planning notes; keep out of the shared tree +PRD.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 2528efb..ff7fce1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -29,8 +29,11 @@ with [GitHub Releases](https://github.com/BradPerbs/cloudterm/releases). ### Fixed -- Session log transcript files are opened synchronously in `start()` so - retention tests (and immediate `utime`) do not race the path. +- Session log transcript files use a unique exclusive path per session so two + tabs in the same second cannot interleave into one file. +- First-run approval warning only clears after **Got it** (not Escape / backdrop). +- Assistant approval acknowledgement accepts only real booleans when loading + `assistant.json`. ## [1.3.0] - 2026-08-10 diff --git a/PRD.md b/PRD.md deleted file mode 100644 index bed35da..0000000 --- a/PRD.md +++ /dev/null @@ -1,241 +0,0 @@ -# CloudTerm — Product Requirements Document - -| Field | Value | -|-------|-------| -| **Product** | CloudTerm | -| **Owner** | CloudBlast | -| **Version covered** | 1.3.0 (as shipped) | -| **Status** | Living document — current product + next roadmap | -| **License** | Fair-code (free to use/modify/share; commercial resale needs a CloudBlast license) | -| **Repo** | https://github.com/BradPerbs/cloudterm | - ---- - -## 1. Vision - -CloudTerm is a **desktop multi-protocol terminal workspace**: one window for SSH, SFTP, Telnet, serial, RDP, VNC, and BMC/IPMI, with encrypted local vault storage, optional CloudBlast cloud sync, and a local AI agent that can act through live sessions (with approval). - -**North star:** Replace tool sprawl (PuTTY + WinSCP + RDP client + VNC + serial terminal + snippet managers) with a single, trustworthy, modern app that travels with the user’s setup across machines. - ---- - -## 2. Problem - -| Pain | Today without CloudTerm | -|------|-------------------------| -| Tool sprawl | Separate apps per protocol; repeated logins and context switching | -| Setup doesn’t travel | Hosts, keys, snippets, known hosts differ per laptop | -| AI is paste-driven | Assistants don’t see the live terminal; users copy/paste riskily | -| CloudBlast customers | Manually re-enter VPS details instead of seeing servers ready to connect | - ---- - -## 3. Goals & non-goals - -### Goals - -1. **One workspace** for every common remote-access protocol on Windows, macOS, and Linux. -2. **Secrets stay local** — vault-locked encrypted storage; cloud sync encrypts before upload. -3. **CloudBlast differentiation** — free encrypted sync for everyone; auto host list for CloudBlast VPS customers. -4. **Session-aware AI** — local CLIs (Claude Code / Codex / OpenCode) with approval gates; no credential tools. -5. **Trustworthy distribution** — signed installs, predictable updates, clear fair-code terms. - -### Non-goals (near term) - -- Becoming a full IDE or browser-based terminal SaaS -- Replacing enterprise PAM / jump-box products -- Storing plaintext credentials in the cloud -- Shipping AI that can extract or export vault secrets -- Guaranteeing recovery if the vault password is forgotten - ---- - -## 4. Personas - -| Persona | Needs | Success looks like | -|---------|--------|-------------------| -| **Sysadmin / SRE** | Many hosts, jump hosts, tunnels, monitoring | Folders/tags/search; reconnect; uptime checks; activity log | -| **Developer** | SSH + SFTP + occasional RDP/VNC | Fast connect, split panes, snippets, remote edit | -| **CloudBlast customer** | Servers appear without manual entry | Sign in → VPS hosts ready | -| **Multi-machine user** | Same setup on laptop + desktop | Encrypted cloud snapshot restore on sign-in | -| **AI-assisted operator** | Agent reads session and runs approved commands | Clear approval UX; safe defaults | - ---- - -## 5. Current product (v1.3) - -### 5.1 User-facing capabilities - -| Area | Capability | -|------|------------| -| **Sessions** | SSH, Telnet, serial; jump hosts; SOCKS/HTTP proxies; tabs & split panes (max 8); themes; find; broadcast; screenshots; session log | -| **Files** | SFTP (list/mkdir/delete/rename, transfers, resume, conflicts, drag-and-drop); remote edit → upload on save | -| **Networking** | Local / remote / dynamic port forwarding with traffic counters | -| **Desktops** | RDP (IronRDP WASM), VNC (noVNC, SSH-tunnelled), BMC/IPMI web UI (sandboxed webview + auto-login) | -| **Inventory** | Hosts, folders, tags, search, quick-connect; keychain (generate/import, Windows Hello/TPM); snippets with `{{placeholders}}` | -| **Import** | `~/.ssh/config`, PuTTY, MobaXterm | -| **Security** | App lock / vault (password or OS keystore); encrypted backup export/restore | -| **Cloud** | CloudBlast OAuth (PKCE, system browser); VPS server sync; encrypted cross-device snapshot | -| **Ops** | Host TCP monitoring + notifications; activity log; auto-update from GitHub Releases (winget on Windows) | -| **AI** | Claude Code / Codex / OpenCode; tools: hosts, sessions, terminal read/write, files, connect/disconnect — approval modes | -| **i18n** | In-app: en, zh, vi, pt, ru (README also offers Español — see gap below) | - -### 5.2 Architecture (summary) - -``` -Renderer (React + xterm / IronRDP / noVNC) - ↕ IPC + MessagePort (session bytes) -Main (Electron): store, vault, transport (ssh|telnet|serial), - sftp, tunnels, rdp, vnc, bmc, account, AI, monitor -``` - -- **Security defaults:** `contextIsolation`, no `nodeIntegration`, `sandbox`; secrets resolved in main; vault lock gates IPC. -- **Persistence:** `sessions.json`, `vault.json`, window state, cloud snapshot under Electron `userData`; UI prefs in `localStorage`. - -### 5.3 Platforms & distribution - -| Channel | Notes | -|---------|--------| -| GitHub Releases | NSIS + portable (Win), DMG/zip (macOS), AppImage (Linux) | -| winget | `CloudBlast.CloudTerm` — see `docs/winget.md` | -| Signing | **Not code-signed today** — SmartScreen / enterprise friction | - ---- - -## 6. Requirements - -### 6.1 Functional (must remain true) - -| ID | Requirement | -|----|-------------| -| F1 | User can open SSH / Telnet / serial sessions from a host record without leaving the app | -| F2 | Credentials and private keys never leave the main process (RDP CredSSP path is the documented exception) | -| F3 | Vault lock blocks sensitive IPC until unlocked | -| F4 | SFTP and tunnels can reuse an active SSH connection | -| F5 | Cloud snapshot is encrypted client-side before upload | -| F6 | AI cannot read vault secrets; destructive/session-changing tools require approval unless user opts into a looser mode | -| F7 | Auto-update can be disabled (`CLOUDBLAST_UPDATE_DISABLED`) for air-gapped use | - -### 6.2 Non-functional - -| ID | Requirement | Current gap | -|----|-------------|-------------| -| N1 | PR builds run automated tests | **Shipped (M1):** `.github/workflows/ci.yml` on PRs and `main` | -| N2 | Installers code-signed on Windows (and ideally macOS) | Unsigned | -| N3 | Core paths (SSH connect, SFTP, lock/unlock) covered by automated smoke | Unit tests only; no E2E | -| N4 | Cold start acceptable for SSH-only users | RDP/VNC/WASM always in bundle weight | -| N5 | CJK IME composition usable in terminal | Known limitation — documented in `docs/ime-composition.md`; fix tracked in #7 | -| N6 | Marketing locales match in-app locales | Spanish README without in-app `es` | - ---- - -## 7. Success metrics (suggested) - -| Metric | Target (directional) | -|--------|----------------------| -| Crash-free sessions / week | Track via optional anonymous telemetry later; start with GitHub issue rate | -| Time-to-first-connect (fresh install → SSH) | < 2 minutes with import or CloudBlast sync | -| Update adoption (7-day) | Majority on latest within a week of release (post-signing) | -| Test gate | `npm test` green on every PR before merge | -| AI incident rate | Near-zero reports of unexpected `run_command` without approval on default settings | - ---- - -## 8. Roadmap — bugs, enhancements, optimizations - -Priorities: **P0** ship-blocking / trust / safety · **P1** product quality · **P2** polish & scale. - -### 8.1 Bug fixes & reliability (P0–P1) - -| Priority | Item | Rationale | Notes | -|----------|------|-----------|-------| -| **P0** | Add CI workflow that runs `npm test` on PRs | Regressions can ship with release-only CI | **Done** — `.github/workflows/ci.yml` | -| **P0** | Harden AI approval defaults + first-run warning | “Never ask” + `run_command` is high risk | **Done** — default `writes`, warning, confirm for `never`; see `docs/assistant-approvals.md` | -| **P0** | Audit RDP password lifetime in renderer/WASM | Documented secret exception | **Done** — see `docs/rdp-credentials.md`; tests in `test/rdp.test.js` | -| **P0** | Fix or quarantine IME composition positioning | Blocks CJK users; zh locale is shipped | **Quarantined (M1)** — `docs/ime-composition.md`; #7 open | -| **P1** | Session restore reconnect policy | Tabs restore without live sockets | Explicit “reconnect all” or per-tab option | -| **P1** | Update download UX | `autoDownload = true` may surprise corporate users | Optional “ask before download” | -| **P1** | Align Spanish: add `es` locale **or** remove es README until ready | Docs/product mismatch | | - -### 8.2 Enhancements (P1–P2) - -| Priority | Item | Rationale | -|----------|------|-----------| -| **P1** | Code-sign Windows (and ideally macOS) builds | SmartScreen, winget, enterprise adoption | -| **P1** | Split oversized modules (`ipc.js`, `store.js`, `App.jsx`, `TerminalView.jsx`) by domain | Safer reviews; fewer regressions | -| **P1** | E2E smoke: mock SSH → SFTP → lock/unlock | Catch IPC/UI wiring unit tests miss | -| **P1** | SSH/SFTP integration tests (container or mock) | Core path under-tested vs edge protocols | -| **P1** | True 1024² app icon (replace upscaled soft cloud) | Documented in `resources/README.md` | -| **P1** | Rename npm package / clarify branding (`cloudblast-ssh` → `cloudterm`) | Contributor & packaging clarity | -| **P2** | Accessibility pass on dialogs + terminal chrome | Enterprise procurement | -| **P2** | Portable winget package parity | Mentioned as future in `docs/winget.md` | -| **P2** | Document env vars & `userData` layout under `docs/` | Support / IT / contributors | -| **P2** | Gradual TypeScript on `store` / `vault` / `ai/tools` | Catch IPC/secret-field shape bugs | - -### 8.3 Optimizations (P2) - -| Priority | Item | Rationale | -|----------|------|-----------| -| **P2** | Lazy-load RDP / VNC / noVNC chunks | Faster cold start for SSH-only users | -| **P2** | Cap / paginate activity & AI event retention more aggressively | Long-lived processes; large event buffers | -| **P2** | Parallelize `npm test` (today sequential `&&`) | Faster local + CI feedback | -| **P2** | Profile WebGL terminals under multi-pane load | Cap is 8 panes; validate GPU cost | - -### 8.4 Explicitly out of scope for this PRD cycle - -- Vault password recovery without user-held secret -- Server-side decryption of cloud snapshots -- AI tools that return private keys or vault DEK material - ---- - -## 9. Security & threat notes (product constraints) - -| Topic | Stance | -|-------|--------| -| Vault | Protects against disk theft; forgotten password = data unrecoverable by design | -| Cloud snapshot | Protects DB theft in transit/at rest; **not** a compromised CloudBlast app server acting as a malicious client | -| AI | Local CLIs under user accounts; approval modes are the control plane | -| BMC webview | Sandboxed but still untrusted vendor JS on LAN — treat as higher risk surface | -| RDP | Password must reach WASM for CredSSP — minimize lifetime and logging | -| OAuth | System browser + PKCE; client id `cloudblast-desktop` | - ---- - -## 10. Milestones (suggested) - -| Milestone | Scope | Exit criteria | -|-----------|--------|---------------| -| **M1 — Trust & gates** | PR CI for tests; AI default hardening; RDP password audit; IME fix plan | **Shipped** — CI on PRs; safer AI defaults; RDP docs + tests; IME quarantined in `docs/ime-composition.md` | -| **M2 — Distribution** | Code signing; icon quality; optional update consent | Signed Win (+ mac if feasible); reduced SmartScreen friction | -| **M3 — Quality depth** | Module splits; E2E smoke; SSH/SFTP integration tests | Reviewable IPC surfaces; smoke green in CI | -| **M4 — Polish** | Locale alignment; lazy-load remoting; docs for env/`userData`; a11y pass | Marketing ↔ app parity; measurable startup win | - ---- - -## 11. Open questions - -1. Should cloud sync remain forever-free for non-CloudBlast users, or become tiered later? -2. Is code signing budgeted for Windows only first, or Win+mac together? -3. Should “never ask” AI mode remain available, or be removed / hidden behind an advanced unlock? -4. Telemetry: opt-in crash/usage metrics, or stay fully silent? -5. Rename `appId` / package name in a major version, or keep `com.cloudblast.ssh` for upgrade continuity? - ---- - -## 12. References - -| Doc | Path / URL | -|-----|------------| -| User README | `README.md` (+ zh-CN, es, ru) | -| Changelog | `CHANGELOG.md` | -| Contributor docs | `docs/README.md` | -| Winget publish | `docs/winget.md` | -| Resources / icon notes | `resources/README.md` | -| License | `LICENSE` | -| Product site | https://cloudblast.io | - ---- - -*This PRD describes the product as of v1.3.0 plus the M1 trust-and-gates -work on `feat/m1-trust-gates`. Update milestones when releases ship.* diff --git a/README.es.md b/README.es.md index 666534f..5ab225d 100644 --- a/README.es.md +++ b/README.es.md @@ -121,9 +121,10 @@ para leerlo y modificarlo. - **Trabaja en el terminal que ves**: los comandos se escriben en el panel y la salida se queda en tu historial, o se ejecutan en un canal oculto si lo prefieres -- **Pregunta antes de cambiar nada** — tres modos de aprobación (cada - herramienta / solo cambios / nunca), aviso al primer uso y confirmación antes - de **Nunca preguntar**. Ver [docs/assistant-approvals.md](docs/assistant-approvals.md) (inglés) +- **Los modos de aprobación controlan cuándo pregunta** — cada herramienta / + solo cambios / nunca (nunca auto-aprueba herramientas no bloqueadas), aviso + al primer uso y confirmación antes de **Nunca preguntar**. Ver + [docs/assistant-approvals.md](docs/assistant-approvals.md) (inglés) - **Apuntado donde tú digas**: la sesión que tienes delante, una que fijes, o todos los hosts que tengas guardados - **Herramientas en vez de suposiciones**: conectar un host guardado, leer y diff --git a/README.md b/README.md index ea02115..e4596f4 100644 --- a/README.md +++ b/README.md @@ -137,9 +137,10 @@ Whichever you choose, the agent: one it answers, without you pasting anything - **Works in the terminal you can see**: commands are typed into the pane and the output stays in your scrollback, or run on a hidden channel if you prefer -- **Asks before it changes anything** — three approval modes (every tool / - changes only / never), a first-run warning, and a confirm step before - **Never ask**. See [assistant approvals](docs/assistant-approvals.md). +- **Approval modes control when it asks** — every tool / changes only / never + (never auto-approves non-blocked tools), plus a first-run warning and a + confirm step before **Never ask**. See + [assistant approvals](docs/assistant-approvals.md). - **Pointed where you like**: the session in front, one you pin, or every host you have saved - **Tools instead of guesses**: connect a saved host, read and write files, diff --git a/README.ru.md b/README.ru.md index 15d5f16..4561cb8 100644 --- a/README.ru.md +++ b/README.ru.md @@ -121,10 +121,11 @@ CloudTerm собирает все способы подключения к се - **Работает в терминале, который вы видите**: команды печатаются в панели, а вывод остаётся в вашем буфере. Или выполняются в скрытом канале, если так удобнее -- **Спрашивает, прежде чем что-то менять** — три режима одобрения (каждый - инструмент / только изменения / никогда), предупреждение при первом запуске и - подтверждение перед **Никогда не спрашивать**. См. - [docs/assistant-approvals.md](docs/assistant-approvals.md) (англ.) +- **Режим одобрения решает, когда спрашивать** — каждый инструмент / только + изменения / никогда (никогда само одобряет незаблокированные инструменты), + предупреждение при первом запуске и подтверждение перед **Никогда не + спрашивать**. См. [docs/assistant-approvals.md](docs/assistant-approvals.md) + (англ.) - **Направлен туда, куда скажете**: сеанс перед вами, закреплённый сеанс или все сохранённые хосты - **Инструменты вместо догадок**: подключиться к сохранённому хосту, читать и diff --git a/README.zh-CN.md b/README.zh-CN.md index 3dd0a27..6c16520 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -105,7 +105,7 @@ Windows 主机,或者任意服务商上的服务器。CloudTerm 由 VPS 云主

- **使用本机已有的 Claude Code、Codex 或 OpenCode**,沿用你自己的账号和配置 -- **读取当前会话并操作远程服务器**,执行更改前会先征求你的同意;提供三种审批模式,详见 [docs/assistant-approvals.md](docs/assistant-approvals.md)(英文) +- **读取当前会话并操作远程服务器**;是否征求同意由审批模式决定(每次 / 仅更改 / 从不——从不模式会自动批准未拦截的工具),详见 [docs/assistant-approvals.md](docs/assistant-approvals.md)(英文) - **每个对话可单独选择模型和推理强度**,并在运行时显示用量 ### 终端 diff --git a/docs/assistant-approvals.md b/docs/assistant-approvals.md index ce3340a..b98eb33 100644 --- a/docs/assistant-approvals.md +++ b/docs/assistant-approvals.md @@ -7,14 +7,14 @@ Approval modes decide when those tools stop for a person. |------|-------|-----------| | Ask every time | `always` | Every tool waits, including reads | | Ask before changes | `writes` | **Default.** Reads run freely; mutating tools ask | -| Never ask | `never` | Nothing asks (except the block list) | +| Never ask | `never` | Nothing asks; blocked commands are still refused | ## Defaults and hardening - New installs ship with `writes` and `allowLocalTools: false`. - The first time the assistant panel opens, a short warning explains the modes - and that tools act on live sessions. Dismissing it sets - `acknowledgedApprovalWarning` in `assistant.json`. + and that tools act on live sessions. Choosing **Got it** sets + `acknowledgedApprovalWarning` in `assistant.json` (Escape / backdrop do not). - Switching to **Never ask** from the composer chip or Settings requires an explicit confirm dialog. The block list (`blockedCommands`, seeded with `rm -rf`) still refuses matching commands under every mode, including never. diff --git a/src/main/ai/settings.js b/src/main/ai/settings.js index c6a4587..d299b42 100644 --- a/src/main/ai/settings.js +++ b/src/main/ai/settings.js @@ -152,8 +152,8 @@ function sanitize(raw) { .filter(Boolean) .slice(0, 12); } - if ('acknowledgedApprovalWarning' in raw) { - next.acknowledgedApprovalWarning = Boolean(raw.acknowledgedApprovalWarning); + if (typeof raw.acknowledgedApprovalWarning === 'boolean') { + next.acknowledgedApprovalWarning = raw.acknowledgedApprovalWarning; } } return next; diff --git a/src/main/session-log.js b/src/main/session-log.js index 344da1e..8a42a20 100644 --- a/src/main/session-log.js +++ b/src/main/session-log.js @@ -1,6 +1,7 @@ const { app } = require('electron'); const path = require('path'); const fs = require('fs'); +const crypto = require('crypto'); const activity = require('./activity'); /** @@ -286,7 +287,7 @@ function start(tabId, { hostName = '', address = '', hostId = '', protocol = '', const directory = current.directory || defaultDirectory(); const startedAt = new Date(); - const filePath = path.join(directory, `${slug(hostName || address)}_${stamp(startedAt)}.log`); + const baseName = `${slug(hostName || address)}_${stamp(startedAt)}`; try { fs.mkdirSync(directory, { recursive: true }); @@ -294,9 +295,24 @@ function start(tabId, { hostName = '', address = '', hostId = '', protocol = '', // Open synchronously so a returned path already exists on disk. The // stream open is otherwise async, and callers (and the retention // tests) treat the path as ready the moment start() returns. - // `a`, not `w`: two sessions to the same host within the same second - // would otherwise have the second silently truncate the first. - const fd = fs.openSync(filePath, 'a'); + // Exclusive create (`wx`) plus a random suffix: two tabs that stamp + // the same second must not share or truncate one transcript. + let filePath; + let fd; + for (let attempt = 0; attempt < 8; attempt++) { + const suffix = crypto.randomBytes(attempt === 0 ? 3 : 4).toString('hex'); + filePath = path.join(directory, `${baseName}_${suffix}.log`); + try { + fd = fs.openSync(filePath, 'wx'); + break; + } catch (error) { + if (error.code !== 'EEXIST') throw error; + fd = undefined; + } + } + if (fd === undefined) { + throw new Error('Could not create a unique session log path'); + } const stream = fs.createWriteStream(null, { fd, encoding: 'utf8' }); stream.on('error', (error) => { console.error(`Session log write failed for ${tabId}:`, error.message); diff --git a/src/renderer/components/assistant/ApprovalMenu.jsx b/src/renderer/components/assistant/ApprovalMenu.jsx index 9b4031d..c0af085 100644 --- a/src/renderer/components/assistant/ApprovalMenu.jsx +++ b/src/renderer/components/assistant/ApprovalMenu.jsx @@ -2,6 +2,7 @@ import { useState } from 'react'; import { ArrowDown01Icon, FlashIcon, SecurityCheckIcon, Shield01Icon } from 'hugeicons-react'; import PanelMenu from './PanelMenu'; import ConfirmDialog from '../ui/ConfirmDialog'; +import { useT } from '../../i18n'; /** * What the assistant will do before asking, as a chip in the composer. @@ -37,15 +38,23 @@ const APPROVALS = [ }, { value: 'never', - label: 'Never ask', - hint: 'Nothing stops, deletes included', + labelKey: 'assistant.approvalNever', + hintKey: 'assistant.approvalNeverHint', icon: , }, ]; export default function ApprovalMenu({ settings, onChange }) { + const t = useT(); const [confirmNever, setConfirmNever] = useState(false); - const current = APPROVALS.find(option => option.value === settings.approval) || APPROVALS[1]; + + const options = APPROVALS.map(option => ({ + ...option, + label: t(option.labelKey), + hint: t(option.hintKey), + })); + + const current = options.find(option => option.value === settings.approval) || options[1]; // The one state worth colouring. Somebody who has turned approvals off and // then forgotten deserves to notice before the next destructive command, @@ -69,7 +78,7 @@ export default function ApprovalMenu({ settings, onChange }) { { value: settings.approval, onChange: pick, - options: APPROVALS, + options, }, ]} trigger={({ open, toggle }) => ( @@ -77,9 +86,9 @@ export default function ApprovalMenu({ settings, onChange }) { type="button" aria-haspopup="menu" aria-expanded={open} - aria-label={`Approvals: ${current.label}`} + aria-label={t('assistant.approvalsLabel', { mode: current.label })} onClick={toggle} - title={`Approvals: ${current.label}`} + title={t('assistant.approvalsLabel', { mode: current.label })} className={`h-7 pl-1.5 pr-1 rounded-xl flex items-center gap-0.5 transition-colors outline-none focus-visible:ring-2 focus-visible:ring-gray-900/20 dark:focus-visible:ring-white/25 @@ -102,9 +111,9 @@ export default function ApprovalMenu({ settings, onChange }) { {confirmNever && ( { setConfirmNever(false); diff --git a/src/renderer/components/assistant/AssistantPanel.jsx b/src/renderer/components/assistant/AssistantPanel.jsx index f22456c..7cc1c81 100644 --- a/src/renderer/components/assistant/AssistantPanel.jsx +++ b/src/renderer/components/assistant/AssistantPanel.jsx @@ -19,6 +19,7 @@ import ModelMenu from './ModelMenu'; import ApprovalMenu from './ApprovalMenu'; import HistoryMenu from './HistoryMenu'; import Dialog, { DialogButton } from '../ui/Dialog'; +import { useT } from '../../i18n'; import { GLOBAL, describe, @@ -482,9 +483,7 @@ function AssistantConversation({ {t('assistant.welcome')}

- It reads this terminal, runs commands on their own channel, and can work - across every host you have saved. Approvals default to ask-before-changes; - switch modes from the composer chip or Settings. + {t('assistant.welcomeNote')}

@@ -696,9 +695,10 @@ function AssistantConversation({ {settings && !settings.acknowledgedApprovalWarning && ( changeSettings({ acknowledgedApprovalWarning: true })} + title={t('assistant.approvalWarningTitle')} + subtitle={t('assistant.approvalWarningBody')} + // Escape / backdrop must not count as acknowledgement. + onClose={() => {}} icon={ @@ -711,7 +711,7 @@ function AssistantConversation({ data-autofocus onClick={() => changeSettings({ acknowledgedApprovalWarning: true })} > - Got it + {t('assistant.approvalWarningGotIt')} } /> diff --git a/src/renderer/i18n/locales/en.js b/src/renderer/i18n/locales/en.js index 9203540..29d3735 100644 --- a/src/renderer/i18n/locales/en.js +++ b/src/renderer/i18n/locales/en.js @@ -1121,9 +1121,10 @@ export default { * The assistant panel * ---------------------------------------------------------------- */ 'assistant.title': 'AI Agent', - 'assistant.welcome': 'Let’s work on your servers', + 'assistant.welcome': "Let's work on your servers", 'assistant.welcomeNote': 'It reads this terminal, runs commands on their own channel, and can ' - + 'work across every host you have saved.', + + 'work across every host you have saved. Approvals default to ask-before-changes; switch ' + + 'modes from the composer chip or Settings.', 'assistant.createQuickPrompts': 'Create quick prompts', 'assistant.newConversation': 'New conversation', 'assistant.chats': 'Chats', @@ -1171,8 +1172,14 @@ export default { 'assistant.approvalAlwaysHint': 'Every tool call waits for you', 'assistant.approvalWrites': 'Ask before changes', 'assistant.approvalWritesHint': 'Reading runs freely', - 'assistant.approvalNever': 'Yolo Mode', + 'assistant.approvalNever': 'Never ask', 'assistant.approvalNeverHint': 'Nothing stops, deletes included', + 'assistant.approvalWarningTitle': 'How approvals work', + 'assistant.approvalWarningBody': 'The assistant can read live terminals and run commands on ' + + 'your hosts. By default it asks before anything that changes a system. Ask every time ' + + 'stops on reads too. Never ask runs tools without prompts (blocked commands are still ' + + 'refused).', + 'assistant.approvalWarningGotIt': 'Got it', 'assistant.didListHosts': 'Listed hosts', 'assistant.didListSessions': 'Listed sessions', diff --git a/src/renderer/i18n/locales/pt.js b/src/renderer/i18n/locales/pt.js index 0945f5e..7cfe126 100644 --- a/src/renderer/i18n/locales/pt.js +++ b/src/renderer/i18n/locales/pt.js @@ -1082,7 +1082,8 @@ export default { 'assistant.title': 'Agente de IA', 'assistant.welcome': 'Vamos trabalhar nos seus servidores', 'assistant.welcomeNote': 'Lê este terminal, corre comandos no canal deles, e pode trabalhar em ' - + 'todos os anfitriões que tem guardados.', + + 'todos os anfitriões que tem guardados. As aprovações começam em perguntar-antes-de-' + + 'alterar; mude o modo no chip do compositor ou nas Definições.', 'assistant.createQuickPrompts': 'Criar perguntas rápidas', 'assistant.newConversation': 'Nova conversa', 'assistant.chats': 'Conversas', @@ -1130,8 +1131,14 @@ export default { 'assistant.approvalAlwaysHint': 'Todas as chamadas a ferramentas esperam por si', 'assistant.approvalWrites': 'Perguntar antes de alterar', 'assistant.approvalWritesHint': 'A leitura corre à vontade', - 'assistant.approvalNever': 'Modo Yolo', + 'assistant.approvalNever': 'Nunca perguntar', 'assistant.approvalNeverHint': 'Nada para, incluindo o que apaga', + 'assistant.approvalWarningTitle': 'Como funcionam as aprovações', + 'assistant.approvalWarningBody': 'O assistente pode ler terminais ao vivo e correr comandos nos ' + + 'seus anfitriões. Por predefinição pergunta antes de qualquer coisa que altere um ' + + 'sistema. Perguntar sempre também para nas leituras. Nunca perguntar corre ferramentas ' + + 'sem pedidos (comandos bloqueados continuam a ser recusados).', + 'assistant.approvalWarningGotIt': 'Percebi', 'assistant.didListHosts': 'Listou os anfitriões', 'assistant.didListSessions': 'Listou as sessões', diff --git a/src/renderer/i18n/locales/ru.js b/src/renderer/i18n/locales/ru.js index 235da69..9859599 100644 --- a/src/renderer/i18n/locales/ru.js +++ b/src/renderer/i18n/locales/ru.js @@ -1123,7 +1123,8 @@ export default { 'assistant.title': 'ИИ-ассистент', 'assistant.welcome': 'Займёмся вашими серверами', 'assistant.welcomeNote': 'Он читает этот терминал, выполняет команды в отдельном канале и может ' - + 'работать со всеми сохранёнными хостами.', + + 'работать со всеми сохранёнными хостами. По умолчанию спрашивает перед изменениями; ' + + 'режим можно сменить в чипе композера или в настройках.', 'assistant.createQuickPrompts': 'Создать быстрые вопросы', 'assistant.newConversation': 'Новый разговор', 'assistant.chats': 'Разговоры', @@ -1173,8 +1174,14 @@ export default { 'assistant.approvalAlwaysHint': 'Каждый вызов инструмента ждёт вас', 'assistant.approvalWrites': 'Спрашивать перед изменениями', 'assistant.approvalWritesHint': 'Чтение выполняется свободно', - 'assistant.approvalNever': 'Режим Yolo', + 'assistant.approvalNever': 'Никогда не спрашивать', 'assistant.approvalNeverHint': 'Ничто не останавливается, включая удаление', + 'assistant.approvalWarningTitle': 'Как работают подтверждения', + 'assistant.approvalWarningBody': 'Ассистент может читать живые терминалы и выполнять команды на ' + + 'ваших хостах. По умолчанию спрашивает перед любым изменением системы. «Спрашивать ' + + 'каждый раз» останавливается и на чтении. «Никогда не спрашивать» запускает инструменты ' + + 'без запросов (заблокированные команды по-прежнему отклоняются).', + 'assistant.approvalWarningGotIt': 'Понятно', 'assistant.didListHosts': 'Перечислил хосты', 'assistant.didListSessions': 'Перечислил сеансы', diff --git a/src/renderer/i18n/locales/vi.js b/src/renderer/i18n/locales/vi.js index b26ca6b..12b3749 100644 --- a/src/renderer/i18n/locales/vi.js +++ b/src/renderer/i18n/locales/vi.js @@ -1034,7 +1034,8 @@ export default { 'assistant.title': 'Trợ lý AI', 'assistant.welcome': 'Cùng làm việc trên máy chủ của bạn nào', 'assistant.welcomeNote': 'Nó đọc terminal này, chạy lệnh trên kênh riêng, và có thể làm việc ' - + 'trên mọi máy chủ bạn đã lưu.', + + 'trên mọi máy chủ bạn đã lưu. Phê duyệt mặc định là hỏi trước khi thay đổi; đổi chế độ ' + + 'từ chip trên khung soạn hoặc trong Cài đặt.', 'assistant.createQuickPrompts': 'Tạo câu hỏi nhanh', 'assistant.newConversation': 'Cuộc trò chuyện mới', 'assistant.chats': 'Cuộc trò chuyện', @@ -1081,8 +1082,14 @@ export default { 'assistant.approvalAlwaysHint': 'Mọi lần gọi công cụ đều chờ bạn', 'assistant.approvalWrites': 'Hỏi trước khi thay đổi', 'assistant.approvalWritesHint': 'Thao tác đọc chạy tự do', - 'assistant.approvalNever': 'Chế độ thả cửa', + 'assistant.approvalNever': 'Không bao giờ hỏi', 'assistant.approvalNeverHint': 'Không gì dừng lại, kể cả lệnh xoá', + 'assistant.approvalWarningTitle': 'Cách phê duyệt hoạt động', + 'assistant.approvalWarningBody': 'Trợ lý có thể đọc terminal đang chạy và thực thi lệnh trên ' + + 'máy chủ của bạn. Mặc định nó hỏi trước mọi thứ làm thay đổi hệ thống. Hỏi mọi lúc cũng ' + + 'dừng cả thao tác đọc. Không bao giờ hỏi chạy công cụ không hỏi (lệnh bị chặn vẫn bị ' + + 'từ chối).', + 'assistant.approvalWarningGotIt': 'Đã hiểu', 'assistant.didListHosts': 'Đã liệt kê máy chủ', 'assistant.didListSessions': 'Đã liệt kê phiên', diff --git a/src/renderer/i18n/locales/zh.js b/src/renderer/i18n/locales/zh.js index 2bad0c3..aeb6137 100644 --- a/src/renderer/i18n/locales/zh.js +++ b/src/renderer/i18n/locales/zh.js @@ -421,7 +421,7 @@ export default { + '并向你展示确切的命令以及将要运行它的主机。这是默认设置。', 'settings.assistant.approval.never.note': '任何操作都不会停下来等待批准,包括删除数据或重启服务的命令。' + '只有在你能承受把主机弄坏时才适合使用。开启前会先要求确认。', - 'settings.assistant.approval.neverConfirmTitle': '关闭批准确认?', + 'settings.assistant.approval.neverConfirmTitle': '关闭审批?', 'settings.assistant.approval.neverConfirmMessage': '“从不询问”会让每个工具直接运行而不停下来——' + '包括在活动主机上删除数据或重启服务的命令。拦截列表中的命令仍会被拒绝。', 'settings.assistant.localTools': '允许在这台电脑上使用工具', @@ -944,7 +944,8 @@ export default { /* ---- Assistant panel ---- */ 'assistant.title': 'AI 助手', 'assistant.welcome': '一起来打理你的服务器吧', - 'assistant.welcomeNote': '它会读取这个终端,在独立通道上运行命令,并且可以在你保存的所有主机上工作。', + 'assistant.welcomeNote': '它会读取这个终端,在独立通道上运行命令,并且可以在你保存的所有主机上工作。' + + '默认审批为更改前询问;可从输入框旁的芯片或设置中切换模式。', 'assistant.createQuickPrompts': '创建快捷提问', 'assistant.newConversation': '新对话', 'assistant.chats': '对话', @@ -991,8 +992,13 @@ export default { 'assistant.approvalAlwaysHint': '每一次工具调用都会等你确认', 'assistant.approvalWrites': '更改前询问', 'assistant.approvalWritesHint': '读取操作自由进行', - 'assistant.approvalNever': '放手模式', + 'assistant.approvalNever': '从不询问', 'assistant.approvalNeverHint': '什么都不会停下,包括删除', + 'assistant.approvalWarningTitle': '审批如何运作', + 'assistant.approvalWarningBody': '助手可以读取实时终端,并在你的主机上运行命令。默认会在任何会改变' + + '系统的操作前询问。“每次都询问”也会在读取时停下。“从不询问”会在不提示的情况下运行工具' + + '(拦截列表中的命令仍会被拒绝)。', + 'assistant.approvalWarningGotIt': '知道了', 'assistant.didListHosts': '已列出主机', 'assistant.didListSessions': '已列出会话', diff --git a/test/assistant-approval.test.js b/test/assistant-approval.test.js index a7193e3..ffe39f1 100644 --- a/test/assistant-approval.test.js +++ b/test/assistant-approval.test.js @@ -84,8 +84,17 @@ check('the first-run warning flag sanitizes and persists', () => { assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, false); settingsModule.set({ acknowledgedApprovalWarning: true }); assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, true); - settingsModule.set({ acknowledgedApprovalWarning: 0 }); - assert.strictEqual(settingsModule.get().acknowledgedApprovalWarning, false); + + // Prove assistant.json survives a fresh module load, not only in-memory state. + const settingsPath = require.resolve(path.join(ROOT, 'ai', 'settings')); + delete require.cache[settingsPath]; + const reloaded = require(settingsPath); + assert.strictEqual(reloaded.get().acknowledgedApprovalWarning, true); + + // Non-booleans must not coerce into a truthy acknowledgement. + reloaded.set({ acknowledgedApprovalWarning: 0 }); + assert.strictEqual(reloaded.get().acknowledgedApprovalWarning, false); + settingsModule.set({ acknowledgedApprovalWarning: false }); // Leave it false for later checks that read the live settings object. }); diff --git a/test/session-log.test.js b/test/session-log.test.js index 4beacef..ecd269e 100644 --- a/test/session-log.test.js +++ b/test/session-log.test.js @@ -219,9 +219,23 @@ const body = (text) => text.split('\n').filter(line => !line.startsWith('#')).jo await settle(); const name = path.basename(filePath); - assert.ok(/^db-primary-2_\d{4}-\d{2}-\d{2}_\d{6}\.log$/.test(name), `unexpected name: ${name}`); + assert.ok( + /^db-primary-2_\d{4}-\d{2}-\d{2}_\d{6}_[0-9a-f]+\.log$/.test(name), + `unexpected name: ${name}` + ); }); + await checkAsync('gives each session its own transcript file', async () => { + const first = sessionLog.start('tab-2a', { hostName: 'same-host', address: 'x' }); + const second = sessionLog.start('tab-2b', { hostName: 'same-host', address: 'x' }); + sessionLog.close('tab-2a'); + sessionLog.close('tab-2b'); + await settle(); + + assert.ok(first && second, 'both sessions opened a log'); + assert.notStrictEqual(first, second, 'concurrent sessions must not share a path'); + assert.ok(fs.existsSync(first) && fs.existsSync(second), 'both files exist'); + }); await checkAsync('reassembles a sequence split across two writes', async () => { const filePath = sessionLog.start('tab-3', { hostName: 'split', address: 'x' });