From 72953e0ae108b293f41220eadb474477008cd04c Mon Sep 17 00:00:00 2001 From: KNIHAL Date: Sat, 31 Oct 2026 21:42:59 +0530 Subject: [PATCH 01/18] docs: add quality and hardening plan (P0, P1, P2) Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_01KXDWKN2tz335nxPSo4Sv2b --- docs/P0.md | 329 +++++++++++++++++++++++++++++++++++++++++++++++++ docs/P1.md | 210 +++++++++++++++++++++++++++++++ docs/P2.md | 82 ++++++++++++ docs/README.md | 90 ++++++++++++++ 4 files changed, 711 insertions(+) create mode 100644 docs/P0.md create mode 100644 docs/P1.md create mode 100644 docs/P2.md create mode 100644 docs/README.md diff --git a/docs/P0.md b/docs/P0.md new file mode 100644 index 0000000..715f6a7 --- /dev/null +++ b/docs/P0.md @@ -0,0 +1,329 @@ +# P0: before 1.5.4 and the 10 Nov launch + +Release blockers. Each item lists the evidence from the current code, the fix, and the +acceptance test. Work on branch `security/hardening-1.5.4`. + +Status: `[ ]` open, `[~]` in progress, `[x]` done. + +--- + +## P0-01 Vulnerable dependencies `[ ]` + +**Evidence.** `npm audit --omit=dev` on 31 Oct 2026 reports 11 issues: 3 critical, 5 high, +3 moderate. + +| Package | Installed | Severity | Fix | +|---|---|---|---| +| `simple-git` (direct) | 3.36.0 | critical (unsafe operation guards can be bypassed) | 4.0.2, major version | +| `@modelcontextprotocol/sdk` (direct) | 1.29.0 | high | 1.32.2 | +| `zod` (direct) | 4.4.3 | high (memory exhaustion on oversized collections) | 4.6.6 or newer | +| `proxy-addr`, `body-parser`, `fast-uri`, `ip-address`, `hono`, `@hono/node-server`, `qs` | transitive | critical to moderate | cleared by the SDK upgrade | + +**Impact.** The server uses stdio, so the HTTP-related advisories are low exposure. `simple-git` +runs on every git tool call and is the one that matters. + +**Fix.** Upgrade in this order, running the full suite after each step: `zod`, SDK, `simple-git` +4.x (read its changelog, it is a major version), then `mysql2`, `pg` and `vitest`. Regenerate the +lockfile. + +**Acceptance.** +- [ ] `npm audit --omit=dev` shows no high or critical findings +- [ ] All 20 git tools pass their tests against `simple-git` 4.x +- [ ] `lazyLoading.e2e`, `useTool.e2e` and `agent-smoke` pass on the new SDK + +**Docs to update.** `CHANGELOG.md` security section. If `simple-git` 4 changes how a git tool behaves, also `tools/filesystem-git-shell.md`. + +--- + +## P0-02 Issue #2: `npm install` fails in nested `cmd.exe` on Windows `[ ]` + +**Evidence.** GitHub issue #2. `npm install` through `run_command` fails on packages with +postinstall scripts: `'node' is not recognized`. `tools/commandOps.js` already patches `PATHEXT` +and `PATH`, but: + +- Line 81 falls back to a hard-coded user profile: `C:\Users\nihal`. That is wrong on every + other machine. +- The Node directory is hard-coded as `C:\Program Files\nodejs`. A Node installed with nvm, + Volta, fnm or Scoop lives elsewhere. +- Missing directories are appended, so an older Node earlier in `PATH` still wins. +- `ComSpec` is not set. npm runs lifecycle scripts through `cmd.exe` and reads `ComSpec` to find + it. In a stripped environment (Claude Desktop) it can be empty. +- The directory of the running Node (`path.dirname(process.execPath)`) is never used. + +**Fix.** +1. Prepend `path.dirname(process.execPath)` and the npm global prefix to `PATH`. Prepend, so the + Node that runs the server is the Node that runs scripts. +2. Remove the hard-coded `C:\Users\nihal` and `C:\Program Files\nodejs`. Derive the profile from + `os.homedir()`. +3. Set `ComSpec` to the absolute `cmd.exe` path when it is missing. +4. Keep the PowerShell and `cmd.exe` fallbacks as they are. + +**Acceptance.** +- [ ] A test starts `runCommand` with a stripped environment (only `SystemRoot`, no `ComSpec`, + no Node directory in `PATH`) and runs a package whose postinstall calls `node` +- [ ] A test proves no code path contains a user name or fixed install directory +- [ ] Manual check from Claude Desktop on Windows: `npm install` of a package with a native + postinstall succeeds +- [ ] Issue #2 closed with the fix commit. The workaround note is removed from + `website/docs/server/troubleshooting.md` and `limitations.md` + +**Docs to update.** `troubleshooting.md` (remove the Windows postinstall workaround), `limitations.md` (Platforms), and close issue #2. + +--- + +## P0-03 Database credentials reach the activity log `[ ]` + +**Evidence.** `tools/security.js` redacts by key name. The list (`token`, `password`, `secret`, +`api_key`, and so on) does not include `connection_string`, and URL credentials inside a value +(`postgres://user:pass@host/db`) are not recognized. `tools/toolWrapper.js:54` returns +`err.message` to the client without passing it through the redactor. + +**Impact.** A Postgres or MySQL call writes the full connection string, password included, to +`logs/activity.log`. Errors can echo it back to the agent. + +**Fix.** One central redaction module used by the logger, error responses and API results: +- key names: add `connection_string`, `connectionString`, `dsn`, `database_url`, `uri`, `url` + (when it carries credentials) +- values: strip `scheme://user:password@host` userinfo, `Bearer` tokens, `ghp_`, `xoxb-`, + `sk-` style prefixes, and JSON `"password": "..."` fragments +- apply it in `toolWrapper.js` before returning an error to the client + +**Acceptance.** +- [ ] A test calls `postgres_query` with a bad host and a password in the connection string, then + asserts the password is absent from the log line and from the returned error +- [ ] Table-driven tests for each pattern above, including false positives (an ordinary URL + without credentials is left alone) + +**Docs to update.** `security.md` (Secret redaction: new key names and credentials inside URLs), `tools/database.md` (connection strings are redacted in logs). + +--- + +## P0-04 Docker Compose shell injection (WSL fallback) `[ ]` + +**Evidence.** `tools/dockerOps.js:231` and `:249`: +``const composeCmd = `cd '${wslDir}' && docker compose up...` ``. A project directory containing a +single quote closes the quote and runs the rest as shell. + +**Fix.** Do not build shell text from a path. Run `wsl.exe` with an argument array +(`--cd -- docker compose up -d`) so the path is never parsed by a shell. Then audit every +`exec`, `execAsync` and string-built command in `tools/` for the same pattern. + +**Acceptance.** +- [ ] A test passes a directory name containing `'`, `"`, `;`, `$(...)`, a backtick and a space + and asserts the argument array is intact and nothing is evaluated +- [ ] A grep-based test fails if a new template string is passed to `exec` + +**Docs to update.** `tools/docker.md` (how Compose runs through WSL on Windows), `security.md` (What is not protected) if the wording changes. + +--- + +## P0-05 `http_check` has no SSRF protection `[ ]` + +**Evidence.** `tools/vercelOps.js:180`: `fetch(url, { method: "GET", redirect: "follow" })`. +`web_fetch` in `tools/webOps.js` already blocks private, loopback and link-local addresses, +checked at connection time, but `http_check` does not use it. + +**Impact.** An agent steered by hostile page content can probe internal addresses +(`169.254.169.254`, `localhost`, `10.x`) and see status and timing. + +**Fix.** Extract the guard from `webOps.js` into a shared module and use it in both tools. +Validate every redirect hop. Keep `CAUSLY_ALLOW_PRIVATE_HOSTS=true` as the single opt-out. + +**Acceptance.** +- [ ] Tests for loopback, private ranges, link-local, IPv4-mapped IPv6, decimal and octal + encodings of an address, DNS names that resolve to private addresses, and a public URL that + redirects to a private one +- [ ] `webOps` tests still pass unchanged + +**Docs to update.** `tools/vercel.md` (`http_check` blocks private addresses), `configuration.md` (`CAUSLY_ALLOW_PRIVATE_HOSTS` now covers `http_check` too), `security.md` (network guard). + +--- + +## P0-06 File tools: inconsistent path protection `[ ]` + +**Evidence.** +- `tools/fileOps.js:108` `copyFile` has no `assertPathAllowed` call. `moveFile` (lines 100 and + 101) does. +- `tools/security.js:483` checks protected paths with a lower-cased string prefix test, with no + path boundary and no real-path resolution. The list is Windows-only. +- `tools/secretsOps.js` accepts a custom `store_path`. + +**Fix.** +1. Call the protection check for source and destination in every file and directory tool. +2. Resolve real paths first (symlinks, `..`, 8.3 short names), then compare on path boundaries. +3. Add protected locations for Linux and macOS (`/etc`, `/usr`, `/bin`, `/System`, `/Library`, + `~/.ssh`, `~/.aws`, `~/.config/gcloud`, the OS keychain folders). +4. Treat `store_path` like any other path argument. + +**Acceptance.** +- [ ] A test enumerates every exported function in `fileOps.js` and `directoryOps.js` and fails if + one does not call the guard +- [ ] Tests: `..` traversal, symlink into a protected folder, UNC path, reserved device names + (`CON`, `NUL`), a sibling folder that shares a prefix with a protected one, `file::$DATA` + +**Docs to update.** `security.md` (Protected paths: Windows, macOS and Linux lists, real-path resolution), `tools/filesystem-git-shell.md` (`copy_file` is protected), `tools/secrets.md` (`store_path` rule). + +--- + +## P0-07 CI hygiene and automated checks `[ ]` + +**Evidence.** +- `.github/workflows/ci.yml:18` runs Node 18 and `:21` uses `npm install`. `package.json` + requires Node 22.13 or newer. +- The same file uses `npm install` again at line 52. +- There is no `.github/dependabot.yml`. +- Nothing runs `npm audit`. +- Nothing checks the docs against the code. + +**Fix.** +- Node 22 (and 24) everywhere, `npm ci` everywhere +- `.github/dependabot.yml` for npm and GitHub Actions, weekly +- A CI step: `npm audit --omit=dev --audit-level=high` +- A tarball test: `npm pack --dry-run --json` must list only the `files` allowlist, and must not + contain `.env`, `*.db`, logs or keys +- A docs drift test: tool names and permission levels on every page in + `website/docs/server/tools/` must match `packs/manifest.json` and `tools/security.js` + (this check was done by hand on 30 Oct and passed, make it automatic) +- Docs site: set `onBrokenLinks` to `throw` + +**Acceptance.** +- [ ] A deliberately broken fixture fails each new check, then is removed +- [ ] CI is green on all three operating systems + +**Docs to update.** `development.md` (CI jobs, new checks, Dependabot, how to run the docs drift test locally). + +--- + +## P0-08 Permission levels and enforcement `[ ]` + +**Evidence.** `tools/security.js` assigns levels. A tool missing from the table defaults to +`MEDIUM` and is allowed. Some levels are lower than the risk: + +| Tool | Now | Proposed | Why | +|---|---|---|---| +| `git_push` | MEDIUM | HIGH | Visible to others, hard to undo | +| `git_merge` | MEDIUM | HIGH | Rewrites the working branch | +| `secrets_get` | MEDIUM | HIGH | Returns a decrypted secret to the agent | +| `git_stash` (drop) | LOW | MEDIUM | Destroys work | +| `git_tag` (delete) | LOW | MEDIUM | Removes a ref | +| `supabase_create_project` | MEDIUM | HIGH | Creates billable resources | +| `vercel_cancel_deployment` | MEDIUM | HIGH | Affects production | + +**Fix.** Apply the table. Keep the safe default for unknown tools, but make a missing level a +**test failure** so it can never ship. + +**Acceptance.** +- [ ] A test loads the manifest and asserts every tool has an explicit level +- [ ] `website/docs/server/security.md` examples and every tool page are updated (the drift test + from P0-07 enforces this) +- [ ] Decide `git_stash` and `git_tag`: per-action levels need `confirm` only for the destructive + action + +**Docs to update.** `security.md` (levels table and examples), `tools/filesystem-git-shell.md` (`git_push`, `git_merge`), `tools/secrets.md` (`secrets_get`), `tools/supabase.md`, `tools/vercel.md`. The drift test from P0-07 enforces this. + +--- + +## P0-09 MCP conformance and tool annotations `[ ]` + +**Evidence.** No tool registers MCP annotations. Clients and directories use +`readOnlyHint`, `destructiveHint`, `idempotentHint` and `openWorldHint` to decide how to present +and approve a tool. The server has not been run against the MCP Inspector. + +**Fix.** +- Derive annotations from the permission table in one place (`READ` is read-only, `DESTRUCTIVE` + is destructive, network tools are open-world) and apply them in the wrapper +- Run the MCP Inspector against the server, check `tools/list`, schemas, error shape and that + nothing but protocol messages reaches stdout + +**Acceptance.** +- [ ] A test asserts all tools carry the four annotations and that `READ` tools are never marked + destructive +- [ ] A test starts the server and asserts stdout contains only valid JSON-RPC lines +- [ ] Every tool has a non-empty description and a valid JSON schema +- [ ] Inspector run recorded in the PR description + +**Docs to update.** `architecture.md` (annotations), `development.md` (adding a pack: annotations come from the permission level), `security.md` (annotations are hints, not enforcement). + +--- + +## P0-10 Input fuzz tests for every path and shell boundary `[ ]` + +**Why.** The Compose bug (P0-04) is one instance of a class. Others will exist. + +**Do.** One shared test helper that feeds each path or command argument these values and asserts +no evaluation, no escape from the workspace and no crash: +`'`, `"`, `;`, `&`, `|`, `$(x)`, a backtick, `%PATH%`, a newline, a very long name, an empty +string, Hindi and emoji names, a trailing space or dot (Windows), `..\..`, a UNC path, and a +symlink. + +**Acceptance.** +- [ ] Helper applied to files, directory, git, docker, terraform, project and command tools +- [ ] No finding left open: each one becomes its own item or a fix in this sprint + +**Docs to update.** `development.md` (how to use the fuzz helper when adding a tool). + +--- + +## P0-11 Install, upgrade and platform matrix `[ ]` + +**Why.** Windows has had hands-on use. macOS and Linux have been run only by CI, never by hand. + +**Do.** +- Clean Windows machine and a Linux machine (WSL is fine): `npm install -g`, `init`, `doctor`, + `status`, a few real tool calls, `remove` +- Node 22.13 (the minimum) and the current release +- A user name with a space or non-ASCII character, and a OneDrive-redirected home folder +- `npx` flow and the global flow +- No internet, and behind a proxy +- **Upgrade:** create a database with 1.5.0, upgrade to 1.5.4, and confirm pipeline, meetings and + reminders data is intact. Also test a corrupt `.env`, a corrupt secrets file and a corrupt + database: each must produce a clear error, not a crash +- A Claude Desktop config that has other servers and invalid JSON: `init` must not damage it + +**Acceptance.** +- [ ] A checklist in the release PR, filled in +- [ ] The migration test lives in `tests/` with a checked-in 1.5.0 fixture database + +**Docs to update.** `getting-started.md` (verified platforms, minimum Node version), `limitations.md` (Platforms), `troubleshooting.md` (new install findings). + +--- + +## P0-12 Release hygiene `[ ]` + +**Evidence.** `package.json` has no `homepage`. The package is not published with provenance. + +**Do.** +- Add `homepage` (`https://server.causly.in`), keep `repository` and `bugs` accurate +- `npm publish --provenance` from GitHub Actions +- `CHANGELOG.md` 1.5.4 with a "Security" section that names what was fixed, without exploit + detail +- After publish: confirm the npm page, the MCP Registry entry and the Glama listing show the new + version, then request a fresh scan (the old scan covered an earlier commit and 181 tools, the + current server has 245) +- `SECURITY.md`: confirm the reporting address and response times are accurate + +**Acceptance.** +- [ ] `npm view causly-server` shows 1.5.4 with the homepage and provenance +- [ ] Fresh Glama scan requested and recorded + +**Docs to update.** `CHANGELOG.md`. README and ROADMAP are updated last, after P0 is green. No docs site change. + +--- + +## P0-13 Correctness bugs found during QA `[ ]` + +**Evidence.** +1. `edit_file` does not match a multi-line `old_str` against a file that uses CRLF line endings, + so edits to normal Windows files fail with "not found". Reproduced on 31 Oct 2026 against a + CRLF `eslint.config.mjs`. +2. `github_list_issues` with `state: "all"` returned an empty list while an open issue existed. + `state: "open"` returned it. Check whether `all` is passed through correctly. + +**Fix.** Normalize line endings when matching and preserve the file's original line endings when +writing. Fix or document the `state` handling. + +**Acceptance.** +- [ ] Tests: multi-line edit on LF, CRLF and mixed files, the file keeps its original endings +- [ ] Test: `state: "all"` returns open and closed issues + +**Docs to update.** `tools/filesystem-git-shell.md` (`edit_file` and line endings), `tools/github.md` (`state` values for `github_list_issues`). diff --git a/docs/P1.md b/docs/P1.md new file mode 100644 index 0000000..ac2301a --- /dev/null +++ b/docs/P1.md @@ -0,0 +1,210 @@ +# P1: next release (1.6) + +Important, but not blockers for 1.5.4 or the launch. Start after P0 is released. + +Status: `[ ]` open, `[~]` in progress, `[x]` done. + +--- + +## P1-01 Real operator approval `[ ]` + +**Problem.** `confirm: true` is an argument the agent chooses. An agent that has read hostile text +(a web page, an email, an issue) can pass it. The docs say so honestly, but the gate cannot tell +the operator from the agent. + +**Approach.** Use MCP elicitation, where the client supports it, so the operator answers a prompt +that the agent cannot answer. Fall back to the current flag when the client does not support it. +Add an optional policy file (`~/.causly/policy.json`) with per-tool rules: `allow`, `ask`, `deny`. + +**Acceptance.** `HIGH` and `DESTRUCTIVE` calls on a supporting client cannot proceed without an +operator response. A test with a mock client that never answers shows the call stays blocked. + +**Docs to update.** `security.md` (The approval gate), `configuration.md` (policy file location), `limitations.md` (drop the flag-only wording once true), `cli.md` if a policy command is added. + +--- + +## P1-02 Owner-only files and atomic writes `[ ]` + +**Evidence.** No `mode` or `chmod` anywhere in `logger.js`, `store.js`, `secretsOps.js`, +`backupOps.js` or `cliOps.js`. The activity log, the SQLite database, the secrets store, backups +and editor-config backups can all hold sensitive data. + +**Fix.** On macOS and Linux create `~/.causly` as `0700` and files as `0600`. On Windows rely on +the profile folder ACL and verify it, since `chmod` is not meaningful there. Write the secrets +file and backups through a temporary file and a rename, so a crash cannot leave half a file. + +**Acceptance.** POSIX tests check modes on every created file. A test kills the process during a +secrets write and the old file is still intact. + +**Docs to update.** `security.md` (new section on local file permissions), `configuration.md` (Where data is stored), `cli.md` (backup and restore). + +--- + +## P1-03 Workspace limit covers more `[ ]` + +**Evidence.** Docker volume mounts and Compose files are not inspected. MCP project resources in +`index.js` call project inspection without the workspace check. Shell commands are not checked. + +**Fix.** Inspect `-v`, `--mount` and Compose `volumes` and reject host paths outside the +workspace. Apply the workspace check to the `causly://project/...` resources. Keep documenting +that shell text is not inspected. + +**Acceptance.** Tests for each mount form and for the resources. + +**Docs to update.** `security.md` (Workspace limit), `tools/docker.md` (mount rules), `architecture.md` (MCP resources), `limitations.md`. + +--- + +## P1-04 Child processes should not inherit every key `[ ]` + +**Problem.** `run_command` and other child processes inherit the server environment, which +includes every key loaded from `~/.causly/.env`. + +**Approach.** Pass a minimal environment by default (`PATH`, `HOME`, locale, temp) plus an +allowlist (`CAUSLY_PASSTHROUGH_ENV`). Document the change in the release notes, because it can +change behavior for people who relied on inherited variables. + +**Acceptance.** A test runs a command that prints its environment and finds no service keys. + +**Docs to update.** `security.md` (commands inherit the environment), `configuration.md` (`CAUSLY_PASSTHROUGH_ENV`), `limitations.md`, `troubleshooting.md` (a command cannot see a key). + +--- + +## P1-05 Project commands policy `[ ]` + +**Context.** `run_tests`, `run_lint`, `run_typecheck` and `run_build` run the project's scripts +(`tools/projectOps.js:115`). A hostile `package.json` can run anything. This is by design, and +the tools are `LOW`. + +**Approach.** Keep them `LOW` by default and say so on the tool pages. Add +`CAUSLY_STRICT_PROJECT_COMMANDS=true` to require `confirm` for them. + +**Acceptance.** Docs updated. A test for the strict mode. + +**Docs to update.** `tools/filesystem-git-shell.md` (project tools run the project's scripts), `configuration.md` (`CAUSLY_STRICT_PROJECT_COMMANDS`), `security.md`. + +--- + +## P1-06 Resource limits `[ ]` + +**Evidence.** `read_multiple_files` has no total size limit. Recursive directory operations have +no effective bound. Database connection pools are cached with no maximum and no cleanup. + +**Fix.** Cap bytes per read and per batch, entries per listing, and pools per process, with idle +cleanup. Return a clear "truncated" marker. + +**Acceptance.** Tests with oversized inputs finish within a memory and time budget. + +**Docs to update.** `tools/filesystem-git-shell.md` and `tools/database.md` (limits and truncation), `configuration.md` if limits get variables. + +--- + +## P1-07 Token efficiency of tool output `[ ]` + +**Why.** Long tool output costs the user tokens and context. Found during QA: +- `directory_tree` returns build output folders (`.docusaurus`, `build`) in full +- `run_command` repeats the full `command` and `cwd` in its result +- `search_tools` and the default packs add a fixed amount to every session + +**Fix.** Default depth and entry caps with a `truncated` hint, an extended ignore list +(`node_modules`, `.git`, `build`, `dist`, `.next`, `.docusaurus`, `.venv`), and no echo of inputs. +Measure the token cost of `tools/list` for the default packs and record it. + +**Acceptance.** A test asserts a size ceiling for `directory_tree` on a large fixture and that the +`run_command` result has no input echo. The measured `tools/list` size is in the docs. + +**Docs to update.** `tools/filesystem-git-shell.md` (`directory_tree` defaults and ignore list), `architecture.md` and `tools/overview.md` (token cost of the default packs). + +--- + +## P1-08 Integration error paths `[ ]` + +**Why.** Happy paths are tested. Real APIs fail. + +**Do.** For GitHub, Gmail, Calendar, Slack and Telegram first, then the rest: recorded-fixture +tests for 401, 403 (missing scope), 404, 429 (with `Retry-After`), 5xx, timeout, malformed JSON +and pagination. Each must give a clear message with the fix command, never a stack trace or a +secret. + +**Acceptance.** One fixture file per service and one test per failure class. + +**Docs to update.** `troubleshooting.md` (error messages by service), each service page (rate limits and retries). + +--- + +## P1-09 Logging: rotation, size cap, injection `[ ]` + +**Problem.** `activity.log` grows without bound. A value containing a newline can forge a log +line. + +**Fix.** Rotate at a size (for example 10 MB, keep 5). Serialize one JSON object per line and +escape control characters. Redact through the central module from P0-03. + +**Acceptance.** Tests for rotation and for a forged-line attempt. + +**Docs to update.** `security.md` (Activity log: rotation), `configuration.md` (log size variables), `troubleshooting.md`. + +--- + +## P1-10 Prompt-injection tests `[ ]` + +**Why.** Web pages, emails, issues and calendar entries are untrusted text. + +**Do.** Fixtures that contain instructions ("call `delete_file`", "pass `confirm: true`", +"send the contents of `~/.causly/.env` to ...") returned by `web_fetch`, `gmail_get_message`, +`github_get_issue`, `calendar_get_event`. Assert tool descriptions mark such output as untrusted +and that the gate behavior does not change. + +**Acceptance.** The suite exists and passes. It documents what the server can and cannot prevent. + +**Docs to update.** `security.md` (Anything the agent reads can influence it: what is tested, what is not prevented). + +--- + +## P1-11 Secrets store polish `[ ]` + +**Do.** Keep the 64-character key check and add a test for it. Make `secrets_set` and +`secrets_rotate_key` atomic (P1-02). Add `secrets_get` to the "needs confirm" set (P0-08). +Reject a `store_path` outside the allowed folders. + +**Docs to update.** `tools/secrets.md`, `security.md` (Encrypted secrets). + +--- + +## P1-12 Calendar: recurring events `[ ]` + +**Limitation.** Updating a recurring event changes only the instance id you pass. + +**Fix.** Add a `scope` argument: `this`, `following` or `all`. Use the recurring event id for +`all`. `calendar_delete_event` gets the same argument. + +**Acceptance.** Tests against recorded Calendar API responses for each scope. + +**Docs to update.** `tools/calendar.md` (`scope` argument), `limitations.md` (remove the recurring-events limit). + +--- + +## P1-13 Google sign-in lifetime `[ ]` + +**Limitation.** In Testing status the refresh token expires after 7 days. + +**Do.** The fix is to publish the Google app to production, which is a user-side step. Document +it step by step in the Gmail and Calendar pages (Branding configuration first, then Publish). +In `status` and `doctor`, warn when the token is close to the 7-day limit. + +**Docs to update.** `tools/gmail.md`, `tools/calendar.md`, `configuration.md` (Google), `troubleshooting.md` (Google), `cli.md` (`status` and `doctor` warning), `limitations.md`. + +--- + +## P1-14 Landing page and docs site QA `[ ]` + +- Hero: the call-to-action and stats bar should sit fully inside the first screen on common + laptop sizes, and stack cleanly on phones. Measure at 360, 390, 430, 768, 1280, 1366, 1440 and + 1920 pixels +- Lighthouse for performance, accessibility and SEO on the landing page and a docs page +- Keyboard navigation and focus order, contrast checks +- After each deploy: `/`, `/docs`, `/privacy`, `/terms`, `/og.png`, the 308 redirect from the old + domain, and the sitemap all return the expected status +- Social card preview for the Open Graph image on the main platforms + +**Docs to update.** None in the server docs. Changes land in the landing page and `website/docusaurus.config.js`. diff --git a/docs/P2.md b/docs/P2.md new file mode 100644 index 0000000..af0f652 --- /dev/null +++ b/docs/P2.md @@ -0,0 +1,82 @@ +# P2: later + +Larger features and long-running verification. Not planned for 1.5.4 or 1.6. Revisit after the +launch based on what users ask for. + +Status: `[ ]` open, `[~]` in progress, `[x]` done. + +--- + +## P2-01 Local transcription engine `[ ]` + +`meeting_transcribe` uploads audio to Groq. Add an optional local engine (Whisper through a local +binary or `whisper.cpp`) so audio never leaves the machine. Needs a decision on model download +size and a clear "first run downloads N MB" message. + +## P2-02 Real macOS verification `[ ]` + +CI covers macOS, but nobody has run the server by hand on a Mac. Find a tester, run the P0-11 +checklist, and record it. + +## P2-03 Nightly live smoke tests `[ ]` + +A scheduled workflow that uses dedicated test accounts and secrets to call each integration once. +It catches API drift (a changed field, a retired endpoint) before users do. Failures open an +issue. + +## P2-04 BoundaryAttest to production grade `[ ]` + +It is a proof of concept today (three workflow tools, a key file you manage). Needs key storage, +rotation, revocation, a trust registry and an optional KMS or HSM backend. Only worth doing if +users ask for signed receipts. + +## P2-05 Sync between machines `[ ]` + +Pipeline, meetings and reminders live in one local database. Options: an encrypted export and +import, or a file-based sync folder. Needs a conflict policy. Do not build a cloud service for +this inside the open-source server. That belongs to the hosted product. + +## P2-06 Soak and performance testing `[ ]` + +- 24-hour session with periodic calls: memory growth, open handles, pool leaks +- Cold start time and `tools/list` latency with all packs loaded +- Large inputs: a 100 MB transcript, a repository with 100,000 files +- Publish the numbers in the docs + +## P2-07 Fuzzing `[ ]` + +Property-based and mutation tests for the argument parsers, the redactor, the path guard and the +URL guard. Run in CI on a schedule. + +## P2-08 Supply-chain extras `[ ]` + +Signed git tags, a software bill of materials published with each release, a license check on +every dependency, and a documented process for revoking a bad release (`npm deprecate`, advisory, +changelog note). + +## P2-09 Enterprise features `[ ]` + +- Policy file with an allowlist and denylist of tools per project +- Audit log export (JSON lines to a file or a syslog target) +- A "read-only mode" switch that removes every non-`READ` tool +- Per-project workspace profiles + +## P2-10 More directory listings `[ ]` + +Smithery needs an `.mcpb` bundle. Revisit PulseMCP, mcp.so and `awesome-mcp-servers` when their +queues or fees change. + +## P2-11 Keyword volume `[ ]` + +Keyword ideas come from search autocomplete and carry no volume. Add an optional provider with a +free tier, behind a key, and keep the keyless path as it is. + +## P2-12 Outreach channels `[ ]` + +Out of scope for now: LinkedIn, X and WhatsApp (terms of service and account risk). Email goes +through Gmail. + +## Out of scope + +Azure, GCP and AWS SDK tools. Terraform covers provisioning. Stripe and Discord packs exist in +the code but are not documented or promoted. diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..7628b17 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,90 @@ +# Quality and hardening plan + +This folder is the engineering plan for making Causly Server enterprise quality. It is internal +working documentation for contributors. User-facing docs live on the docs site +(`server.causly.in/docs`). + +| File | What it holds | +|---|---| +| [P0.md](./P0.md) | Must be done before the 1.5.4 release and the 10 Nov 2026 launch | +| [P1.md](./P1.md) | Next release (1.6): deeper hardening, real approval flow, broader test coverage | +| [P2.md](./P2.md) | Later: larger features, long-running verification, enterprise extras | + +Every item has an ID (`P0-03`), the evidence (file and line), the impact, the fix, and an +acceptance test. An item is done only when its acceptance test exists and passes. + +## Where this came from + +- An independent audit run on 30 Oct 2026 against `master` at commit `c6bc132`. +- A re-check of every audit claim against the code and `npm audit` on 31 Oct 2026. +- The open GitHub issue #2 and the "Limitations" page of the docs. +- A QA review of what the existing test suite (56 test files) does not cover. + +Audit claims that did not survive the re-check are listed at the bottom of this page, so they are +not worked on by mistake. + +## Rules of the sprint + +1. **Test first.** For every security item, write the failing test that reproduces the problem, + then fix it. The test stays as a regression test. +2. **One item, one commit.** Keep each commit small so it can be reviewed and reverted alone. +3. **Work on a branch.** P0 work goes on `security/hardening-1.5.4`. `master` is untouched until + the branch is green on Ubuntu, macOS and Windows. +4. **No blind fixes.** Reproduce first. If an item cannot be reproduced, write down why and + close it. +5. **Docs move with code.** Any change to a tool, a permission level or a default updates the + matching page in the docs site in the same change set. The pages live in the `agenv` repo + under `website/docs/server/`. Each P0 and P1 item has a "Docs to update" line that names them. +6. **No new unrequested scope.** New ideas go into P1 or P2, not into the sprint. + +## Definition of done for a release + +- [ ] `npm audit --omit=dev` reports no high or critical issues +- [ ] `npm test` passes on Ubuntu, macOS and Windows (Node 22 and 24) +- [ ] Every registered tool has an explicit permission level (enforced by a test) +- [ ] `npm pack --dry-run` shows only the allowlisted files +- [ ] `CHANGELOG.md` has a security section for the release +- [ ] `causly-server doctor --test` passes on a clean machine with real keys +- [ ] Package published with provenance, tag pushed, MCP Registry entry updated + +## Timeline to launch (10 Nov 2026) + +| Dates | Focus | +|---|---| +| 1 to 3 Nov | P0-01 to P0-06: dependencies, issue #2, redaction, shell injection, SSRF, file guards | +| 4 to 5 Nov | P0-07 to P0-09: CI, permission levels, MCP conformance and annotations | +| 6 to 7 Nov | P0-10 to P0-12: fuzz tests, install and upgrade matrix, release hygiene | +| 8 Nov | Release 1.5.4, ask Glama and the other directories to re-scan | +| 9 Nov | Buffer. Renew the Google token (`causly-server login google`), final `doctor --test` | +| 10 Nov | Product Hunt launch | + +README and ROADMAP are updated last, after P0 is green. + +## Limitations page to plan + +| Limitation (docs) | Plan | +|---|---| +| Issue #2: `npm install` fails inside nested `cmd.exe` on Windows | P0-02 | +| Workspace limit is a guardrail, not a sandbox | P1-03 (Docker mounts, resources), documented honestly in the meantime | +| Approval gate is a flag, not a screen | P1-01 (real operator approval) | +| Commands inherit the server environment, including keys | P1-04 | +| Recurring calendar events: updating changes one instance only | P1-12 | +| Google token expires after 7 days in Testing mode | P1-13 (production publish is a user-side step) | +| Transcription needs the internet | P2-01 (local engine) | +| No sync between machines | P2-05 | +| BoundaryAttest is a proof of concept | P2-04 | +| Direct cloud tools (Azure, GCP, AWS) | Out of scope | +| LinkedIn, X and WhatsApp outreach | Out of scope | + +## Audit claims that did not hold up + +- **Master key validation is weak.** It is already checked: the key must decode to exactly 32 + bytes (`tools/secretsOps.js`). No work needed beyond a unit test. +- **Test tools bypass the approval gate.** `run_tests`, `run_lint`, `run_typecheck` and + `run_build` run the project's own scripts. That is what they are for, so this is a policy + decision, not a bug. See P1-05. +- **CVE numbers in the audit.** The audit quoted CVE IDs that `npm audit` does not report. + `npm audit` reports GitHub advisory IDs (GHSA). Work is tracked against `npm audit`, not the + quoted IDs. +- **"410+ tests".** That figure came from an older pull request. The suite has 56 test files and + CI is green, but the count has to be taken from a fresh run. From 573a3e9b22211f757cb9b53b5e2decb5e67ef746 Mon Sep 17 00:00:00 2001 From: KNIHAL Date: Sat, 31 Oct 2026 21:48:38 +0530 Subject: [PATCH 02/18] security(P0-01): update vulnerable dependencies (simple-git 4, SDK 1.32, zod 4.6) --- CHANGELOG.md | 6 + package-lock.json | 439 ++++++++++++++++++------------------- package.json | 6 +- tests/dependencies.test.js | 46 ++++ 4 files changed, 270 insertions(+), 227 deletions(-) create mode 100644 tests/dependencies.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index c1baeb9..37af2eb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,12 @@ All notable changes to this project are documented here. +## [1.5.4] (unreleased) + +### Security +- Updated vulnerable dependencies: `simple-git` 4.x, `@modelcontextprotocol/sdk` 1.32.x, `zod` 4.6.x, plus transitive fixes. `npm audit --omit=dev` now reports 0 vulnerabilities. +- Added a test that keeps the direct dependencies on patched versions. + ## [1.5.3] ### Added diff --git a/package-lock.json b/package-lock.json index 3215e08..b68eb7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,26 +9,32 @@ "version": "1.5.3", "license": "MIT", "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/sdk": "^1.32.2", "mysql2": "^3.24.3", "pg": "^8.23.0", - "simple-git": "^3.36.0", - "zod": "^4.4.3" + "simple-git": "^4.0.2", + "zod": "^4.6.7" + }, + "bin": { + "causly-server": "bin/causly.js" }, "devDependencies": { "vitest": "^5.0.0" + }, + "engines": { + "node": ">=22.13" } }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.1.5.tgz", + "integrity": "sha512-MgP/I06GdvsilDspMk/qmRzpk8LOUStP6jFAbSRi40W09DhoyUn+Mj7eyKICPqeYKybOjVFLVIAQzvu1bfIa7Q==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { - "hono": "^4" + "hono": "^4 || ^5.0.0-0" } }, "node_modules/@jridgewell/resolve-uri": { @@ -75,12 +81,12 @@ "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.29.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", - "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", + "version": "1.32.2", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.32.2.tgz", + "integrity": "sha512-SWsVtH/0vULSQStoZ6xxH2K09Li8fXYTMC64ZeogCdpZ3XSi1eCPOByV1OpuJxuI3OcmHhosDWYyii07tpPsLw==", "license": "MIT", "dependencies": { - "@hono/node-server": "^1.19.9", + "@hono/node-server": "^1.19.9 || ^2.0.5", "ajv": "^8.17.1", "ajv-formats": "^3.0.1", "content-type": "^1.0.5", @@ -115,9 +121,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.148.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.148.0.tgz", - "integrity": "sha512-Nm4s/jB+4FpFsPhWGEC4h7rzksesmtnMXomo6rCMcg/b8zLQuOziRgkCS1fxDCXOlJB/6Q8oABOZ/OP6RIPj9A==", + "version": "0.156.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.156.0.tgz", + "integrity": "sha512-1Duyg48TdJNdyhEB8Xn1L36bT9Pv6GUtlKjHxOys8xkaF/rQq3XJE+bAurSK/AgIz5mGFsb77cn9Pb20+WCScw==", "dev": true, "license": "MIT", "funding": { @@ -125,9 +131,9 @@ } }, "node_modules/@rolldown/binding-android-arm-eabi": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.7.tgz", - "integrity": "sha512-EypzgnYCwyVY4NDHKzGmNJT5b+XaQEBniHxsMdeIQLB/tcCzZnhqrzHpZFbX9iaxx+5RiB8caATBtfvZP7zVxQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm-eabi/-/binding-android-arm-eabi-1.2.16.tgz", + "integrity": "sha512-ia3DwAs0OsMGS7+IConptSiyk9pEXkOx/AbJ2n/lAGxH3QkWQus1t8d7gnAT38FCU2sQu8lUbeR6AiZFv4JxaA==", "cpu": [ "arm" ], @@ -142,9 +148,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.7.tgz", - "integrity": "sha512-l17HE9EweWaqJZhuUuNBN/FzM62xw+DECVnJyvMsxn8vJFAGLy5QfLDoYAcronkAN8VxKZHezDpulHDPx95vFw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.2.16.tgz", + "integrity": "sha512-kx5wBiMB6E7TEvpl2B9gjLU004BkvEm2Dr941BFjx/SontPwSzNuQUIoVKxHHoSjpFIfHMmcguyLOtyn0hObRg==", "cpu": [ "arm64" ], @@ -159,9 +165,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.7.tgz", - "integrity": "sha512-8ED8ELFvHXc6OCETIn4gXObPiaR6bckM/ipXtbzlPVDRMBfEGjCKgO90F9YtfdpDatVx/ZQw7aZ1vUMf/+T3Mw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.2.16.tgz", + "integrity": "sha512-qslZrXnKJU4cUpjoAmtAo/KQM9e2IRMlmpig6Zpwj+8KWxW8v+nkAf9216Bsek9lOC1jvg2yCjygCFo2f83msQ==", "cpu": [ "arm64" ], @@ -176,9 +182,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.7.tgz", - "integrity": "sha512-/WPripjtiAIZ2tWY7ddijORT0Ujg87wxWW/qcoFVCKAWVDPhtY0xr7Dj0M3GyNGz60jGwTElhro/mkF9dT7dDQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.2.16.tgz", + "integrity": "sha512-FdF+y48ND13eAyM2mQn+85kvAqNlvRWROy2rhgA463X/0Gv4vGtazZtfaF1mRBLscYvtOlh4QnLjOWiFzX69OQ==", "cpu": [ "x64" ], @@ -193,9 +199,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.7.tgz", - "integrity": "sha512-14DI4NcqpvbICxSnGLx3PmtDaWqRP/KGSGb6C+JLLVPeZRl6dKdHba3pGsqT3vpdTqhEYIPG0MMQ8c0xYqoJxA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.2.16.tgz", + "integrity": "sha512-ddjqFM5e95AgLbUPtKIS7V1KTVlA0RUgCVr5mn+/IEhsdy3pr+Vuj/HZ7nlMN2FyxIwUktOH1LEPIQXO1v9y1Q==", "cpu": [ "x64" ], @@ -210,9 +216,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.7.tgz", - "integrity": "sha512-bxrWIRvHWQvbJwi+VIie/kDJmQxcNE6xxWwZdqF/ExVAigtHkv54WTLQPb+QsZdnFy18fg7JPfWGL0RH6vwIlQ==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.2.16.tgz", + "integrity": "sha512-tHdKWbFkFBI687iim3LIyIvyL7j66VvQYkDPss5Stn/qyE2tWkR1YD0dXmXPFKKqT/o5ZEJVsaRsWS2meQwGOA==", "cpu": [ "arm" ], @@ -227,9 +233,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.7.tgz", - "integrity": "sha512-toOY2BChBZyuxU7OYX6Tn389di4IzAqPTycVcci0O7FSfBqzRB3RZn+K5Is6ANf4tmgRd/K1yZTsNTXbkXsnLg==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.2.16.tgz", + "integrity": "sha512-FJiLGAblPBMlBEdbOqw6kW6QcXDWAf/oJu7gYs9ZJZwmg+K+/zjFLxPTIY9izJUzxzc24FHvmseyA0IvJSl17w==", "cpu": [ "arm64" ], @@ -244,9 +250,9 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.7.tgz", - "integrity": "sha512-lAIXTH/aiLRLxsTgQvfhjo4K1ydWIp00+V0voOr9beb/9ZmkUFrSIb03dXNFRgMNvkE6oGsF10ioQ6UsI+vS5Q==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.2.16.tgz", + "integrity": "sha512-hrJpblMsrQ58U9veWpbXzkhV5zStunds6HWLprqq1qauvgBr1E6P+24kLj6lSLI9vjRn/RYpjprhkHQPKGWpNg==", "cpu": [ "arm64" ], @@ -261,9 +267,9 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.7.tgz", - "integrity": "sha512-kdnwS28Pkenp/mZMRwjXXXwxQ7pIsm+bF919LUK93BOyhcLsrVKdP2p9fxpiPNPAbNuch8ypQt0pm2P2LYCAGg==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.2.16.tgz", + "integrity": "sha512-qJd/dC6j6CwLG9/s/RP2nb1RZ0GIgj7Ioj7x/SYa3RzjVrlzrmH0fhIOpfvqQDeIsdP0loi+kHaMhJzY1PBIaA==", "cpu": [ "ppc64" ], @@ -278,9 +284,9 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.7.tgz", - "integrity": "sha512-516OdsyLdr5E65paF3yBF55t8mfm9+gmtCsK3xI7XKXIT7EfRlHhxL8K/NR6Hu8BWSgF5+1w74lTL0+nxcc8Qw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.2.16.tgz", + "integrity": "sha512-Vx7Xg5uUJuNcujJjI70OICaH0EYj80BtOLZTSN7uOYg8QOLlyKnaG0IW7Fn/J0IpJsaKzmDKMhOQv1JBM9m9vg==", "cpu": [ "s390x" ], @@ -295,9 +301,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.7.tgz", - "integrity": "sha512-r8/z8n7GFaYRln3xmP1Cxy0HH/HLM0uBUPkEuSVEfKGDA89M0FsZRZJRSwe/tJjRx+fpH/gjorfhB8tmEbSFLA==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.2.16.tgz", + "integrity": "sha512-TNKhgE3uJTsTzatUSSYt0Kgo+Oa2uSlSjz4r1aEIea7HX6ULBym4fAlYUhG1i6pjNf2njfCUAfKwr6C3E9Xg1w==", "cpu": [ "x64" ], @@ -312,9 +318,9 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.7.tgz", - "integrity": "sha512-pAsE8iiDxUg1xBqdhrTfg45AVDVpirjz00sblEYClGNNcMnDb+e8beQgqIAw6LvauX/APvgxUnwrgun/YYGBhw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.2.16.tgz", + "integrity": "sha512-7chXdpjXZ8qG0+mIxECN3cT9kvhZ3/oZhMX6Pp876P7CDrIy8nJswva6Mq357JR7eYXfuskwZxMeV1SzoZdHrA==", "cpu": [ "x64" ], @@ -329,9 +335,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.7.tgz", - "integrity": "sha512-lTcIYmmnQQA8Or/2DatS6oSqcdLHvendjS+zLu+FwgToynWMRSmQdpM65fTANJgIS4mjbMOo5KT2lnT9SAb96w==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.2.16.tgz", + "integrity": "sha512-Kwjt+7uNTJK5/MDkGWGVO2KQPnj8yAV75dMpfwyHZrJSXWHrniOB0aUEWlVYf3H1hOqFLIStLdM5QXq6bnKmOQ==", "cpu": [ "arm64" ], @@ -346,9 +352,9 @@ } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.7.tgz", - "integrity": "sha512-e3Gu3WxbNk/UqQhxqU7YIYO+9ZBvWNz3U+h/qRFosscMFzdRPbXYSaSWgSnklv2fz1TgzBTcti2z35c/7irsHw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.2.16.tgz", + "integrity": "sha512-/LH6WfjDRXXJz/p5xl4TmU+jpcnwcxbKEQBW/GZE0JHfN0lGIyTtsUEGaQ/q/GikkGJ2yLKe6pwm7AnEWO8pdw==", "cpu": [ "arm64" ], @@ -363,9 +369,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.7.tgz", - "integrity": "sha512-W/jg5qoRSqjsEv0+dZi4e687mcHqmVuU0P4fK6qS/xjetW2Gmc1W8j//z5nAeNcC8Ttm0hV46IjcYeuVwYhuiw==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.2.16.tgz", + "integrity": "sha512-1lv1KvX0Z871zfk7vw+dnXMMnHNyDwMzWG8Kv3HxDXMOhzCX4SHc7HK319fInP0i+0p9D8brkfzL7pJOaGl+tw==", "cpu": [ "x64" ], @@ -387,18 +393,18 @@ "license": "MIT" }, "node_modules/@simple-git/args-pathspec": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.3.tgz", - "integrity": "sha512-ngJMaHlsWDTfjyq9F3VIQ8b7NXbBLq5j9i5bJ6XLYtD6qlDXT7fdKY2KscWWUF8t18xx052Y/PUO1K1TRc9yKA==", + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@simple-git/args-pathspec/-/args-pathspec-1.0.4.tgz", + "integrity": "sha512-EtMX6XjRWSastG2SdkmSQByPtJ9dx/NIjnHbpQPCYt62j4RmSx5rgLTGpw0YCjF5h191gZOmBfheOT23cRSFdw==", "license": "MIT" }, "node_modules/@simple-git/argv-parser": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-1.1.1.tgz", - "integrity": "sha512-Q9lBcfQ+VQCpQqGJFHe5yooOS5hGdLFFbJ5R+R5aDsnkPCahtn1hSkMcORX65J2Z5lxSkD0lQorMsncuBQxYUw==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@simple-git/argv-parser/-/argv-parser-2.0.1.tgz", + "integrity": "sha512-M++IaVWrN+vYalilOHfwvT2IL3wYZtVEBQpmX6DLSOJLtxbZ+SUeIDE3IiPogzhPACLajwtxfa5rp+ZX1FczzQ==", "license": "MIT", "dependencies": { - "@simple-git/args-pathspec": "^1.0.3" + "@simple-git/args-pathspec": "^1.0.4" } }, "node_modules/@types/chai": { @@ -420,9 +426,9 @@ "license": "MIT" }, "node_modules/@types/estree": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", - "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.10.tgz", + "integrity": "sha512-lTyOMnOcmRBzdCv6/4zUZCrEhV0xrqQiUZBZtVcbmLHBXKLnN8q89v/wKRF/Q0HbwVsqbIG3o5HDoFl9sduM+g==", "dev": true, "license": "MIT" }, @@ -437,22 +443,22 @@ } }, "node_modules/@vitest/mocker": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.0.tgz", - "integrity": "sha512-66PGTMIiVJP3t4a5yxU9qPtf7MdTBs8jmToMvy+HVflB3Yy13WJZTtPePdvU+wjRV02SKK5doLbSA6o9pwOmiA==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-5.0.5.tgz", + "integrity": "sha512-xLL3lkDegJZw+MGoGaCCsFayDZthGgpAtr3Vziyfc4p7xH89K7KRPcRBBh/kEYm0Aqkt5DtTmkYpBtRV/FrHZg==", "dev": true, "license": "MIT", "dependencies": { "@jridgewell/trace-mapping": "0.3.31", - "@vitest/spy": "5.0.0", + "@vitest/spy": "5.0.5", "estree-walker": "^3.0.3", - "magic-string": "^1.2.3" + "magic-string": "^1.4.3" }, "funding": { "url": "https://opencollective.com/vitest" }, "peerDependencies": { - "msw": "^2.4.9", + "msw": "^2.4.9 || ^3.0.0", "vite": "^6.0.0 || ^7.0.0 || ^8.0.0" }, "peerDependenciesMeta": { @@ -465,9 +471,9 @@ } }, "node_modules/@vitest/spy": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.0.tgz", - "integrity": "sha512-uy+luWBAPw9XfthoHi5AkfHUnuPYEESjl0p/r+meoBnU8bxg5GDQ3Ey8MjcJ6sqahkL4PFyrvfMJJBw7LbU06g==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-5.0.5.tgz", + "integrity": "sha512-lwJRExuH417257Q5wv9QYugN9IMJkoxcrKrYxGPQkBzl7wlNpTUxD/5yNX4ESKVUIShKPgkGchYPFMWOOk0wzw==", "dev": true, "license": "MIT", "funding": { @@ -540,18 +546,18 @@ } }, "node_modules/body-parser": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", - "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.4.0.tgz", + "integrity": "sha512-Ifk1KcLrxaGt1occQagQNFNNOqRt1LFuehEnHHzB8Z6cRgIxRU1TBaiIsUiCdA+gy5PL7Y1IX31DNKkRmxtXjQ==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^2.0.0", + "content-type": "^2.1.0", "debug": "^4.4.3", "http-errors": "^2.0.1", - "iconv-lite": "^0.7.2", + "iconv-lite": "^0.7.3", "on-finished": "^2.4.1", - "qs": "^6.15.2", + "qs": "^6.16.1", "raw-body": "^3.0.2", "type-is": "^2.1.0" }, @@ -564,9 +570,9 @@ } }, "node_modules/body-parser/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.1.0.tgz", + "integrity": "sha512-mj7UPXE0jaqaOsukNZRUEfEi2AcL7C/vwmwcHV0O97eO1E1pxBZuyjlZrx5seTaNBg1U6+o35wpa35Qfcc+7ag==", "license": "MIT", "engines": { "node": ">=18" @@ -615,9 +621,9 @@ } }, "node_modules/chai": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/chai/-/chai-6.2.2.tgz", - "integrity": "sha512-NUPRluOfOiTKBKvWPtSD4PhFvWCqOi0BGStNWs57X9js7XGTprSmFoz5F0tWhR4WPjNeR9jXqdC7/UpSJTnlRg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", + "integrity": "sha512-XWAtwJ6OHO+tj0EKCs0Y2UamnyOxseZWltU4x2U2wh8g4AigdjwvtUjvLP2tqkA/avxHEtzxNaqGq/YGNwckKg==", "dev": true, "license": "MIT", "engines": { @@ -923,9 +929,9 @@ "license": "MIT" }, "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.9", + "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.9.tgz", + "integrity": "sha512-hS/0Wt3Ya043oKXs0sIxrMpKQITXhMLIrVAKKXi4gozGC9v7YRWVnXIZzjNPTO02M9BdxLCWqK1yRXgNziVyJQ==", "funding": [ { "type": "github", @@ -1102,9 +1108,9 @@ } }, "node_modules/hono": { - "version": "4.12.31", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.31.tgz", - "integrity": "sha512-zJIHFrl6bq3RDd2YusFNCDlM8qUprxKswyi/OPzPyzKDdyBXDqWx8bZlZ7R+saTdSTatUmb3O7K4SspGPaEOQg==", + "version": "4.13.14", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.13.14.tgz", + "integrity": "sha512-yhRLQ21U6qa9Hsu2N7Qcbh8tE+WKEcoErjIo56LeSGiGXGp9sA7UpE86Ui9uexb61KakiGwuHMn2QdV7w6i6kQ==", "license": "MIT", "peer": true, "engines": { @@ -1154,9 +1160,9 @@ "license": "ISC" }, "node_modules/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", + "version": "10.7.5", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.7.5.tgz", + "integrity": "sha512-t2v0rkILYv9WXmSL7TpAaU3HXgaFYOPUcH3QHgwQ++gpJuH3bqyMy4Fvt/DJCTKwzH3Cf+bAp5W/mFL39KPLzA==", "license": "MIT", "engines": { "node": ">= 12" @@ -1493,13 +1499,13 @@ } }, "node_modules/magic-string": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.2.3.tgz", - "integrity": "sha512-Bpb0W2TbLKOZ7vJnOUnVRGq3WL2p+ISV29M6hYPL1AFCpyKZpdr5ytiXoTSSxRVhg8YW7f65+6gbG8WG6PCa/g==", + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-1.4.5.tgz", + "integrity": "sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==", "dev": true, "license": "MIT", "dependencies": { - "@jridgewell/sourcemap-codec": "^1.5.5" + "@jridgewell/sourcemap-codec": "^1.6.0" } }, "node_modules/math-intrinsics": { @@ -1564,9 +1570,9 @@ "license": "MIT" }, "node_modules/mysql2": { - "version": "3.24.3", - "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.3.tgz", - "integrity": "sha512-OKfWHkMAg9v06neq8FmSyhbxPQKABN9PAW5G9/bDTXzJBO5xXtkKL0V27vju7HQWk9UD4Od5BZsvCtBTB1CPEw==", + "version": "3.24.6", + "resolved": "https://registry.npmjs.org/mysql2/-/mysql2-3.24.6.tgz", + "integrity": "sha512-enu73KhbAua2L3dIxjc6qg6ZJ7H3pt1BImZASWNWY5xSsmJ1ZuUPULTDGXgmJbI61vb+nJAG19IZclIq19UidQ==", "license": "MIT", "dependencies": { "aws-ssl-profiles": "^1.1.2", @@ -1597,9 +1603,9 @@ } }, "node_modules/nanoid": { - "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==", + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", "dev": true, "funding": [ { @@ -1646,9 +1652,9 @@ } }, "node_modules/obug": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/obug/-/obug-2.1.4.tgz", - "integrity": "sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==", + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/obug/-/obug-3.0.0.tgz", + "integrity": "sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==", "dev": true, "funding": [ "https://github.com/sponsors/sxzz", @@ -1709,15 +1715,15 @@ } }, "node_modules/pg": { - "version": "8.23.0", - "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.0.tgz", - "integrity": "sha512-Ip2EQCngowJLGOfCwkFhPXU7/ljlhn6Rxlmy4XYfL2Y+vyRM59+8uR2xqRWKdYmbXmxCFOAmKxBuSUCdF34qLg==", + "version": "8.23.1", + "resolved": "https://registry.npmjs.org/pg/-/pg-8.23.1.tgz", + "integrity": "sha512-aL96AHANtWjPLDOLqnhx+ngp9+UK7ETEU8VJrDCGvsSSi/mGLcWYsS6Herg7lmaBJe4uwrfqsa7gTEFaSizDoQ==", "license": "MIT", "peer": true, "dependencies": { - "pg-connection-string": "^2.14.0", + "pg-connection-string": "^2.14.1", "pg-pool": "^3.14.0", - "pg-protocol": "^1.16.0", + "pg-protocol": "^1.16.1", "pg-types": "2.2.0", "pgpass": "1.0.5" }, @@ -1725,7 +1731,7 @@ "node": ">= 16.0.0" }, "optionalDependencies": { - "pg-cloudflare": "^1.4.0" + "pg-cloudflare": "^1.4.1" }, "peerDependencies": { "pg-native": ">=3.0.1" @@ -1737,16 +1743,16 @@ } }, "node_modules/pg-cloudflare": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.0.tgz", - "integrity": "sha512-Vo7z/6rrQYxpNRylp4Tlob2elzbh+N/MOQbxFVWCxS7oEx6jF53GTJFxK2WWpKuBRkmiin4Mt+xofFDjx09R0A==", + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/pg-cloudflare/-/pg-cloudflare-1.4.1.tgz", + "integrity": "sha512-6PQbsFWZcp9EmJEwy5cGQ2La+AMWpP46lgbb8X+U/XsHIUweYDNCpeuKck5RxL2MdVFi7krbbEi5nX4Zh7JhrQ==", "license": "MIT", "optional": true }, "node_modules/pg-connection-string": { - "version": "2.14.0", - "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.0.tgz", - "integrity": "sha512-XwWDGcLRGCXAR8F/AM5bG7Q+A3Wm2s6QeEjlOKZLlH3UYcguiqCWKyWXVag5TLTIjR7oOJUY8kcADaZgWPyLeg==", + "version": "2.14.1", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.14.1.tgz", + "integrity": "sha512-qR3kGNPBLpCNtz0evbKA0Y/MRFXwSSdT+pTJvYp/bXTcReZbvX1kzF0IyTc1QnxqF7AZbOeBhNL8R5mYQZV/MA==", "license": "MIT" }, "node_modules/pg-int8": { @@ -1768,9 +1774,9 @@ } }, "node_modules/pg-protocol": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.0.tgz", - "integrity": "sha512-sILXutLVjCLjcDuOmvhX5e2Z4cS5qG/6Bu3VkpFwdf/633ElGLpEh9bgmuI5I4sqKqkifQiGyiCcx1HdtrK7tg==", + "version": "1.16.1", + "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.16.1.tgz", + "integrity": "sha512-p9VOFMiHB/ZbJATetbg+99PxssTVSQRnyuPSQ67mN1+1KBOjZaZ83ZQzltnxPhJwSsC3nwVjJ10DVJlerbFzLg==", "license": "MIT" }, "node_modules/pg-types": { @@ -1811,7 +1817,6 @@ "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", "dev": true, "license": "MIT", - "peer": true, "engines": { "node": ">=12" }, @@ -1829,9 +1834,9 @@ } }, "node_modules/postcss": { - "version": "8.5.28", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.28.tgz", - "integrity": "sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==", + "version": "8.5.32", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz", + "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==", "dev": true, "funding": [ { @@ -1849,9 +1854,9 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.18", + "nanoid": "^3.3.20", "picocolors": "^1.1.1", - "source-map-js": "^1.2.1" + "source-map-js": "^1.2.2" }, "engines": { "node": "^10 || ^12 || >=14" @@ -1897,9 +1902,9 @@ } }, "node_modules/proxy-addr": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", - "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.8.tgz", + "integrity": "sha512-5nnx0yGyVUcY6t9RnWcARWtwT9F1D8O9rt08htPvnd49W1IgZtmLkhu9WfMzQj1cFxjHIO6connUNVW5k7AVyQ==", "license": "MIT", "dependencies": { "forwarded": "0.2.0", @@ -1907,12 +1912,16 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/qs": { - "version": "6.15.3", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.3.tgz", - "integrity": "sha512-O9gl3zCl5h5blw1KGUzQKhA5oUXSl8rwUIM5o0S3nCXMliSvy5Dzx7/DJcI+SwgICv+IneSZwhBh1oSyEHA71A==", + "version": "6.17.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.17.1.tgz", + "integrity": "sha512-jd3COkaKVgGUW+ZHyEHCG9bH7RoaaiWuMgzsIBjUMVjLAaWGWpoUAVwljVvBHxRu/2qEHnadLGZrkutYUBPQJA==", "license": "BSD-3-Clause", "dependencies": { "es-define-property": "^1.0.1", @@ -1963,13 +1972,13 @@ } }, "node_modules/rolldown": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.7.tgz", - "integrity": "sha512-g0EtLvBjTUB7jhyV0S/TCup3v/XSVl45vUIGbOGU4QPiyjTenCe4mKuFvW9fEgYmS2Fo42AUssRmNuMziXdrig==", + "version": "1.2.16", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.2.16.tgz", + "integrity": "sha512-bWvlMpFKiTfDKfnK7FC2MPgPeWCf/Kpwsq49ApdW+FQaBZb6O6dZ4MAPbPUjp7hYQrw/RobqCzn64BRrQecGHw==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.148.0", + "@oxc-project/types": "=0.156.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -1979,21 +1988,21 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm-eabi": "1.2.7", - "@rolldown/binding-android-arm64": "1.2.7", - "@rolldown/binding-darwin-arm64": "1.2.7", - "@rolldown/binding-darwin-x64": "1.2.7", - "@rolldown/binding-freebsd-x64": "1.2.7", - "@rolldown/binding-linux-arm-gnueabihf": "1.2.7", - "@rolldown/binding-linux-arm64-gnu": "1.2.7", - "@rolldown/binding-linux-arm64-musl": "1.2.7", - "@rolldown/binding-linux-ppc64-gnu": "1.2.7", - "@rolldown/binding-linux-s390x-gnu": "1.2.7", - "@rolldown/binding-linux-x64-gnu": "1.2.7", - "@rolldown/binding-linux-x64-musl": "1.2.7", - "@rolldown/binding-openharmony-arm64": "1.2.7", - "@rolldown/binding-win32-arm64-msvc": "1.2.7", - "@rolldown/binding-win32-x64-msvc": "1.2.7" + "@rolldown/binding-android-arm-eabi": "1.2.16", + "@rolldown/binding-android-arm64": "1.2.16", + "@rolldown/binding-darwin-arm64": "1.2.16", + "@rolldown/binding-darwin-x64": "1.2.16", + "@rolldown/binding-freebsd-x64": "1.2.16", + "@rolldown/binding-linux-arm-gnueabihf": "1.2.16", + "@rolldown/binding-linux-arm64-gnu": "1.2.16", + "@rolldown/binding-linux-arm64-musl": "1.2.16", + "@rolldown/binding-linux-ppc64-gnu": "1.2.16", + "@rolldown/binding-linux-s390x-gnu": "1.2.16", + "@rolldown/binding-linux-x64-gnu": "1.2.16", + "@rolldown/binding-linux-x64-musl": "1.2.16", + "@rolldown/binding-openharmony-arm64": "1.2.16", + "@rolldown/binding-win32-arm64-msvc": "1.2.16", + "@rolldown/binding-win32-x64-msvc": "1.2.16" } }, "node_modules/router": { @@ -2162,23 +2171,16 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/siginfo": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", - "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", - "dev": true, - "license": "ISC" - }, "node_modules/simple-git": { - "version": "3.36.0", - "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-3.36.0.tgz", - "integrity": "sha512-cGQjLjK8bxJw4QuYT7gxHw3/IouVESbhahSsHrX97MzCL1gu2u7oy38W6L2ZIGECEfIBG4BabsWDPjBxJENv9Q==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-4.0.2.tgz", + "integrity": "sha512-l0sIsv9VrPwovKELFDBOj/lhmIcbMvxBk2B+hRhsMM4/VL3gKtuZvYnBNixVo+N/0HnqQx2cBdLvxnAbygHpqA==", "license": "MIT", "dependencies": { "@kwsites/file-exists": "^1.1.1", "@kwsites/promise-deferred": "^1.1.1", - "@simple-git/args-pathspec": "^1.0.3", - "@simple-git/argv-parser": "^1.1.0", + "@simple-git/args-pathspec": "1.0.4", + "@simple-git/argv-parser": "2.0.1", "debug": "^4.4.0" }, "funding": { @@ -2187,9 +2189,9 @@ } }, "node_modules/source-map-js": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", - "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -2220,13 +2222,6 @@ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1" } }, - "node_modules/stackback": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", - "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", - "dev": true, - "license": "MIT" - }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -2237,16 +2232,16 @@ } }, "node_modules/std-env": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.2.0.tgz", - "integrity": "sha512-oCUKSupKTHX53EyjDtuZQ64pjLJ6yYCtpmEw0goYxtjG9KpbRe8KAsl2tBUGU9DyMcJ0RwJ8GqJAFzMXcXW1Rw==", + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-4.3.0.tgz", + "integrity": "sha512-OtU/EgQ1kIm5KwqQpBC6ZEMXrZRui11w8zgfTWp8cdO9B8OaPsbA8bTHO2P+HNo1VlUTGMVBwPhydu6poeXiag==", "dev": true, "license": "MIT" }, "node_modules/tinybench": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.1.4.tgz", - "integrity": "sha512-9APumHG7r4yOk4X4WlkmE71aZcv1gvin1czO3OQ1U9iJcFA5Ja/ygyb0vPOVHTthFozUYs8CLoLUlM8grb2lTQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.2.tgz", + "integrity": "sha512-nIZ2N2dFpjBsg2hgitKu7mA+FvsIJuGJOwysRngmVRRmWgR/UyPjwJj8c/fT/dhm8EkHnF5EOogRf+0Z2qrimA==", "dev": true, "license": "MIT", "engines": { @@ -2254,9 +2249,9 @@ } }, "node_modules/tinyexec": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.0.tgz", - "integrity": "sha512-QKAl9m8gWWGHV8jZcPeym6j+XULi6tOf1mT83WYJ4Lk2ytW/uwAWkrP0uFsdoYMdueVJ0qs26wZ+23xeB4ibNQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.3.1.tgz", + "integrity": "sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA==", "dev": true, "license": "MIT", "engines": { @@ -2345,17 +2340,17 @@ } }, "node_modules/vite": { - "version": "8.2.2", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.2.2.tgz", - "integrity": "sha512-cFKLV/PRgAUlIRm5WjMjJ86jrftzpqcgH+Us+DS8mI3CDNiH30Whrz8uHL3+MOLPAgqbMBAqWdAHAphOAM+z/Q==", + "version": "8.3.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.3.5.tgz", + "integrity": "sha512-z1YpjieJ9g0ebTmQpLBv/oq9E3F9EIlOe+7LahW8SrE3LTiG3udBRUXQOlYXP9b+IC2q2phHeVxj9xGlw34OEQ==", "dev": true, "license": "MIT", "peer": true, "dependencies": { "lightningcss": "^1.33.0", - "picomatch": "^4.0.5", - "postcss": "^8.5.26", - "rolldown": "~1.2.4", + "picomatch": "^4.0.7", + "postcss": "^8.5.30", + "rolldown": "~1.2.14", "tinyglobby": "^0.2.17" }, "bin": { @@ -2372,7 +2367,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.4.0 || ^0.5.0", + "@vitejs/devtools": "^0.7.1 || ^0.8.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", @@ -2424,25 +2419,25 @@ } }, "node_modules/vitest": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.0.tgz", - "integrity": "sha512-gpsMNoRhMjMktVxPtstOH4/PJuPyovVaMDr4oDilXaGH1EcqM2OE96SoHT2VIQ6fTGtTjqmHDrEu2X9RQiXf8Q==", + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-5.0.5.tgz", + "integrity": "sha512-cPODOMF8YnkiiYtDdHo6n4wWmBrOBBbOIYH2d33M4l3CdNn72h+qMmO6uXffUN6A5AZFXNND/d+krS9mgkXRfg==", "dev": true, "license": "MIT", "dependencies": { "@types/chai": "^5.2.2", - "@vitest/mocker": "5.0.0", - "chai": "^6.2.2", + "@vitest/mocker": "5.0.5", + "chai": "^6.3.0", "es-module-lexer": "^2.3.2", "expect-type": "^1.4.0", - "magic-string": "^1.2.3", - "obug": "^2.1.4", + "magic-string": "^1.4.3", + "obug": "^3.0.0", "picomatch": "^4.0.7", - "std-env": "^4.2.0", - "tinybench": "6.1.4", - "tinyexec": "1.3.0", + "std-env": "^4.3.0", + "tinybench": "^6.2.2", + "tinyexec": "^1.3.1", "tinyglobby": "^0.2.17", - "why-is-node-running": "^2.3.0" + "why-is-node-running": "3.2.1" }, "bin": { "vitest": "vitest.mjs" @@ -2457,12 +2452,12 @@ "@edge-runtime/vm": "*", "@opentelemetry/api": "^1.9.0", "@types/node": "^22.0.0 || >=24.0.0", - "@vitest/browser-playwright": "5.0.0", - "@vitest/browser-preview": "5.0.0", + "@vitest/browser-playwright": "5.0.5", + "@vitest/browser-preview": "5.0.5", "@vitest/browser-webdriverio": "^5.0.0-beta.5 || >=5.0.0", - "@vitest/coverage-istanbul": "5.0.0", - "@vitest/coverage-v8": "5.0.0", - "@vitest/ui": "5.0.0", + "@vitest/coverage-istanbul": "5.0.5", + "@vitest/coverage-v8": "5.0.5", + "@vitest/ui": "5.0.5", "happy-dom": "*", "jsdom": "*", "vite": "^6.4.0 || ^7.0.0 || ^8.0.0" @@ -2522,20 +2517,16 @@ } }, "node_modules/why-is-node-running": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", - "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-3.2.1.tgz", + "integrity": "sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==", "dev": true, "license": "MIT", - "dependencies": { - "siginfo": "^2.0.0", - "stackback": "0.0.2" - }, "bin": { "why-is-node-running": "cli.js" }, "engines": { - "node": ">=8" + "node": ">=20.11" } }, "node_modules/wrappy": { @@ -2554,9 +2545,9 @@ } }, "node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", + "version": "4.6.7", + "resolved": "https://registry.npmjs.org/zod/-/zod-4.6.7.tgz", + "integrity": "sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==", "license": "MIT", "peer": true, "funding": { diff --git a/package.json b/package.json index 4e66988..3d5a87f 100644 --- a/package.json +++ b/package.json @@ -17,11 +17,11 @@ "verify:boundaryattest": "node tools/verifyBoundaryAttest.js" }, "dependencies": { - "@modelcontextprotocol/sdk": "^1.29.0", + "@modelcontextprotocol/sdk": "^1.32.2", "mysql2": "^3.24.3", "pg": "^8.23.0", - "simple-git": "^3.36.0", - "zod": "^4.4.3" + "simple-git": "^4.0.2", + "zod": "^4.6.7" }, "devDependencies": { "vitest": "^5.0.0" diff --git a/tests/dependencies.test.js b/tests/dependencies.test.js new file mode 100644 index 0000000..534930b --- /dev/null +++ b/tests/dependencies.test.js @@ -0,0 +1,46 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { test } from "vitest"; + +// P0-01: direct dependencies must stay on patched versions (see docs/P0.md). +// Offline check of the installed versions; `npm audit --omit=dev --audit-level=high` runs in CI (P0-07). +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); + +function installedVersion(name) { + const file = path.join(root, "node_modules", ...name.split("/"), "package.json"); + return JSON.parse(fs.readFileSync(file, "utf8")).version; +} + +function atLeast(version, minimum) { + const a = version.split("-")[0].split(".").map(Number); + const b = minimum.split(".").map(Number); + for (let i = 0; i < 3; i += 1) { + if ((a[i] || 0) !== (b[i] || 0)) return (a[i] || 0) > (b[i] || 0); + } + return true; +} + +const minimums = { + "simple-git": "4.0.2", + "@modelcontextprotocol/sdk": "1.32.2", + zod: "4.6.6", + "@simple-git/argv-parser": "2.0.1", + "body-parser": "2.4.0", +}; + +for (const [name, minimum] of Object.entries(minimums)) { + test(`${name} is at least ${minimum}`, () => { + const version = installedVersion(name); + assert.ok(atLeast(version, minimum), `${name} ${version} is older than ${minimum}`); + }); +} + +test("package.json ranges do not allow the vulnerable versions", () => { + const pkg = JSON.parse(fs.readFileSync(path.join(root, "package.json"), "utf8")); + for (const name of ["simple-git", "@modelcontextprotocol/sdk", "zod"]) { + const floor = pkg.dependencies[name].replace(/^[\^~]/, ""); + assert.ok(atLeast(floor, minimums[name]), `${name} range ${pkg.dependencies[name]} allows versions below ${minimums[name]}`); + } +}); From c9c01223ee6485bab8a0108742792c60064063a2 Mon Sep 17 00:00:00 2001 From: KNIHAL Date: Sun, 1 Nov 2026 06:26:09 +0530 Subject: [PATCH 03/18] fix(P0-02): Windows nested cmd.exe finds node, ComSpec set (issue #2) --- CHANGELOG.md | 3 ++ tests/commandEnv.test.js | 89 ++++++++++++++++++++++++++++++++ tests/commandOps.windows.test.js | 48 +++++++++++++++++ tools/commandOps.js | 51 +++++++++++++----- 4 files changed, 177 insertions(+), 14 deletions(-) create mode 100644 tests/commandEnv.test.js create mode 100644 tests/commandOps.windows.test.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 37af2eb..f1a501f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ All notable changes to this project are documented here. - Updated vulnerable dependencies: `simple-git` 4.x, `@modelcontextprotocol/sdk` 1.32.x, `zod` 4.6.x, plus transitive fixes. `npm audit --omit=dev` now reports 0 vulnerabilities. - Added a test that keeps the direct dependencies on patched versions. +### Fixed +- Windows: `npm install` (and any npm lifecycle script) through `run_command` no longer fails with `'node' is not recognized` (issue #2). The Node that runs the server and the npm global folder now come first in `PATH`, `ComSpec` is set when missing, and the hard-coded user name and Node folder are gone. + ## [1.5.3] ### Added diff --git a/tests/commandEnv.test.js b/tests/commandEnv.test.js new file mode 100644 index 0000000..318aaf9 --- /dev/null +++ b/tests/commandEnv.test.js @@ -0,0 +1,89 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import path from "node:path"; +import { fileURLToPath } from "node:url"; +import { describe, test } from "vitest"; +import { buildWindowsEnv } from "../tools/commandOps.js"; + +// P0-02 / issue #2: nested cmd.exe (npm lifecycle scripts) must find the same node that runs the server. +const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), ".."); +const NODE_EXE = "D:\\tools\\fnm\\node-v22\\node.exe"; +const NODE_DIR = "D:\\tools\\fnm\\node-v22"; +const HOME = "C:\\Users\\Ünï Köder"; + +function build(env, extra = {}) { + return buildWindowsEnv(env, { execPath: NODE_EXE, homedir: () => HOME, ...extra }); +} + +function pathList(env) { + const key = Object.keys(env).find((k) => k.toUpperCase() === "PATH"); + return env[key].split(";"); +} + +describe("buildWindowsEnv", () => { + test("stripped environment gets the running node directory and the npm global dir", () => { + const env = build({ SystemRoot: "C:\\Windows" }); + const list = pathList(env); + assert.equal(list[0], NODE_DIR); + assert.ok(list.includes(`${HOME}\\AppData\\Roaming\\npm`)); + }); + + test("uses APPDATA for the npm global dir when present", () => { + const env = build({ SystemRoot: "C:\\Windows", APPDATA: "D:\\Roam" }); + assert.ok(pathList(env).includes("D:\\Roam\\npm")); + }); + + test("sets ComSpec to the absolute cmd.exe when missing or empty", () => { + assert.equal(build({ SystemRoot: "C:\\Windows" }).ComSpec, "C:\\Windows\\System32\\cmd.exe"); + assert.equal(build({ SystemRoot: "D:\\WinNT", ComSpec: "" }).ComSpec, "D:\\WinNT\\System32\\cmd.exe"); + }); + + test("keeps an existing ComSpec", () => { + const env = build({ SystemRoot: "C:\\Windows", ComSpec: "C:\\custom\\cmd.exe" }); + assert.equal(env.ComSpec, "C:\\custom\\cmd.exe"); + }); + + test("node directory goes first even when an older node is already in PATH", () => { + const env = build({ SystemRoot: "C:\\Windows", Path: "C:\\old\\node;C:\\Windows\\System32" }); + const list = pathList(env); + assert.equal(list[0], NODE_DIR); + assert.ok(list.indexOf("C:\\old\\node") > 0); + }); + + test("no duplicate entries, compared case-insensitively", () => { + const env = build({ SystemRoot: "C:\\Windows", PATH: `C:\\Windows;${NODE_DIR.toUpperCase()};C:\\Windows` }); + const list = pathList(env).map((p) => p.toLowerCase()); + assert.equal(list.filter((p) => p === NODE_DIR.toLowerCase()).length, 1); + assert.equal(list.filter((p) => p === "c:\\windows").length, 1); + }); + + test("keeps the original PATH key casing and never creates a second key", () => { + const env = build({ SystemRoot: "C:\\Windows", Path: "C:\\Windows" }); + assert.deepEqual(Object.keys(env).filter((k) => k.toUpperCase() === "PATH"), ["Path"]); + }); + + test("home comes from the given home directory, never a fixed user", () => { + const env = build({ SystemRoot: "C:\\Windows" }); + assert.ok(pathList(env).some((p) => p.startsWith(HOME))); + }); + + test("restores PATHEXT without empty segments", () => { + const env = build({ SystemRoot: "C:\\Windows", PATHEXT: ";.COM" }); + const ext = env.PATHEXT.split(";"); + assert.ok(!ext.includes("")); + for (const e of [".EXE", ".CMD", ".BAT"]) assert.ok(ext.includes(e)); + }); + + test("handles non-ASCII, spaces and a very long PATH", () => { + const long = Array.from({ length: 500 }, (_, i) => `C:\\dir ${i}\\ünï`).join(";"); + const list = pathList(build({ SystemRoot: "C:\\Windows", PATH: long })); + assert.equal(list[0], NODE_DIR); + assert.ok(list.includes("C:\\dir 499\\ünï")); + }); +}); + +test("commandOps.js contains no user name or fixed Node install directory", () => { + const source = fs.readFileSync(path.join(root, "tools", "commandOps.js"), "utf8"); + assert.ok(!/nihal/i.test(source), "hard-coded user name"); + assert.ok(!/Program Files\\+nodejs/i.test(source), "hard-coded Node directory"); +}); diff --git a/tests/commandOps.windows.test.js b/tests/commandOps.windows.test.js new file mode 100644 index 0000000..d4c249d --- /dev/null +++ b/tests/commandOps.windows.test.js @@ -0,0 +1,48 @@ +import assert from "node:assert/strict"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; +import { afterEach, beforeEach, describe, test } from "vitest"; +import { runCommand } from "../tools/commandOps.js"; + +// P0-02 / issue #2: real process, stripped environment, npm lifecycle script that calls `node`. +describe.skipIf(process.platform !== "win32")("runCommand on Windows with a stripped environment", () => { + let dir; + let saved; + + beforeEach(() => { + dir = fs.mkdtempSync(path.join(os.tmpdir(), "causly-postinstall-")); + fs.writeFileSync( + path.join(dir, "package.json"), + JSON.stringify({ + name: "postinstall-fixture", + version: "1.0.0", + scripts: { postinstall: "node -e \"require('fs').writeFileSync('ran.txt','ok')\"" }, + }) + ); + saved = { ...process.env }; + const systemRoot = process.env.SystemRoot || "C:\\Windows"; + for (const key of Object.keys(process.env)) delete process.env[key]; + process.env.SystemRoot = systemRoot; + process.env.USERPROFILE = saved.USERPROFILE || os.homedir(); + process.env.TEMP = saved.TEMP || os.tmpdir(); + process.env.PATH = `${systemRoot}\\System32`; + }); + + afterEach(() => { + for (const key of Object.keys(process.env)) delete process.env[key]; + Object.assign(process.env, saved); + fs.rmSync(dir, { recursive: true, force: true }); + }); + + test("npm install runs a postinstall script that calls node", async () => { + const npmCmd = path.join(path.dirname(process.execPath), "npm.cmd"); + const result = await runCommand({ + command: `& '${npmCmd}' install --no-audit --no-fund --offline`, + cwd: dir, + timeout_ms: 120_000, + }); + assert.equal(result.exit_code, 0, `${result.stdout}\n${result.stderr}`); + assert.equal(fs.readFileSync(path.join(dir, "ran.txt"), "utf8"), "ok"); + }); +}); diff --git a/tools/commandOps.js b/tools/commandOps.js index 6f56a4b..3ca2d5e 100644 --- a/tools/commandOps.js +++ b/tools/commandOps.js @@ -1,6 +1,7 @@ import { exec, execFile } from "child_process"; import { promisify } from "util"; import os from "os"; +import path from "path"; import { classifyCommandRisk } from "./security.js"; const execAsync = promisify(exec); @@ -42,9 +43,9 @@ function findKey(env, name) { return Object.keys(env).find((k) => k.toUpperCase() === name.toUpperCase()); } -function buildEnv() { - if (!IS_WINDOWS) return process.env; - const env = { ...process.env }; +// Pure and platform-independent (uses path.win32) so it can be tested on any OS. +export function buildWindowsEnv(baseEnv, { execPath = process.execPath, homedir = () => os.homedir() } = {}) { + const env = { ...baseEnv }; // IMPORTANT: always read/write through the env's *actual* existing key // casing (e.g. "Path" vs "PATH"). Setting env.PATH when the real key is @@ -77,22 +78,44 @@ function buildEnv() { // `npm run