High risk. Don't ship without significant remediation.
Scanned 5/15/2026, 5:43:49 PMΒ·Cached resultΒ·Deep ScanΒ·91 rulesΒ·View source βΒ·How we decide β
AIVSS Score
High
Severity Breakdown
0
critical
13
high
2
medium
1
low
MCP Server Information
Findings
This package carries significant security concerns with a D grade and 67/100 safety score, driven primarily by 13 high-severity vulnerabilities spanning prompt injection and tool poisoning risks. The prompt injection findings (8 total) suggest the server could be manipulated through crafted inputs to execute unintended operations, while tool poisoning vulnerabilities (5 total) indicate potential for malicious tool behavior or data corruption. You should address these high-severity issues before deployment, particularly the prompt injection vectors which represent the most prevalent threat category.
AIPer-finding remediation generated by bedrock-claude-haiku-4-5 β 11 of 16 findings. Click any finding to read.
No known CVEs found for this package or its dependencies.
Scan Details
Done
Sign in to save scan history and re-scan automatically on new commits.
Building your own MCP server?
Same rules, same LLM judges, same grade. Private scans stay isolated to your account and never appear in the public registry. Required for code your team hasnβt shipped yet.
16 of 16 findings
16 findings
admin subcommand 'grant-access <email>' accepts email identifier and modifies user access via REST API without verifying caller authorization; missing ownership/admin scope check in handler
Evidence
| 1 | /** |
| 2 | * admin β CLI admin subcommands for gramatr user/org/team management. |
| 3 | * |
| 4 | * Calls the server REST API admin endpoints with local auth credentials. |
RemediationAI
The problem is that the 'restore-access' handler in admin.js calls the REST API to restore user access without checking whether the caller has admin or ownership scope. Add a scope validation check before the API call by calling `requireAdminScope(callerContext)` that verifies the caller's JWT or session token contains 'admin' or 'owner' scope; if not, throw an AuthorizationError. This ensures only authorized users can restore access. Verify the fix by running the CLI with a non-admin user token and confirming the command returns a 403 Forbidden error.
admin subcommand 'revoke-access <email>' accepts email identifier and revokes user access via REST API without verifying caller authorization; missing ownership/admin scope check in handler
Evidence
| 1 | /** |
| 2 | * admin β CLI admin subcommands for gramatr user/org/team management. |
| 3 | * |
| 4 | * Calls the server REST API admin endpoints with local auth credentials. |
RemediationAI
The problem is that the 'restore-access' handler in admin.js calls the REST API to restore user access without checking whether the caller has admin or ownership scope. Add a scope validation check before the API call by calling `requireAdminScope(callerContext)` that verifies the caller's JWT or session token contains 'admin' or 'owner' scope; if not, throw an AuthorizationError. This ensures only authorized users can restore access. Verify the fix by running the CLI with a non-admin user token and confirming the command returns a 403 Forbidden error.
admin subcommand 'assign-team <email>' accepts email identifier and modifies team membership via REST API without verifying caller authorization; missing ownership/admin scope check in handler
Evidence
| 1 | /** |
| 2 | * admin β CLI admin subcommands for gramatr user/org/team management. |
| 3 | * |
| 4 | * Calls the server REST API admin endpoints with local auth credentials. |
RemediationAI
The problem is that the 'restore-access' handler in admin.js calls the REST API to restore user access without checking whether the caller has admin or ownership scope. Add a scope validation check before the API call by calling `requireAdminScope(callerContext)` that verifies the caller's JWT or session token contains 'admin' or 'owner' scope; if not, throw an AuthorizationError. This ensures only authorized users can restore access. Verify the fix by running the CLI with a non-admin user token and confirming the command returns a 403 Forbidden error.
admin subcommand 'user-detail <email>' accepts email identifier and fetches user record via REST API without verifying caller authorization; missing ownership/admin scope check in handler
Evidence
| 1 | /** |
| 2 | * admin β CLI admin subcommands for gramatr user/org/team management. |
| 3 | * |
| 4 | * Calls the server REST API admin endpoints with local auth credentials. |
RemediationAI
The problem is that the 'restore-access' handler in admin.js calls the REST API to restore user access without checking whether the caller has admin or ownership scope. Add a scope validation check before the API call by calling `requireAdminScope(callerContext)` that verifies the caller's JWT or session token contains 'admin' or 'owner' scope; if not, throw an AuthorizationError. This ensures only authorized users can restore access. Verify the fix by running the CLI with a non-admin user token and confirming the command returns a 403 Forbidden error.
admin subcommand 'restore-access <email>' accepts email identifier and restores user access via REST API without verifying caller authorization; missing ownership/admin scope check in handler
Evidence
| 1 | /** |
| 2 | * admin β CLI admin subcommands for gramatr user/org/team management. |
| 3 | * |
| 4 | * Calls the server REST API admin endpoints with local auth credentials. |
RemediationAI
The problem is that the 'restore-access' handler in admin.js calls the REST API to restore user access without checking whether the caller has admin or ownership scope. Add a scope validation check before the API call by calling `requireAdminScope(callerContext)` that verifies the caller's JWT or session token contains 'admin' or 'owner' scope; if not, throw an AuthorizationError. This ensures only authorized users can restore access. Verify the fix by running the CLI with a non-admin user token and confirming the command returns a 403 Forbidden error.
adminFetch() uses module-level API_KEY credential (from process.env at import time) to call privileged /api/v1 admin endpoints without consulting per-request caller identity.
Evidence
| 37 | // gramatr-allow: c1 |
| 38 | const API_KEY_LEGACY = process.env['GMTR_API_KEY'] || ''; |
| 39 | const API_KEY = API_KEY_PRIMARY || API_KEY_LEGACY; |
| 40 | const cliEnv = { home: HOME_DIR, serverUrl: SERVER_URL, apiKey: API_KEY }; |
| 41 | // ---- Config helpers -------------------------------------------------------- |
| 42 | function getServerBase() { |
| 43 | return cliEnv.serverUrl.replace(/\/mcp\/?$/, ''); |
RemediationAI
The problem is that adminFetch() uses a static module-level API_KEY credential loaded at import time, which means all requests are authenticated as a single privileged identity regardless of who invoked the CLI command. Replace the static API_KEY with a per-request credential by modifying adminFetch() to accept a callerCredential parameter and pass it in the Authorization header instead of using the module-level API_KEY constant. This ensures each request is authenticated as the actual caller, not a shared service account. Verify the fix by logging the Authorization header value in adminFetch() and confirming it reflects the current user's token, not a static key.
validateAgainstServer() uses a user-supplied API key to call SERVER_BASE/mcp endpoint, but the key validation is performed without verifying caller authorization to use that key.
Evidence
| 57 | export async function readInteractive() { |
| 58 | log(''); |
| 59 | log('Paste your gramatr API key below.'); |
| 60 | log('(keys usually start with sk_)'); |
| 61 | log(''); |
| 62 | process.stdout.write(' Key: '); |
| 63 | const rl = createInterface({ input: process.stdin, output: process.stdout }); |
RemediationAI
The problem is that validateAgainstServer() accepts a user-supplied API key and validates it against the server without verifying that the caller is authorized to use that key (e.g., the key belongs to the caller's account). Add a caller identity check by requiring the caller to provide their own credentials (JWT or session token) and verify that the supplied API key is registered to that caller's account before validation. This prevents users from validating or using API keys that belong to other users. Verify the fix by attempting to validate an API key belonging to a different user account and confirming the function returns an authorization error.
Function clearAll() performs FILESYSTEM side effects (unlinkSync deletes ~/.gramatr.json and settings.json) not disclosed in function signature.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | import { existsSync, unlinkSync } from 'fs'; |
| 3 | import { join } from 'path'; |
| 4 | import { getGramatrDirFromEnv, getHomeDir } from '../config-runtime.js'; |
RemediationAI
The problem is that clearAll() in clear-creds.js deletes sensitive files (~/.gramatr.json and settings.json) but the function signature and documentation do not disclose these filesystem side effects. Add JSDoc documentation to the clearAll() function with an @sideEffect tag listing the files deleted, and rename the function to clearAllCredentialsAndSettings() to make the destructive intent explicit in the name. This makes the side effects visible to callers and reviewers. Verify the fix by checking that the JSDoc appears in IDE tooltips and that the function name clearly indicates file deletion.
Function main() performs NETWORK side effect (validateAgainstServer calls fetch to SERVER_BASE) and FILESYSTEM side effect (writeKey writes credentials) not disclosed in function documentation.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | import { chmodSync, existsSync, readFileSync, writeFileSync } from 'fs'; |
| 3 | import { join } from 'path'; |
| 4 | import { createInterface } from 'readline'; |
RemediationAI
The problem is that writeKey() in add-api-key.js writes sensitive credentials to ~/.gramatr.json but the function signature and documentation do not disclose this filesystem side effect. Add JSDoc documentation to writeKey() with an @sideEffect tag explicitly stating that it writes the API key to ~/.gramatr.json with restricted file permissions, and update the function name to writeApiKeyToFile() to make the intent explicit. This makes the side effect visible to callers and reviewers. Verify the fix by checking that the JSDoc appears in IDE tooltips and that the function name clearly indicates file writing.
Function buildMcpb() performs FILESYSTEM side effects (ensureDir, writeManifest, copyRuntimeFiles, buildArchive) not disclosed in function documentation.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | import { chmodSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync, } from 'node:fs'; |
| 3 | import { execFileSync } from 'node:child_process'; |
| 4 | import { dirname, join, resolve } from 'node:path'; |
RemediationAI
The problem is that buildMcpb() in build-mcpb.js performs multiple filesystem side effects (ensureDir, writeManifest, copyRuntimeFiles, buildArchive) but the function documentation does not disclose them. Add comprehensive JSDoc documentation to buildMcpb() with @sideEffect tags listing all filesystem operations (directory creation, file writes, file copies, archive creation), and document the output directory and artifact locations. This makes the side effects visible to callers and reviewers. Verify the fix by checking that the JSDoc appears in IDE tooltips and lists all filesystem operations performed.
Brain upload functionality performs NETWORK side effect (callTool invokes bulk_upload MCP tool) and FILESYSTEM side effect (writeManifestAtomic writes manifest files) not fully disclosed in tool description.
Evidence
| 1 | /** |
| 2 | * brain β CLI subcommand for bulk file upload to the grΔmatr brain. |
| 3 | * |
| 4 | * Reads local files (txt, md, csv, pdf, docx) and uploads them to the server |
RemediationAI
The problem is that the brain upload functionality performs network side effects (callTool invokes bulk_upload MCP tool) and filesystem side effects (writeManifestAtomic writes manifest files) but the tool description does not fully disclose these. Add comprehensive documentation to the brain command and its internal functions with @sideEffect tags listing network calls to the bulk_upload MCP tool and filesystem writes to manifest files, including the manifest file paths. This makes the side effects visible to callers and reviewers. Verify the fix by checking that the command help text and function JSDoc list all network and filesystem operations.
Function writeKey() performs FILESYSTEM side effect (writes to ~/.gramatr.json) not disclosed in function signature or documentation.
Evidence
| 1 | #!/usr/bin/env node |
| 2 | import { chmodSync, existsSync, readFileSync, writeFileSync } from 'fs'; |
| 3 | import { join } from 'path'; |
| 4 | import { createInterface } from 'readline'; |
RemediationAI
The problem is that writeKey() in add-api-key.js writes sensitive credentials to ~/.gramatr.json but the function signature and documentation do not disclose this filesystem side effect. Add JSDoc documentation to writeKey() with an @sideEffect tag explicitly stating that it writes the API key to ~/.gramatr.json with restricted file permissions, and update the function name to writeApiKeyToFile() to make the intent explicit. This makes the side effect visible to callers and reviewers. Verify the fix by checking that the JSDoc appears in IDE tooltips and that the function name clearly indicates file writing.
brain upload tool reads untrusted file content (txt, md, csv, pdf, docx) from local filesystem and ships raw bytes to server via bulk_upload MCP tool, returning validation results and server responses without visible provenance delimiters around the uploaded content.
Evidence
| 1 | /** |
| 2 | * brain β CLI subcommand for bulk file upload to the grΔmatr brain. |
| 3 | * |
| 4 | * Reads local files (txt, md, csv, pdf, docx) and uploads them to the server |
| 5 | * via the `bulk_upload` MCP tool. The server is the single source of truth |
| 6 | * for validation, idempotency (sha256-based dedup), virus scanning (ClamAV), |
| 7 | * and entity/observation creation. The CLI is a thin wrapper. |
| 8 | * |
| 9 | * Migrated in epic #1904 PR 3 from the legacy `create_entity` + `add_observation` |
| 10 | * chain. The orphan-fix that PR 1 added to |
RemediationAI
The problem is that brain.js reads untrusted file content from the local filesystem and uploads raw bytes to the server without visible provenance delimiters, making it difficult to distinguish uploaded content from server responses or validation results. Wrap all uploaded file content with explicit provenance markers (e.g., '--- BEGIN UPLOADED FILE: <filename> ---' and '--- END UPLOADED FILE: <filename> ---') before sending to the bulk_upload MCP tool, and ensure these markers appear in all server responses and logs. This provides clear visual separation between user-supplied content and system output. Verify the fix by uploading a test file and confirming the provenance markers appear in the tool output and server response.
Package declares an install-time hook (npm postinstall/preinstall/prepare, setup.py cmdclass override, custom setuptools install class, or non-default pyproject build-backend). Anyone installing this package runs the hook. Confirm the hook is necessary and review its contents; prefer shipping a plain library without install-time execution.
Evidence
| 70 | "vitest": "^4.0.18" |
| 71 | }, |
| 72 | "scripts": { |
| 73 | "postinstall": "node scripts/postinstall.mjs", |
| 74 | "build": "tsc --build && node ./scripts/build-marketplace.mjs", |
| 75 | "build:binary": "node ./scripts/build-binary.mjs", |
| 76 | "build:marketplace": "node ./scripts/build-marketplace.mjs", |
RemediationAI
The problem is that package.json declares a postinstall hook (node scripts/postinstall.mjs) which executes arbitrary code whenever anyone installs this package, creating a supply-chain attack surface. Review the contents of scripts/postinstall.mjs to confirm it only performs necessary setup (e.g., optional native module compilation); if it performs network calls, downloads, or other risky operations, move that logic to a separate optional setup script and remove the postinstall hook. Update package.json to remove the postinstall entry and document any manual setup steps in the README. Verify the fix by running npm install in a clean environment and confirming no unexpected network calls or file modifications occur.
MCP manifest declares tools but no authentication field is present (none of: auth, authorization, bearer, oauth, mtls, apiKey, api_key, basic, token, authToken). Absence is a weak signal β confirm whether the server relies on network-layer or host-level auth, or declare the real mechanism explicitly so reviewers can audit it.
Evidence
| 1 | # @gramatr/mcp |
| 2 | |
| 3 | Intelligence middleware for AI agents by [gramatr](https://gramatr.com). |
| 4 | |
| 5 | Pre-classifies every request, injects relevant memory and behavioral context, |
| 6 | enforces data quality, and maintains session continuity across Claude, ChatGPT, |
| 7 | Codex, Cursor, Gemini, and any MCP-compatible client. |
| 8 | |
| 9 | ## Quick Install (Claude Code) |
| 10 | |
| 11 | ```bash |
| 12 | npx @gramatr/mcp install |
| 13 | ``` |
| 14 | |
| 15 | Idempotent. Safe to re-run for upgrades. Configures everything in one shot: |
| 16 | |
| 17 | 1. **Auth check** β runs device-flow login if `~/. |
RemediationAI
The problem is that the MCP manifest does not declare an authentication mechanism, making it unclear how the server validates caller identity and authorization. Add an explicit 'auth' or 'authorization' field to the manifest (in README.md or the MCP config file) documenting the authentication method used (e.g., 'bearer: JWT token in Authorization header', 'apiKey: GMTR_API_KEY environment variable', 'mtls: mutual TLS certificate validation'). This allows reviewers to audit the authentication implementation. Verify the fix by checking that the manifest clearly states the authentication mechanism and that the implementation matches the declared method.
Silent error swallowing detected. An except clause that does pass or ... discards the exception with no log, no metric, and no trace. This blinds incident response and hides real failures.
Evidence
| 48 | stdio: "inherit", |
| 49 | timeout: 300000, // 5 min β enough for OAuth browser flow |
| 50 | }); |
| 51 | } catch { |
| 52 | } |
| 53 | } |
| 54 | |
| 55 | main().catch(() => process.exit(0)); |
RemediationAI
The problem is that the catch block in scripts/postinstall.mjs silently swallows exceptions with no logging, making it impossible to debug failures or detect real errors during installation. Replace the empty catch block with a logging statement: `catch (err) { console.error('postinstall warning:', err.message); }` to ensure errors are visible to users and developers. This enables incident response and helps identify real failures. Verify the fix by intentionally causing an error in the postinstall script and confirming the error message appears in the npm install output.