Skip to content

Private/issue 307 hono subrouter route detection - #1479

Draft
ljluestc wants to merge 3 commits into
colbymchenry:mainfrom
ljluestc:private/issue-307-hono-subrouter-route-detection
Draft

Private/issue 307 hono subrouter route detection#1479
ljluestc wants to merge 3 commits into
colbymchenry:mainfrom
ljluestc:private/issue-307-hono-subrouter-route-detection

Conversation

@ljluestc

Copy link
Copy Markdown

PR: Detect Hono sub-router routes and mounted prefixes (issue #307)

Branch: private/issue-307-hono-subrouter-route-detection
Compare: colbymchenry/codegraph:mainljluestc/codegraph:private/issue-307-hono-subrouter-route-detection
Linked issue: #307 (Hono sub-router detection)
Bundled work: #383 (optional MCP output sanitization)

Summary

This PR extends the framework resolver to correctly detect Hono sub-router routes and to compute full route paths across files when sub-routers are mounted with a prefix. It also bundles a related privacy-hardening change that adds an optional sanitization layer for MCP tool output (#383), so a single PR ships both workflow improvements together.

In concrete terms, this PR:

  • Adds Hono as a recognized framework dependency (alongside Express), since Hono's app.get / app.route surface is compatible with the existing expressResolver.
  • Generalizes the route-extraction receiver regex so routes can hang off any identifier (e.g. userRoutes.get('/me', ...)) rather than only the literal app / router names.
  • Introduces a postExtract cross-file pass that walks JS/TS source files, resolves relative imports (./routes/users), and rewrites sub-router route names to include the parent's mount prefix — so app.route('/users', userRoutes) + userRoutes.get('/', listUsers) yields GET /users (not GET /).
  • Adds an optional, opt-in sanitization layer for MCP tool output: a built-in redaction pass (--sanitize) for common PII/secrets, plus a custom-hook escape hatch (--sanitize-hook <module>) for organization-specific policies or external sanitizers.

Problem

Issue #307 — Hono routes and mount prefixes are not visible

expressResolver.extract was indexed on the literal names app and router, so two common Hono patterns produced incomplete (or missing) routes:

  1. Sub-routers stored in user-named variables. A Hono project typically writes

    export const userRoutes = new Hono()
    userRoutes.get('/', listUsers)

    Without dynamic receiver detection, the GET / handler is dropped from the route index entirely.

  2. Sub-routers mounted with a prefix. With

    app.route('/users', userRoutes)

    the resolved route is GET /users, not GET /. There was no cross-file pass that would first map each Hono() instance to its file, then propagate the mount prefix from app.route(prefix, child) calls.

The end result: code browsing and "what calls this endpoint" tooling missed Hono sub-router routes, and mount prefixes were never reflected in route names.

Issue #383 — Sensitive content may leak through MCP tool output

Indexing a codebase and exposing it through MCP can surface strings that include emails, phone numbers, SSNs, credit-card-like values, or secret-like API keys. There was no pre-LLM redaction layer in the MCP response pipeline; users in regulated environments had no built-in way to enforce redaction without giving up MCP.

Solution

1) Hono detection + general receiver support in src/resolution/frameworks/express.ts

  • Treat hono in package.json dependencies as a framework dependency (the resolver is now explicitly multi-framework).
  • Accept Hono-style signals during per-file content scanning: content.includes('hono'), content.includes('new Hono('), content.includes('.route(').
  • Generalize the route-head regex so it captures any valid JS/TS identifier as the receiver, not only app/router:
    • /\b([A-Za-z_$][\w$]*)\.(get|post|put|patch|delete|all|use)\s*\(\s*['"]([^'"]+)['"]\s*,/g

2) Cross-file postExtract pass

After per-file extraction runs, a new postExtract step:

  • Walks every JS/TS file in the repo.
  • Maps each Hono() (or express()) invocation to a file path and variable name (the "owner").
  • Collects parent.route('/prefix', childRouter) mounts across all files.
  • Resolves each childRouter import to its owning file via relative-import resolution (with .ts / .tsx / .js / .jsx / /index.* candidate extensions), so the prefix can be propagated across files.
  • Rewrites sub-router route names to include the mount prefix, idempotently (running the pass twice yields the same result).

New helpers introduced in the same file:

  • parseRouteFromQualifiedName — splits a stored qualified name into method + path.
  • joinRoutePath — joins a parent prefix path with a child route path, normalizing leading/trailing slashes.
  • resolveImportedFile — relative-import resolution with multiple candidate extensions.

3) MCP sanitization layer (src/mcp/sanitization.ts, new)

  • Exposes builtInSanitizationEnabled(), sanitizeText(), and sanitizeToolResult().
  • Built-in redaction covers: emails, US SSNs, phone numbers, credit-card-like values (Luhn-validated to reduce false positives), OpenAI-style API keys, and AWS access key IDs.
  • Redaction tokens used: [REDACTED_EMAIL], [REDACTED_PHONE], [REDACTED_SSN], [REDACTED_CREDIT_CARD], [REDACTED_API_KEY].
  • Hook contract: a user-provided JS/TS module exporting a function (default export or named sanitize) with signature (text: string) => string | Promise<string>. The hook is loaded once and cached; load/exec failures are reported to stderr ([CodeGraph MCP] Failed to load CODEGRAPH_SANITIZE_HOOK: ..., [CodeGraph MCP] CODEGRAPH_SANITIZE_HOOK execution failed: ...) without crashing the MCP server.

4) Wire sanitization into codegraph serve --mcp

  • New CLI flags on src/bin/codegraph.ts:
    • --sanitize — propagate CODEGRAPH_SANITIZE=1.
    • --sanitize-hook <modulePath> — propagate CODEGRAPH_SANITIZE_HOOK=<absolute-path>.
  • ToolHandler.execute() in src/mcp/tools.ts now applies sanitizeToolResult() to every content block of the final tool result, after the existing worktree / staleness wrappers, so redaction happens exactly once and is consistent across all tool payloads.

Changes

Production code

File Change
src/resolution/frameworks/express.ts Add Hono dependency/content signals; generalize route-head regex; add cross-file postExtract pass; add parseRouteFromQualifiedName, joinRoutePath, resolveImportedFile helpers.
src/mcp/sanitization.ts (new) Built-in redaction + custom-hook support; env-gated (CODEGRAPH_SANITIZE, CODEGRAPH_SANITIZE_HOOK).
src/mcp/tools.ts Apply sanitizeToolResult to ToolHandler.execute output.
src/bin/codegraph.ts Add --sanitize and --sanitize-hook <modulePath> to codegraph serve.

Tests

File Change
__tests__/frameworks.test.ts Add extracts routes from non-app/router receivers (e.g. Hono sub-router vars) — verifies userRoutes.get('/me', getMe) produces GET /me and a reference named getMe.
__tests__/frameworks-integration.test.ts Add Hono end-to-end framework extraction describe block: end-to-end indexing of routes/users.ts + main.ts (with app.route('/users', userRoutes)) and assertion that the route set contains GET /health and GET /users.
__tests__/mcp-sanitization.test.ts (new) Built-in redaction behavior; env-gated opt-in through MCP responses; disabled-mode passthrough; custom hook execution.

Documentation

File Change
README.md Add MCP sanitization section under installation; new codegraph serve --mcp --sanitize example under command reference.
site/src/content/docs/reference/cli.md Document --sanitize and --sanitize-hook flags.
site/src/content/docs/reference/mcp-server.md Document MCP privacy hardening and the hook contract.
CHANGELOG.md Add [Unreleased] / New Features entry for --sanitize / --sanitize-hook.

Tooling housekeeping

File Change
(none on disk) The branch's final commit (91bd045) removes an PR_383_DESCRIPTION_LOCAL.md that was accidentally committed earlier on this branch. No content change results on main.

Test plan

Local validation commands:

  1. Build the TypeScript output:

    npm run build
  2. Run the framework tests (unit + integration):

    npx vitest run __tests__/frameworks.test.ts __tests__/frameworks-integration.test.ts
  3. Run the MCP sanitization tests:

    npx vitest run __tests__/mcp-sanitization.test.ts
  4. Run the broader MCP test suite to confirm no regression in adjacent pipeline behavior:

    npx vitest run __tests__/mcp-tool-allowlist.test.ts

Manual smoke checks:

  • codegraph serve --mcp --sanitize then ask an MCP client to read a file containing an email — the response should contain [REDACTED_EMAIL].
  • codegraph serve --mcp --sanitize-hook ./my-hook.cjs where my-hook.cjs exports a function that wraps customer_id=... as [REDACTED_CUSTOMER_ID] — verify the hook's redaction is applied.
  • In a small Hono fixture (main.ts with app.route('/users', userRoutes) + routes/users.ts exporting userRoutes.get('/', listUsers)), run cg indexAll() and confirm getNodesByKind('route') includes both GET /health and GET /users.

Documentation updates

  • README.md — installed-binary callers see the new ### MCP sanitization (optional) block and an updated command-list entry.
  • site/src/content/docs/reference/cli.md--sanitize and --sanitize-hook are now part of the published CLI reference.
  • site/src/content/docs/reference/mcp-server.md — privacy-hardening paragraph and hook-contract description.
  • CHANGELOG.md[Unreleased] / New Features entry.

Backward compatibility

  • Default behavior is unchanged for both changes:
    • Hono detection is purely additive — existing Express projects see no change in resolved routes.
    • MCP sanitization is strictly opt-in (requires --sanitize or CODEGRAPH_SANITIZE=1); no existing MCP workflow is altered by default.
  • The custom hook loader reports failures to stderr rather than crashing the server, so a malformed hook can't take down an MCP session.
  • Route-name rewrites in postExtract are idempotent — running extraction twice yields the same route names.

Issue linkage

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.

Hono framework sub-router routes not detected (~82% miss rate)

1 participant