From 60e87cb9de8f1c34c7f08f4956de6842593b00c7 Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Tue, 2 Jun 2026 15:17:40 +0530 Subject: [PATCH 1/6] mcb --- mcpb/manifest.json | 74 ++++++++++++++++++++++++++++++++++++++++-- src/mcp/server.js | 8 +---- src/tools/globTool.js | 2 ++ src/tools/grepTool.js | 2 ++ src/tools/lsTool.js | 2 ++ src/tools/pathUtils.js | 9 +++++ src/tools/readTool.js | 2 ++ 7 files changed, 89 insertions(+), 10 deletions(-) create mode 100644 src/tools/pathUtils.js diff --git a/mcpb/manifest.json b/mcpb/manifest.json index 6a2dd9c..e9a1a0a 100644 --- a/mcpb/manifest.json +++ b/mcpb/manifest.json @@ -4,7 +4,7 @@ "display_name": "CodeAnt AI", "version": "0.5.0", "description": "Drive CodeAnt AI security scans and code review from Claude — org-wide secret triage, cross-repo SAST/SCA findings, on-demand scans, and local PR review.", - "long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai and grab an API key from your settings page.", + "long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai and grab an API key from your settings page.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.", "author": { "name": "CodeAnt AI", "email": "support@codeant.ai", @@ -28,7 +28,7 @@ "pull-requests" ], "privacy_policies": [ - "https://codeant.ai/privacy" + "https://www.codeant.ai/privacy-policy" ], "icon": "icon.png", "server": { @@ -40,7 +40,15 @@ "env": { "CODEANT_API_TOKEN": "${user_config.api_token}", "CODEANT_API_URL": "${user_config.base_url}", - "CODEANT_READ_ONLY": "${user_config.read_only}" + "CODEANT_READ_ONLY": "${user_config.read_only}", + "GITHUB_TOKEN": "${user_config.github_token}", + "GITLAB_TOKEN": "${user_config.gitlab_token}", + "BITBUCKET_TOKEN": "${user_config.bitbucket_token}", + "AZURE_DEVOPS_TOKEN": "${user_config.azure_devops_token}", + "GITHUB_API_URL": "${user_config.github_api_url}", + "GITLAB_URL": "${user_config.gitlab_url}", + "BITBUCKET_URL": "${user_config.bitbucket_url}", + "AZURE_DEVOPS_ORG_URL": "${user_config.azure_devops_org_url}" } } }, @@ -88,6 +96,66 @@ "description": "When enabled, write tools (trigger scan, resolve PR thread) are hidden. Recommended.", "required": false, "default": true + }, + "github_token": { + "type": "string", + "title": "GitHub token", + "description": "Personal access token for GitHub. Also accepts the GH_TOKEN environment variable. Leave blank to fall back to the gh CLI.", + "required": false, + "sensitive": true, + "default": "" + }, + "gitlab_token": { + "type": "string", + "title": "GitLab token", + "description": "Personal access token for GitLab. Leave blank to fall back to the glab CLI.", + "required": false, + "sensitive": true, + "default": "" + }, + "bitbucket_token": { + "type": "string", + "title": "Bitbucket token", + "description": "App password or access token for Bitbucket.", + "required": false, + "sensitive": true, + "default": "" + }, + "azure_devops_token": { + "type": "string", + "title": "Azure DevOps token", + "description": "Personal access token for Azure DevOps. Also accepts AZURE_DEVOPS_PAT.", + "required": false, + "sensitive": true, + "default": "" + }, + "github_api_url": { + "type": "string", + "title": "GitHub API URL", + "description": "Override for GitHub Enterprise Server (e.g. https://github.example.com/api/v3). Leave blank for GitHub.com.", + "required": false, + "default": "" + }, + "gitlab_url": { + "type": "string", + "title": "GitLab URL", + "description": "Override for a self-hosted GitLab instance (e.g. https://gitlab.example.com). Leave blank for GitLab.com.", + "required": false, + "default": "" + }, + "bitbucket_url": { + "type": "string", + "title": "Bitbucket URL", + "description": "Override for a self-hosted Bitbucket Server instance. Leave blank for Bitbucket Cloud.", + "required": false, + "default": "" + }, + "azure_devops_org_url": { + "type": "string", + "title": "Azure DevOps organization URL", + "description": "Full URL to your Azure DevOps organization (e.g. https://dev.azure.com/myorg).", + "required": false, + "default": "" } } } diff --git a/src/mcp/server.js b/src/mcp/server.js index d48f254..595dd18 100644 --- a/src/mcp/server.js +++ b/src/mcp/server.js @@ -73,13 +73,7 @@ async function ensureAuthenticated() { return; } - console.error('[codeant-mcp] No API token configured — opening browser for sign-in.'); - try { - await runLoginFlow(); - console.error('[codeant-mcp] Login complete.'); - } catch (err) { - console.error(`[codeant-mcp] Login failed: ${err.message}. The server will start anyway; call the codeant_login tool to retry.`); - } + console.error('[codeant-mcp] No API token configured. Call the codeant_login tool to sign in, or set CODEANT_API_TOKEN.'); } export async function startMcpServer() { diff --git a/src/tools/globTool.js b/src/tools/globTool.js index 6b83bb3..b4324aa 100644 --- a/src/tools/globTool.js +++ b/src/tools/globTool.js @@ -1,8 +1,10 @@ import path from 'path'; +import { assertInsideCwd } from './pathUtils.js'; export async function globTool(args, cwd) { const { globSync } = await import('glob'); const pattern = path.resolve(cwd, args.pattern); + assertInsideCwd(pattern.replace(/[*?{[\\].*$/, '') || cwd, cwd); const matches = globSync(pattern); if (!matches.length) return 'No files found'; return matches.map(m => path.relative(cwd, m)).join('\n'); diff --git a/src/tools/grepTool.js b/src/tools/grepTool.js index 5abe980..414ecc3 100644 --- a/src/tools/grepTool.js +++ b/src/tools/grepTool.js @@ -1,8 +1,10 @@ import { spawn } from 'child_process'; import path from 'path'; +import { assertInsideCwd } from './pathUtils.js'; export async function grepTool(args, cwd) { const target = args.path ? path.resolve(cwd, args.path) : cwd; + assertInsideCwd(target, cwd); const result = await new Promise((resolve) => { const proc = spawn('grep', ['-rn', args.pattern, target], { cwd, diff --git a/src/tools/lsTool.js b/src/tools/lsTool.js index e110408..bdae7e5 100644 --- a/src/tools/lsTool.js +++ b/src/tools/lsTool.js @@ -1,7 +1,9 @@ import fs from 'fs'; import path from 'path'; +import { assertInsideCwd } from './pathUtils.js'; export async function lsTool(args, cwd) { const dirPath = args.path ? path.resolve(cwd, args.path) : cwd; + assertInsideCwd(dirPath, cwd); return fs.readdirSync(dirPath).sort().join('\n'); } diff --git a/src/tools/pathUtils.js b/src/tools/pathUtils.js new file mode 100644 index 0000000..2f3c24d --- /dev/null +++ b/src/tools/pathUtils.js @@ -0,0 +1,9 @@ +import path from 'path'; + +export function assertInsideCwd(resolved, cwd) { + const base = path.resolve(cwd); + const target = path.resolve(resolved); + if (target !== base && !target.startsWith(base + path.sep)) { + throw new Error(`Access denied: path is outside the working directory`); + } +} diff --git a/src/tools/readTool.js b/src/tools/readTool.js index 333a451..b0caa39 100644 --- a/src/tools/readTool.js +++ b/src/tools/readTool.js @@ -1,8 +1,10 @@ import fs from 'fs'; import path from 'path'; +import { assertInsideCwd } from './pathUtils.js'; export async function readTool(args, cwd) { const filePath = path.resolve(cwd, args.file_path); + assertInsideCwd(filePath, cwd); const content = await fs.promises.readFile(filePath, 'utf8'); const lines = content.split('\n'); const offset = args.offset || 1; From 3a0796fda8e9f6c3dc8eeeeb29bb4caa4bccbdd9 Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Tue, 2 Jun 2026 15:54:30 +0530 Subject: [PATCH 2/6] codeant-cli: PostHog project API key (phc_*) should not be flagged as a secret COD-497 --- src/rules/secrets.toml | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/src/rules/secrets.toml b/src/rules/secrets.toml index f19b9b8..874f79b 100644 --- a/src/rules/secrets.toml +++ b/src/rules/secrets.toml @@ -4071,16 +4071,6 @@ regex = '''(?i)\b(phx_[a-zA-Z0-9_\-]{47})(?:\\?['"\x60]|[\s;]|\\[nr]|$)''' entropy = 3 keywords = ["phx_"] -# ────────────────────────────────────────────────────────────────────────────── -# posthog-project-api-key -# ────────────────────────────────────────────────────────────────────────────── -[[rules]] -id = "posthog-project-api-key" -description = "Detected a PostHog Project API Key, which may expose product analytics data and event tracking to unauthorized access." -regex = '''(?i)\b(phc_[a-zA-Z0-9_\-]{43})(?:\\?['"\x60]|[\s;]|\\[nr]|$)''' -entropy = 3 -keywords = ["phc_"] - # ────────────────────────────────────────────────────────────────────────────── # postman-api-token # ────────────────────────────────────────────────────────────────────────────── From e6b5fd688dd969e59d701859a20912a69c554f9a Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Tue, 2 Jun 2026 16:12:47 +0530 Subject: [PATCH 3/6] manifest --- mcpb/manifest.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcpb/manifest.json b/mcpb/manifest.json index e9a1a0a..3b2f0da 100644 --- a/mcpb/manifest.json +++ b/mcpb/manifest.json @@ -4,7 +4,7 @@ "display_name": "CodeAnt AI", "version": "0.5.0", "description": "Drive CodeAnt AI security scans and code review from Claude — org-wide secret triage, cross-repo SAST/SCA findings, on-demand scans, and local PR review.", - "long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai and grab an API key from your settings page.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.", + "long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai. To authenticate, call the `codeant_login` tool — it opens the CodeAnt sign-in page in your browser and saves the token automatically.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.", "author": { "name": "CodeAnt AI", "email": "support@codeant.ai", @@ -78,7 +78,7 @@ "api_token": { "type": "string", "title": "CodeAnt API token", - "description": "Optional. Leave blank and run the `codeant_login` tool to sign in through your browser instead. Otherwise paste a token from your CodeAnt account settings at app.codeant.ai.", + "description": "Optional. Leave blank and call the `codeant_login` tool to sign in through your browser.", "required": false, "sensitive": true, "default": "" From a2bd71d7098fd668a9ccf86e2fed9e9bac25e3c2 Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Mon, 1 Jun 2026 15:50:31 +0530 Subject: [PATCH 4/6] stuff --- .gitignore | 1 + src/components/ScanCenter.js | 2 +- src/scanCenter/handleSelectRepo.js | 10 ++++++++-- src/utils/fetchApi.js | 2 +- 4 files changed, 11 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index 385ac5a..a0bcf22 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ Thumbs.db # Build output dist/ build/ +tmp/ # Yarn lock (if using npm) yarn.lock diff --git a/src/components/ScanCenter.js b/src/components/ScanCenter.js index d6cdf15..b7c043e 100644 --- a/src/components/ScanCenter.js +++ b/src/components/ScanCenter.js @@ -190,7 +190,7 @@ export default function ScanCenter({ filterDismissed = false, includeFalsePositi // ── Step 3: repo selected → fetch scan history ── const handleSelectRepo = (item) => - _handleSelectRepo({ STEPS, item, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }); + _handleSelectRepo({ STEPS, item, selectedConnection, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }); // ── Step 4: scan selected → show result type menu ── const handleSelectScan = (item) => diff --git a/src/scanCenter/handleSelectRepo.js b/src/scanCenter/handleSelectRepo.js index f8559c1..866d3f9 100644 --- a/src/scanCenter/handleSelectRepo.js +++ b/src/scanCenter/handleSelectRepo.js @@ -1,9 +1,15 @@ import { getScanHistory } from '../scans/getScanHistory.js'; -export async function handleSelectRepo({ STEPS, item, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }) { +export async function handleSelectRepo({ STEPS, item, selectedConnection, setSelectedRepo, setStep, setLoadingMsg, setError, setScanHistory }) { setSelectedRepo(item.value); setStep(STEPS.LOADING); - const repoFullName = item.value.full_name || item.value.name; + const orgName = selectedConnection?.organizationName; + const repoName = item.value.name; + const repoFullName = item.value.full_name || (orgName && repoName ? `${orgName}/${repoName}` : repoName); + if (!repoFullName || !repoFullName.includes('/')) { + setError(`Cannot resolve repository in org/repo form (got "${repoFullName}")`, STEPS.SELECT_REPO); + return; + } setLoadingMsg(`Loading scan history for ${repoFullName}…`); const res = await getScanHistory(repoFullName); if (!res.success) { diff --git a/src/utils/fetchApi.js b/src/utils/fetchApi.js index 1415a28..d85e1a2 100644 --- a/src/utils/fetchApi.js +++ b/src/utils/fetchApi.js @@ -49,7 +49,7 @@ const fetchApi = async (endpoint, method = 'GET', body = null) => { for (let attempt = 1; attempt <= MAX_ATTEMPTS; attempt++) { try { const response = await fetch(url, options); - console.error('API Response Status:', response.status); + // console.error('API Response Status:', response.status); if (response.status === 403) { throw new Error('Access denied (403). Please run `codeant logout` and then `codeant login` to re-authenticate.'); From 8fa741fc28d454ebbe9643c8818d7a6c0c00408e Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Wed, 3 Jun 2026 16:16:58 +0530 Subject: [PATCH 5/6] add logout --- src/mcp/server.js | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/mcp/server.js b/src/mcp/server.js index 595dd18..b53693e 100644 --- a/src/mcp/server.js +++ b/src/mcp/server.js @@ -13,7 +13,7 @@ import { runStartScan } from '../commands/scans/start-scan.js'; import { runReviewHeadless } from '../reviewHeadless.js'; import * as scm from '../scm/index.js'; import { isAlreadyLoggedIn, runLoginFlow } from '../utils/loginFlow.js'; -import { getConfigValue } from '../utils/config.js'; +import { getConfigValue, setConfigValue } from '../utils/config.js'; const require = createRequire(import.meta.url); const pkg = require('../../package.json'); @@ -409,6 +409,24 @@ export async function startMcpServer() { } ); + server.registerTool( + 'codeant_logout', + { + title: 'Sign out of CodeAnt AI', + description: 'Clears the saved API token from ~/.codeant/config.json and unsets CODEANT_API_TOKEN on the running MCP process. Returns { wasLoggedIn: false } immediately if no token was configured.', + inputSchema: {}, + annotations: { ...WRITE_NON_DESTRUCTIVE, idempotentHint: true }, + }, + async () => { + try { + const wasLoggedIn = !!(process.env.CODEANT_API_TOKEN?.trim() || getConfigValue('apiKeyV2')); + setConfigValue('apiKeyV2', null); + delete process.env.CODEANT_API_TOKEN; + return ok({ wasLoggedIn, status: wasLoggedIn ? 'logged_out' : 'not_logged_in' }); + } catch (err) { return fail(err); } + } + ); + // ─── Write-side tools (gated behind CODEANT_READ_ONLY=0) ───────────────── if (!readOnly) { server.registerTool( From 0f4255095c0604578db699c626ba3eda5d2150f1 Mon Sep 17 00:00:00 2001 From: Pranav Viswanatha Date: Wed, 3 Jun 2026 16:54:10 +0530 Subject: [PATCH 6/6] publish --- .github/workflows/publish.yml | 16 +++++----------- mcpb/manifest.json | 3 ++- 2 files changed, 7 insertions(+), 12 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 1590cb5..86af13e 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,14 +17,18 @@ jobs: - uses: actions/setup-node@v4 with: node-version: '20' + registry-url: 'https://registry.npmjs.org' - id: version run: echo "version=$(jq -r .version package.json)" >> "$GITHUB_OUTPUT" - run: npm ci + - run: npm publish --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + - run: npm pack - # produces codeant-cli-.tgz in the working directory - uses: actions/upload-artifact@v4 with: @@ -43,13 +47,3 @@ jobs: Commit: ${{ github.sha }} Message: ${{ github.event.head_commit.message }} - - ## Publish to npm - - Download the `.tgz` and run: - - ``` - npm publish codeant-cli-${{ steps.version.outputs.version }}.tgz --access public - ``` - - (Requires npm auth with publish rights on the `codeant-cli` package.) diff --git a/mcpb/manifest.json b/mcpb/manifest.json index 3b2f0da..1ea5079 100644 --- a/mcpb/manifest.json +++ b/mcpb/manifest.json @@ -2,7 +2,7 @@ "manifest_version": "0.3", "name": "codeant", "display_name": "CodeAnt AI", - "version": "0.5.0", + "version": "0.5.1", "description": "Drive CodeAnt AI security scans and code review from Claude — org-wide secret triage, cross-repo SAST/SCA findings, on-demand scans, and local PR review.", "long_description": "CodeAnt AI inside Claude. Ask things like \"how many critical SAST findings do I have across my org?\", \"show every exposed secret in payments-service\", or \"review my staged changes\" — Claude calls the CodeAnt API directly via this MCP server.\n\nIncludes 11 read-only tools (orgs, repos, scan history, scan metadata, findings, dismissed alerts, PRs, comments, comment search, local review) and 2 opt-in write tools (trigger a scan, resolve a PR conversation) gated behind a setting.\n\nRequires a CodeAnt account. Sign up at https://codeant.ai. To authenticate, call the `codeant_login` tool — it opens the CodeAnt sign-in page in your browser and saves the token automatically.\n\nCollects anonymous usage telemetry via PostHog by default; set CODEANT_TELEMETRY_DISABLED=1 to opt out.", "author": { @@ -71,6 +71,7 @@ { "name": "codeant_comments_search", "description": "Search across CodeAnt review comments by free-text query." }, { "name": "codeant_review_local", "description": "Run a CodeAnt AI review on local working-copy changes." }, { "name": "codeant_login", "description": "Open app.codeant.ai in the browser and poll until the user completes sign-in; saves the resulting API token." }, + { "name": "codeant_logout", "description": "Clear the saved API token and sign out of CodeAnt AI." }, { "name": "codeant_scans_start", "description": "Trigger a new scan run (write — gated behind read_only=false)." }, { "name": "codeant_pr_resolve", "description": "Resolve a PR conversation thread (write — gated behind read_only=false)." } ],