feat: add canny_filter_posts tool, fix SSRF vulnerability, and modernize project infrastructure#3
Conversation
…ize project infrastructure Add a new read-only MCP tool for advanced post filtering, fix a subdomain injection vulnerability, restructure documentation for npm users, and add CI/CD automation for GitHub releases. ## New Tool: canny_filter_posts Introduce `canny_filter_posts`, a read-only discovery tool that calls Canny's internal search endpoint. It supports filtering by category, company, segment, and tag slugs, plus date-range queries on post creation and vote activity -- capabilities absent from the public API. The tool uses cumulative pagination with a page cap of 10 to prevent excessive fetches. It requires `CANNY_SUBDOMAIN` to be configured (auto-detected from `CANNY_BASE_URL` when using `*.canny.io`). Files: src/tools/posts/search.ts (new), src/tools/index.ts, src/types/config.ts, src/api/client.ts, src/config/loader.ts ## Security: SSRF Subdomain Validation The `searchPosts()` method interpolates a subdomain into a URL. An explicit `CANNY_SUBDOMAIN` value like `evil.com/foo#` could redirect the request -- and the API key -- to an attacker-controlled host. Fix: validate the subdomain with `/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/i` before constructing the URL. Invalid values now throw immediately with a clear error message. File: src/api/client.ts ## Architecture: Move InternalPost to types layer Move the `InternalPost` interface from `src/api/client.ts` to `src/types/canny.ts`. All other domain types (`CannyPost`, `CannyBoard`, `CannyTag`, etc.) live in the types module; this change restores the layering convention. Files: src/types/canny.ts, src/api/client.ts ## Tests Add 13 unit tests covering the new code: - `extractSubdomain()`: 5 tests for HTTPS, HTTP, no-subdomain, non-Canny URLs, and hyphenated subdomains - `searchPosts()`: 8 tests for URL construction, `$in` status wrapping, `_id` to `id` normalization, response fallback, and subdomain validation (invalid chars, slashes, empty string, valid hyphens) Update E2E and integration test counts for the new tool. Files: tests/unit/api/client.test.ts, tests/unit/config/loader.test.ts, tests/e2e/readonly-tools.e2e.test.ts, tests/integration/tool-mode-filtering.test.ts ## Documentation Overhaul Restructure the README to lead with `npx` via Claude Code, followed by global `npm install`, then local development. All four doc files (QUICKSTART, TOOLSET_GUIDE, PROMPT_CONFIGURATION, custom_prompts) rewritten to match the source code: - Correct tool count: 24 (not 25). Remove phantom `canny_batch_tag` and `canny_batch_merge` entries that never existed in the codebase. - Correct toolset sizes: discovery 7, posts 4, engagement 6, users 4, jira 2, batch 1. - Fix env var name: `CANNY_TOOL_MODE`, not `TOOL_MODE`. - Add complete environment variable reference (22 vars across 8 categories) with defaults and descriptions. - Add `CANNY_CONFIG_PATH` to `.env.example`. Reduce total documentation from ~2,900 lines to ~950 by cutting redundancy between PROMPT_CONFIGURATION.md and custom_prompts.md. Files: README.md, docs/QUICKSTART.md, docs/TOOLSET_GUIDE.md, docs/PROMPT_CONFIGURATION.md, docs/custom_prompts.md, .env.example ## CI/CD: GitHub Actions Workflows Add two workflows: - `ci.yml`: runs tests and build on every PR and push to `main` across Node 20, 22, and 24. Never publishes. - `release.yml`: triggers on GitHub release publication. Tests across three Node versions, validates the `v`-prefixed tag matches `package.json` version, then publishes to npm with `--provenance --access public`. Requires `NPM_TOKEN` repository secret. Drop Node 18 (EOL April 2025) from the engines field. Files: .github/workflows/ci.yml (new), .github/workflows/release.yml (new), package.json ## Other - Cap `page` parameter at 10 in the filter tool's Zod schema to bound the linear pagination cost.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Add a new read-only MCP tool for advanced post filtering, fix a subdomain injection vulnerability, restructure documentation for npm users, and add CI/CD automation for GitHub releases.
New Tool: canny_filter_posts
Introduce
canny_filter_posts, a read-only discovery tool that calls Canny's internal search endpoint. It supports filtering by category, company, segment, and tag slugs, plus date-range queries on post creation and vote activity -- capabilities absent from the public API.The tool uses cumulative pagination with a page cap of 10 to prevent excessive fetches. It requires
CANNY_SUBDOMAINto be configured (auto-detected fromCANNY_BASE_URLwhen using*.canny.io).Files: src/tools/posts/search.ts (new), src/tools/index.ts, src/types/config.ts, src/api/client.ts, src/config/loader.ts
Security: SSRF Subdomain Validation
The
searchPosts()method interpolates a subdomain into a URL. An explicitCANNY_SUBDOMAINvalue likeevil.com/foo#could redirect the request -- and the API key -- to an attacker-controlled host.Fix: validate the subdomain with
/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/ibefore constructing the URL. Invalid values now throw immediately with a clear error message.File: src/api/client.ts
Architecture: Move InternalPost to types layer
Move the
InternalPostinterface fromsrc/api/client.tstosrc/types/canny.ts. All other domain types (CannyPost,CannyBoard,CannyTag, etc.) live in the types module; this change restores the layering convention.Files: src/types/canny.ts, src/api/client.ts
Tests
Add 13 unit tests covering the new code:
extractSubdomain(): 5 tests for HTTPS, HTTP, no-subdomain, non-Canny URLs, and hyphenated subdomainssearchPosts(): 8 tests for URL construction,$instatus wrapping,_idtoidnormalization, response fallback, and subdomain validation (invalid chars, slashes, empty string, valid hyphens)Update E2E and integration test counts for the new tool.
Files: tests/unit/api/client.test.ts, tests/unit/config/loader.test.ts, tests/e2e/readonly-tools.e2e.test.ts,
tests/integration/tool-mode-filtering.test.ts
Documentation Overhaul
Restructure the README to lead with
npxvia Claude Code, followed by globalnpm install, then local development. All four doc files (QUICKSTART, TOOLSET_GUIDE, PROMPT_CONFIGURATION, custom_prompts) rewritten to match the source code:canny_batch_tagandcanny_batch_mergeentries that never existed in the codebase.CANNY_TOOL_MODE, notTOOL_MODE.CANNY_CONFIG_PATHto.env.example.Reduce total documentation from ~2,900 lines to ~950 by cutting redundancy between PROMPT_CONFIGURATION.md and custom_prompts.md.
Files: README.md, docs/QUICKSTART.md, docs/TOOLSET_GUIDE.md, docs/PROMPT_CONFIGURATION.md, docs/custom_prompts.md, .env.example
CI/CD: GitHub Actions Workflows
Add two workflows:
ci.yml: runs tests and build on every PR and push tomainacross Node 20, 22, and 24. Never publishes.release.yml: triggers on GitHub release publication. Tests across three Node versions, validates thev-prefixed tag matchespackage.jsonversion, then publishes to npm with--provenance --access public. RequiresNPM_TOKENrepository secret.Drop Node 18 (EOL April 2025) from the engines field.
Files: .github/workflows/ci.yml (new), .github/workflows/release.yml (new), package.json
Other
pageparameter at 10 in the filter tool's Zod schema to bound the linear pagination cost.