Skip to content

fix(security): audit round 3 — error leakage, querySelector crashes, stale closures, path traversal#309

Open
vamgan wants to merge 10 commits into
mainfrom
claude/create-agents-md-fqfEf
Open

fix(security): audit round 3 — error leakage, querySelector crashes, stale closures, path traversal#309
vamgan wants to merge 10 commits into
mainfrom
claude/create-agents-md-fqfEf

Conversation

@vamgan
Copy link
Copy Markdown
Collaborator

@vamgan vamgan commented Jun 1, 2026

Summary

Security audit of the full monorepo, fixing 5 real vulnerabilities across 4 packages.

  • MCP error leakageget_current_context and format_context_for_prompt tool handlers forwarded raw provider error messages to MCP clients (template literals: `Failed: ${err.message}`). Provider errors can contain DB connection strings, internal stack traces, or API keys. Replaced with a generic message; raw errors now go to console.error on the server side only.
  • querySelector SyntaxError (context.ts) — AskableContext.resolveExplicitHierarchyParent() passed data-askable-parent attribute values to querySelector() without try/catch, same class of bug already fixed in observer.ts. An invalid selector silently breaks focus tracking for the element. Closes fix(security): querySelector SyntaxError not caught in AskableContext.resolveExplicitHierarchyParent #305.
  • parseMeta type confusion (observer.ts) — JSON.parse() result was unsafely cast to Record<string, unknown> without validating the type. Non-object JSON (numbers, arrays, null) now falls back to the raw string, matching the declared return type. Closes fix(security): parseMeta in observer.ts silently casts non-object JSON to Record type #306.
  • Stale onCapture/onCancel closure (React) — useAskableTextSelectionCapture called currentOptions.onCapture (captured at start() time) instead of optionsRef.current.onCapture (always the latest). In React, callbacks are recreated each render; stale closure meant old callbacks fired after re-renders. useAskableRegionCapture already used optionsRef.current correctly — this aligns the text selection hook. Closes fix(react): stale onCapture/onCancel closure in useAskableTextSelectionCapture #307.
  • Path traversal (create-askable-app) — scaffold resolved project names containing ../ to paths outside cwd() and wrote template files there. Added a guard that rejects target directories outside the current working directory. Closes fix(security): path traversal in create-askable-app scaffold allows writing outside cwd #308.

Test plan

  • MCP: createAskableMcpServer describe block (5 tests) — error containment, option forwarding, defaultPromptFormatter fallback
  • Core observer: invalid selector no-throw, numeric JSON fallback, array JSON fallback
  • Core context: invalid data-askable-parent selector no-throw
  • React: invokes the latest onCapture after prop changes mid-session — verifies stale closure is fixed
  • create-askable-app: runCli rejects path traversal outside cwd
  • npm run test --workspaces passes

Generated by Claude Code

claude added 10 commits June 1, 2026 05:38
…eardown

- core: push() now applies sanitizeText/sanitizeMeta to ancestor segments
  and calls sanitizeText even when text is undefined (fixes #247, #248)
- core: validate maxHistory is a non-negative integer in constructor (fixes #254)
- core: guard a11yTextExtractor aria-labelledby lookup against missing
  document in non-browser environments (fixes #257)
- react-native: useAskableVisibility and useAskableScrollView now capture
  context ownership at creation time so cleanup always destroys the right
  context regardless of later prop changes (fixes #250)
- svelte: useAskable.svelte.ts registers focus/clear listeners inside
  $effect so they are removed when the component unmounts (fixes #252)
- svelte, vue: remove wildcard @askable-ui/core from devDependencies (fixes #262)
Moves removeOverlay / state reset before the onCapture call so that
any error thrown inside the callback cannot leave a stale overlay in
the DOM. Adds a regression test that asserts the overlay is already
absent from the DOM by the time onCapture fires.
Replaces Promise.all with Promise.allSettled (plus an async wrapper to
capture synchronous throws) so a single failing sanitizeItem does not
reject the entire collection. Failed items are omitted from the result
while all healthy items still resolve. Adds tests for both synchronous
throw and async rejection isolation.
- Wrap get_current_context and format_context_for_prompt handlers in
  try/catch so a provider error returns isError:true instead of
  crashing the MCP server process.
- Remove .passthrough() from the sources array item schema so
  unrecognised fields are stripped rather than forwarded to the
  provider without validation.
The previous implementation evaluated enabled once at setup time so
toggling it later had no effect. Now accepts MaybeRef<boolean> and
uses watch({ immediate: true }) to register or unregister the source
whenever the value changes. Adds a test that toggles a ref from false
to true and back.
…Inspector

useAskableRegionCapture: onCapture and onCancel callbacks captured the
options object at start() call time. Any callback change between start()
and the pointer-up event would be silently ignored. Now always reads
from optionsRef.current so the most-recent callback fires.

AskableInspector: inspectorOptions was used inside useEffect but only
individual scalar fields were listed in the deps array. Stabilise the
options object with useMemo keyed on the same scalars so the effect dep
is a single stable reference and exhaustive-deps is satisfied.
…ector crash in observer

- Wrap querySelector in try/catch in resolveExplicitParent() so malformed
  data-askable-parent selectors (e.g. ':invalid') don't throw uncaught
  SyntaxErrors that silently break focus tracking for the element
- Replace template-literal error forwarding in get_current_context and
  format_context_for_prompt tool handlers with a generic message; raw
  provider errors (potentially containing connection strings or internal
  details) are now only written to server-side logs via console.error
- Add createAskableMcpServer test suite covering error containment,
  option forwarding, and defaultPromptFormatter fallback
- Add try/catch to resolveExplicitHierarchyParent() in context.ts to match
  the same guard added to observer.ts — an invalid data-askable-parent
  selector now silently returns null instead of propagating a SyntaxError
- Validate JSON.parse result in observer.ts parseMeta: non-plain-object
  values (numbers, booleans, arrays, null) now fall back to the raw string
  rather than being unsafely cast to Record<string, unknown>, preventing
  unexpected type confusion downstream
- Add observer tests: invalid selector no-throw, numeric JSON fallback,
  array JSON fallback
- Add context test: invalid data-askable-parent selector no-throw
…ection hook and path traversal in scaffold

React useAskableTextSelectionCapture invoked currentOptions.onCapture and
currentOptions.onCancel (stale closure captured at ensureHandle call time)
instead of optionsRef.current equivalents. The region capture hook already
used optionsRef.current correctly — this aligns the text selection hook to
match, ensuring the latest callbacks from re-renders are always invoked.

create-askable-app scaffold accepted any projectName argument including
path traversal sequences like ../../etc. Added a guard that rejects
target directories outside process.cwd(), with a matching test.
@vercel
Copy link
Copy Markdown

vercel Bot commented Jun 1, 2026

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

Project Deployment Actions Updated (UTC)
askable Ready Ready Preview, Comment Jun 1, 2026 10:16pm

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment