Skip to content

Security audit follow-up: resolve all deferred items - #86

Merged
johnnyclem merged 7 commits into
mainfrom
claude/code-review-security-audit-hyxm9a
Aug 16, 2026
Merged

Security audit follow-up: resolve all deferred items#86
johnnyclem merged 7 commits into
mainfrom
claude/code-review-security-audit-hyxm9a

Conversation

@johnnyclem

Copy link
Copy Markdown
Owner

Summary

Follow-up to #85 (merged). That PR's AUDIT.md deliberately deferred five findings rather than folding them into a security-audit branch without individual review. This PR resolves all five, plus one thing found along the way. Full detail in AUDIT.md's "Follow-up: Deferred Items Addressed" section and the corresponding CHANGELOG.md entries.

What's in this PR

  • Stray nested lockfiles removed (shorthand/, packages/examples/). Neither was actually maintained by any normal npm install — npm defers to the workspace root — and shorthand/package-lock.json had already drifted back to vitest@3.2.4, the exact CVE fixed in Security audit: close MCP/channel auth gaps, fix critical CVE, add CI #85, invisible to root npm audit/CI.
  • packages/docs: Docusaurus 3.6.33.10.2 (all four @docusaurus/* packages together), resolving 44 → 24 npm audit findings. The remaining 24 have no fix available yet upstream (confirmed via npm audit's own output) — they're in @docusaurus/bundler's webpack toolchain, dev-time only, not shipped in the built site.
  • Fixed the Tier-1/Tier-2 layering violation ToolClass/ToolProxy had — statically importing MCPTransport from src/mcp/transport.ts, contradicting @smallchat/core/inference's "transport-agnostic durable core" contract. ToolProxy now takes an injected transportFactory; the two places that construct one (compiler.ts, mcp/artifact.ts) pass the concrete implementation, preserving identical behavior. Added a source-scan regression test (src/inference.test.ts) so this can't silently regress.
  • Reconciled the src/importance/ fork. It had drifted from @shorthand/core/importance (a narrower local ConversationMessage type, missing the 'tool' role and normalizeTimestamp) — tracked as a "Phase 0" fix in docs/ecosystem/engineering-guide.md. Replaced it with a thin re-export, matching how compaction/CRDT already work. Found and closed a real CI gap while making this change: shorthand/'s own ~260-spec test suite was never run by root npm test or CI.
  • Node floor raised to >=22, with an explicit decision rather than a unilateral one — I asked before doing this, since it's a real breaking change. Unblocked commander (13→15) and better-sqlite3 (11→13), applied consistently across every package.json in the repo (including the smallchat init scaffold template) and CI's Node matrix.

Test plan

  • npm run lint (tsc --noEmit) clean at every step
  • npm test — 1106/1106 passing (down from 1153 in Security audit: close MCP/channel auth gaps, fix critical CVE, add CI #85; the importance-fork consolidation removed duplicated tests whose logic is now covered by @shorthand/core's own suite instead)
  • npm test --workspace=shorthand — 260/260, newly wired into CI
  • npm run build and npm run build:packages clean
  • packages/docs: npm run build (docusaurus build) clean, no warnings
  • Built and ran the actual CLI (smallchat --help, smallchat doctor) against the new commander/better-sqlite3 — a native-binding major bump a type-check wouldn't necessarily catch
  • Verified via dist/inference.js/dist/core/*.js/dist/runtime/*.js that no trace of mcp/transport remains after the layering fix

Generated by Claude Code

claude added 7 commits August 15, 2026 19:09
…amples/

Both were flagged as a deferred finding in the prior audit pass
(AUDIT.md #15) pending confirmation they weren't serving some
standalone-publish purpose that would make removing them unsafe.
Checked both possibilities directly:

- @shorthand/core is not published to npm (registry 404) despite
  shorthand/'s lockfile implying an independently-tracked dependency
  tree.
- @smallchat/examples is published, but publishing doesn't consume a
  package-lock.json, so that's not a reason to keep one either.

More importantly, `npm install` run directly inside either directory
does not update its local lockfile at all — npm detects the ancestor
workspace root and defers to the root package-lock.json entirely, so
these files were not being maintained by any normal workflow. This
had already caused real drift: shorthand/package-lock.json still
resolved vitest@3.2.4, the exact version with the critical CVE fixed
at the root in the prior audit pass (#85) — invisible to root-level
`npm audit`/CI, the same class of blind spot as the packages/docs
lockfile found in that same pass. A lockfile nobody can update and
that silently reintroduces fixed CVEs is worse than no lockfile.

Verified: fresh `npm install` from a clean node_modules, `npm test`
(1153/1153), `npm run build`, and `npm run build:packages` all pass
using only the root lockfile.
Second half of the deferred packages/docs dependency work: bumping
just @docusaurus/preset-classic (via npm audit fix --force) while
@docusaurus/core stayed pinned at 3.6.3 produced a mismatched,
out-of-range pair that wasn't safe to ship. Bumped all four
@docusaurus/* packages (core, preset-classic, module-type-aliases,
types) together to the current latest, 3.10.2.

- npm audit: 44 -> 24 remaining findings. All 24 are
  serialize-javascript/uuid/sockjs/webpack-dev-server, pulled in
  transitively via @docusaurus/bundler's own webpack toolchain, with
  "No fix available" per npm audit's own output even at Docusaurus's
  current latest release -- nothing further to do here until
  Docusaurus updates that dependency chain upstream. These are
  build/dev-time tooling deps, not shipped in the static site output.
- Fixed the onBrokenMarkdownLinks config deprecation warning the
  bump surfaced (moved to markdown.hooks.onBrokenMarkdownLinks per
  the new Docusaurus config shape).
- Aligned engines.node from >=18.0 to >=20.0 -- Docusaurus 3.10
  itself now requires Node >=20, so the old floor was both already
  inconsistent with the rest of the monorepo and no longer accurate.

Verified: `npm run build` (docusaurus build) succeeds cleanly with no
warnings; root `npm test` (1153/1153) and `npm run build` unaffected
(packages/docs is outside the root workspace/tsconfig).
Deferred item #13 from the security audit: ToolClass/ToolProxy are
re-exported from `@smallchat/core/inference`, documented as the
durable, transport-agnostic engine -- but ToolProxy statically
imported MCPTransport/getTransport from src/mcp/transport.ts, ~550
lines of HTTP/JSON-RPC/SSE/gRPC wire-protocol code. Anyone importing
`@smallchat/core/inference` expecting pure selection logic got
concrete networking code pulled in transitively, contradicting the
entry point's own stated contract in ARCHITECTURE.md.

- src/core/types.ts: added ToolTransport (execute/executeStream/
  executeInference), ToolTransportConnectionOptions, and a
  ToolTransportFactory type -- type-only, zero runtime import cost.
- src/core/tool-class.ts: ToolProxy no longer imports mcp/transport.js
  at all. Its constructor takes an optional transportFactory; getTransport()
  is now a private method that uses the injected factory (lazily,
  same caching behavior as before) or returns null. execute()/
  executeStream() return a clear "no transport configured" ToolResult
  error when no factory was injected, rather than silently reaching
  into a hardcoded implementation; executeInference() returns nothing,
  matching its existing "unsupported transport" no-op pattern.
- src/compiler/compiler.ts (createIMP) and src/mcp/artifact.ts
  (hydrateRuntime) -- the only two places that construct ToolProxy --
  now explicitly pass getTransport from mcp/transport.ts, preserving
  identical existing behavior at both call sites.
- src/inference.ts and src/index.ts: export the three new types
  alongside the existing core type exports.

Verified the fix is real, not just moved: grepped the built
dist/inference.js, dist/core/*.js, and dist/runtime/*.js for any
reference to mcp/transport -- none. Added src/inference.test.ts, a
source-scan regression test that fails if any core/runtime file
statically imports mcp/transport.ts again. Added constructor-injection
tests to core/tool-class.test.ts (no-factory error path, and routing
through an injected factory).

Verified: npm run lint (tsc --noEmit), npm test (1156/1156), npm run
build, and npm run build:packages all clean.
…tance

Deferred item #21, tracked as a "Phase 0, do first" fix in
docs/ecosystem/engineering-guide.md: PR #58 titled itself "Extract
@shorthand/core package from compaction, CRDT, and importance
modules," but only compaction and CRDT actually got extracted --
src/compaction/ and src/crdt/ don't exist as local directories, only
as re-exports from @shorthand/core in src/index.ts. src/importance/
was left as a full duplicate copy of shorthand/src/importance/
instead, and it had already drifted: its types.ts defined a narrower
local ConversationMessage (missing the 'tool' role option,
timestamp: string | number, and normalizeTimestamp) instead of
importing the canonical shared type shorthand/src/importance/types.ts
already does.

`diff -rq` on the two directories confirmed every other file was
byte-identical -- this was purely a types.ts fork. Replaced
src/importance/ with a thin re-export of @shorthand/core/importance,
matching how compaction/CRDT already work, and deleted the five
duplicated implementation files plus their tests (the underlying
logic is exercised by @shorthand/core's own test suite). The public
@smallchat/core/importance subpath is unchanged -- same exported
names, same behavior -- so this is non-breaking for anyone consuming
it.

Making this change surfaced a real CI gap: shorthand/'s own ~260-spec
test suite (compaction, CRDT, importance) was never run by root
`npm test` or CI -- root vitest.config.ts only globs the root src/,
and nothing wired shorthand's tests into either. This was true even
before this change; deleting the local duplicate just made the gap
visible rather than causing it. Added `npm test --workspace=shorthand`
to CI's test job to close it.

Adds src/importance/index.test.ts: a smoke test verifying the
re-export barrel resolves and works end-to-end (not re-testing the
underlying logic, which shorthand's own suite already covers).

Verified: npm run lint (tsc --noEmit) clean, npm test (1106/1106),
npm run build, npm run build:packages, and
`npm test --workspace=shorthand` (260/260) all pass.
Root spec count dropped from ~1,150 to ~1,106 (removing the duplicated
src/importance/ implementation files also removed their tests -- the
underlying logic is now exercised by @shorthand/core's own suite
instead). Also documents `npm test --workspace=shorthand`, which the
audit follow-up wired into CI but wasn't mentioned anywhere for anyone
running it locally.
Last deferred item from the security audit. commander@15 and
better-sqlite3@13 (two majors ahead of what was pinned) both now
require Node >=22 upstream -- a real breaking-change decision that
wasn't this audit's to make unilaterally, so it was put to the
maintainer directly: raise the floor, or hold at Node >=20 and skip
the bumps. Decision: raise the floor.

BREAKING CHANGE: engines.node is now >=22.0.0 (was >=20.0.0). Node 20
is no longer supported.

- package.json: commander ^13.0.0 -> ^15.0.0, better-sqlite3
  ^11.0.0 -> ^13.0.3, @types/better-sqlite3 ^7.6.12 -> ^9.6.0,
  engines.node -> >=22.0.0.
- shorthand/package.json: better-sqlite3 bumped to match (it depends
  on the same package directly -- now deduped to one copy across the
  workspace instead of two divergent majors), plus vitest ^3.0.0 ->
  ^3.2.7 to match the critical-CVE fix applied to root earlier in
  this audit but missed here, and engines.node -> >=22.0.0.
- packages/examples/package.json, packages/docs/package.json:
  engines.node -> >=22.0.0 for consistency.
- src/cli/commands/init.ts: the package.json template `smallchat
  init` scaffolds for new projects also bumped to >=22.0.0 -- it
  depends on @smallchat/core, which now requires that floor anyway.
- .github/workflows/ci.yml: test matrix ['20','22'] -> ['22','24'];
  the audit and docs jobs' single Node version '20' -> '22'.
- README.md: "Requires Node.js >= 20" -> ">= 22".

Verified beyond typecheck/tests (which a native-binding major bump
like better-sqlite3 wouldn't necessarily catch): built and ran the
actual CLI against the new versions -- `smallchat --help` renders
correctly and `smallchat doctor` confirms
"better-sqlite3 + sqlite-vec: working".

Verified: npm run lint, npm test (1106/1106), npm run build,
npm run build:packages, and npm test --workspace=shorthand
(260/260) all pass on Node 22.
@vercel

vercel Bot commented Aug 16, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
smallchat-web Ready Ready Preview Aug 16, 2026 1:07am

@johnnyclem
johnnyclem merged commit ec612bb into main Aug 16, 2026
6 checks passed
@johnnyclem
johnnyclem deleted the claude/code-review-security-audit-hyxm9a branch August 16, 2026 01:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants