Skip to content

feat: add canny_filter_posts tool, fix SSRF vulnerability, and modernize project infrastructure#3

Merged
Ompragash merged 2 commits intomainfrom
new-tool
Feb 19, 2026
Merged

feat: add canny_filter_posts tool, fix SSRF vulnerability, and modernize project infrastructure#3
Ompragash merged 2 commits intomainfrom
new-tool

Conversation

@Ompragash
Copy link
Contributor

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.

…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.
@Ompragash Ompragash merged commit 6dee1e9 into main Feb 19, 2026
3 checks passed
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.

1 participant