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.
[ ]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 findingssimple-git 4.xlazyLoading.e2e, useTool.e2e and agent-smoke pass on the new SDKDocs to update. CHANGELOG.md security section. If simple-git 4 changes how a git tool behaves, also tools/filesystem-git-shell.md.
npm install fails in nested cmd.exe on Windows [x]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:
C:\Users\nihal. That is wrong on every
other machine.C:\Program Files\nodejs. A Node installed with nvm,
Volta, fnm or Scoop lives elsewhere.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.path.dirname(process.execPath)) is never used.Fix.
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.C:\Users\nihal and C:\Program Files\nodejs. Derive the profile from
os.homedir().ComSpec to the absolute cmd.exe path when it is missing.cmd.exe fallbacks as they are.Acceptance.
runCommand with a stripped environment (only SystemRoot, no ComSpec,
no Node directory in PATH) and runs a package whose postinstall calls nodenpm install of a package with a native
postinstall succeedswebsite/docs/server/troubleshooting.md and limitations.mdDocs to update. troubleshooting.md (remove the Windows postinstall workaround), limitations.md (Platforms), and close issue #2.
[ ]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:
connection_string, connectionString, dsn, database_url, uri, url
(when it carries credentials)scheme://user:password@host userinfo, Bearer tokens, ghp_, xoxb-,
sk- style prefixes, and JSON "password": "..." fragmentstoolWrapper.js before returning an error to the clientAcceptance.
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 errorDocs to update. security.md (Secret redaction: new key names and credentials inside URLs), tools/database.md (connection strings are redacted in logs).
[ ]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 <dir> -- 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 backtick and a space
and asserts the argument array is intact and nothing is evaluatedexecDocs to update. tools/docker.md (how Compose runs through WSL on Windows), security.md (What is not protected) if the wording changes.
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.
webOps tests still pass unchangedDocs 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).
[ ]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.
.., 8.3 short names), then compare on path boundaries./etc, /usr, /bin, /System, /Library,
~/.ssh, ~/.aws, ~/.config/gcloud, the OS keychain folders).store_path like any other path argument.Acceptance.
fileOps.js and directoryOps.js and fails if
one does not call the guard.. 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::$DATADocs 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).
[ ]Evidence.
.github/workflows/ci.yml:18 runs Node 18 and :21 uses npm install. package.json
requires Node 22.13 or newer.npm install again at line 52..github/dependabot.yml.npm audit.Fix.
npm ci everywhere.github/dependabot.yml for npm and GitHub Actions, weeklynpm audit --omit=dev --audit-level=highnpm pack --dry-run --json must list only the files allowlist, and must not
contain .env, *.db, logs or keyswebsite/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)onBrokenLinks to throwAcceptance.
Docs to update. development.md (CI jobs, new checks, Dependabot, how to run the docs drift test locally).
[ ]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.
website/docs/server/security.md examples and every tool page are updated (the drift test
from P0-07 enforces this)git_stash and git_tag: per-action levels need confirm only for the destructive
actionDocs 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.
[ ]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.
READ is read-only, DESTRUCTIVE
is destructive, network tools are open-world) and apply them in the wrappertools/list, schemas, error shape and that
nothing but protocol messages reaches stdoutAcceptance.
READ tools are never marked
destructiveDocs to update. architecture.md (annotations), development.md (adding a pack: annotations come from the permission level), security.md (annotations are hints, not enforcement).
[ ]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.
Docs to update. development.md (how to use the fuzz helper when adding a tool).
[ ]Why. Windows has had hands-on use. macOS and Linux have been run only by CI, never by hand.
Do.
npm install -g, init, doctor,
status, a few real tool calls, removenpx flow and the global flow.env, a corrupt secrets file and a corrupt
database: each must produce a clear error, not a crashinit must not damage itAcceptance.
tests/ with a checked-in 1.5.0 fixture databaseDocs to update. getting-started.md (verified platforms, minimum Node version), limitations.md (Platforms), troubleshooting.md (new install findings).
[ ]Evidence. package.json has no homepage. The package is not published with provenance.
Do.
homepage (https://server.causly.in), keep repository and bugs accuratenpm publish --provenance from GitHub ActionsCHANGELOG.md 1.5.4 with a “Security” section that names what was fixed, without exploit
detailSECURITY.md: confirm the reporting address and response times are accurateAcceptance.
npm view causly-server shows 1.5.4 with the homepage and provenanceDocs to update. CHANGELOG.md. README and ROADMAP are updated last, after P0 is green. No docs site change.
[ ]Evidence.
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.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.
state: "all" returns open and closed issuesDocs to update. tools/filesystem-git-shell.md (edit_file and line endings), tools/github.md (state values for github_list_issues).