High risk. Don't ship without significant remediation.
Scanned 5/15/2026, 5:20:21 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 safety score of 67/100, driven primarily by 13 high-severity vulnerabilities spanning prompt injection and tool poisoning risks. The prompt injection findings (8 instances) suggest the server could be manipulated through crafted inputs to behave unexpectedly, while tool poisoning vulnerabilities (5 instances) indicate potential for malicious data or command execution through the tools it exposes. You should address these high-severity issues before deployment, particularly the prompt injection vectors that could compromise the integrity of your MCP interactions.
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 restores user access without verifying caller permissions. Add `requireAdminScope(callerIdentity, targetEmail)` before the restore operation to validate admin authorization. This prevents unauthorized access restoration. Verify by attempting restore-access as a non-admin user and confirming the request is denied.
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 restores user access without verifying caller permissions. Add `requireAdminScope(callerIdentity, targetEmail)` before the restore operation to validate admin authorization. This prevents unauthorized access restoration. Verify by attempting restore-access as a non-admin user and confirming the request is denied.
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 restores user access without verifying caller permissions. Add `requireAdminScope(callerIdentity, targetEmail)` before the restore operation to validate admin authorization. This prevents unauthorized access restoration. Verify by attempting restore-access as a non-admin user and confirming the request is denied.
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 restores user access without verifying caller permissions. Add `requireAdminScope(callerIdentity, targetEmail)` before the restore operation to validate admin authorization. This prevents unauthorized access restoration. Verify by attempting restore-access as a non-admin user and confirming the request is denied.
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 restores user access without verifying caller permissions. Add `requireAdminScope(callerIdentity, targetEmail)` before the restore operation to validate admin authorization. This prevents unauthorized access restoration. Verify by attempting restore-access as a non-admin user and confirming the request is denied.
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 from process.env, ignoring the actual caller's identity and permissions. Replace the hardcoded credential approach with a function `adminFetch(callerToken, endpoint, options)` that accepts the caller's token and validates it against the endpoint's required scopes before making the request. This ensures each API call is authenticated to the actual caller, not a shared service account. Verify by logging the caller identity in adminFetch and confirming it matches the user making the CLI command.
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 without confirming the caller is authorized to use that key. Modify the function signature to `validateAgainstServer(callerIdentity, userSuppliedKey)` and add a check that the caller owns or has permission to use the supplied key before calling the server endpoint. This prevents users from validating or activating API keys belonging to other users. Test by attempting to validate another user's API key and confirm it is rejected.
Function buildMcpb() performs FILESYSTEM side effects (mkdirSync, writeFileSync, copyFileSync, 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() performs filesystem operations (mkdirSync, writeFileSync, copyFileSync, rmSync) without documenting these side effects. Update the JSDoc comment to include `@side-effect Filesystem: creates directories, writes/copies/removes files in build output directory` and add a parameter `{ dryRun?: boolean }` to allow callers to preview changes. This makes side effects explicit and testable. Verify by running buildMcpb with dryRun=true and confirming no files are modified.
Brain upload functionality performs NETWORK side effect (callTool makes MCP calls to server) and FILESYSTEM side effect (writeManifestAtomic writes manifest files) with side effects not fully disclosed in 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 function performs network calls (callTool to server) and filesystem writes (writeManifestAtomic) without fully disclosing these side effects in the documentation. Expand the JSDoc to include `@side-effect Network: uploads files via bulk_upload MCP tool; Filesystem: writes manifest to ~/.gramatr/brain-manifest.json` and add error handling that rolls back manifest writes on network failure. This makes side effects transparent and recoverable. Test by simulating a network failure mid-upload and confirming the manifest is not partially written.
Function clearAll() performs FILESYSTEM side effects (unlinkSync deletes credential files) 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() deletes credential files via unlinkSync without documenting this destructive side effect. Add a JSDoc comment `@side-effect Filesystem: permanently deletes credential files from ~/.gramatr/` and add a confirmation prompt `await confirmUserAction('This will permanently delete all stored credentials. Continue?')` before executing unlinkSync. This prevents accidental credential loss. Verify by running clearAll() and confirming it prompts for confirmation before deletion.
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 main() performs network validation and filesystem credential storage without fully disclosing these side effects in the help text. Update the help/description text to include 'This command will: (1) validate your API key with the server, (2) store the key in ~/.gramatr.json' and add a dry-run flag `--dry-run` that performs validation without writing credentials. This ensures users understand the side effects before running. Verify by running with --dry-run and confirming no credentials are written.
Function main() performs NETWORK side effect (validateAgainstServer makes HTTP POST to server) and FILESYSTEM side effect (writeKey writes credentials) not fully disclosed in help text.
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 main() performs network validation and filesystem credential storage without fully disclosing these side effects in the help text. Update the help/description text to include 'This command will: (1) validate your API key with the server, (2) store the key in ~/.gramatr.json' and add a dry-run flag `--dry-run` that performs validation without writing credentials. This ensures users understand the side effects before running. Verify by running with --dry-run and confirming no credentials are written.
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 upload reads untrusted local files and sends raw bytes to the server without marking uploaded content boundaries, making it difficult to distinguish attacker-controlled content from server responses. Wrap each uploaded file with explicit delimiters in the request: `{ file_name, file_hash, content_start_marker: '<<<FILE_START>>>', content: rawBytes, content_end_marker: '<<<FILE_END>>>' }` and require the server to echo these markers in responses. This provides provenance and prevents content injection attacks. Test by uploading a file containing server response patterns and confirming the markers prevent confusion.
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 that runs arbitrary code during npm install, creating a supply-chain attack surface. Remove the `"postinstall": "node scripts/postinstall.mjs"` line from scripts and move any necessary setup into a separate setup script or documentation step that users run explicitly. If the hook is truly necessary, add a comment explaining why and audit scripts/postinstall.mjs for security issues. Verify by running npm install in a clean environment and confirming postinstall.mjs does not execute.
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. Add an explicit `"authentication": { "type": "bearer", "description": "Caller must provide valid API key via Authorization: Bearer header or GMTR_API_KEY env var" }` field to the manifest or README. This clarifies the security model for auditors. Verify by reviewing the manifest and confirming the auth field matches the actual implementation in admin.js and add-api-key.js.
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 postinstall.mjs silently swallows exceptions with no logging, hiding real failures during installation. Replace `catch { }` with `catch (err) { console.error('postinstall failed:', err.message); process.exit(1); }` to log the error and exit with a non-zero code. This ensures installation failures are visible and incident response is possible. Verify by intentionally breaking the postinstall script and confirming the error message is logged and npm install fails.