High risk. Don't ship without significant remediation.
Scanned 5/15/2026, 5:50:53 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 65/100, driven primarily by 13 high-severity findings concentrated in prompt injection and tool poisoning vulnerabilities. The AIVSS score of 7.1/10 reflects substantial risks that could allow attackers to manipulate model behavior or compromise the integrity of tools the server exposes. You should address these vulnerabilities or seek alternatives before deploying this in production.
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 endpoint without checking whether the caller has admin or ownership scope over the target user or organization. Add a scope validation check before the API call by calling an authorization function (e.g., `await requireAdminScope(caller, targetEmail)`) that verifies the caller's JWT token contains 'admin' or 'owner' scope. This ensures only authorized users can restore access. Verify the fix by attempting to call 'restore-access' with a non-admin token and confirming it 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 endpoint without checking whether the caller has admin or ownership scope over the target user or organization. Add a scope validation check before the API call by calling an authorization function (e.g., `await requireAdminScope(caller, targetEmail)`) that verifies the caller's JWT token contains 'admin' or 'owner' scope. This ensures only authorized users can restore access. Verify the fix by attempting to call 'restore-access' with a non-admin token and confirming it 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 endpoint without checking whether the caller has admin or ownership scope over the target user or organization. Add a scope validation check before the API call by calling an authorization function (e.g., `await requireAdminScope(caller, targetEmail)`) that verifies the caller's JWT token contains 'admin' or 'owner' scope. This ensures only authorized users can restore access. Verify the fix by attempting to call 'restore-access' with a non-admin token and confirming it 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 endpoint without checking whether the caller has admin or ownership scope over the target user or organization. Add a scope validation check before the API call by calling an authorization function (e.g., `await requireAdminScope(caller, targetEmail)`) that verifies the caller's JWT token contains 'admin' or 'owner' scope. This ensures only authorized users can restore access. Verify the fix by attempting to call 'restore-access' with a non-admin token and confirming it 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 endpoint without checking whether the caller has admin or ownership scope over the target user or organization. Add a scope validation check before the API call by calling an authorization function (e.g., `await requireAdminScope(caller, targetEmail)`) that verifies the caller's JWT token contains 'admin' or 'owner' scope. This ensures only authorized users can restore access. Verify the fix by attempting to call 'restore-access' with a non-admin token and confirming it 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 module-level API_KEY credential loaded at import time from process.env, which means all requests use the same static credential regardless of the actual caller's identity or permissions. Replace the static API_KEY with a per-request credential by accepting the caller's token as a parameter to adminFetch() and passing it in the Authorization header instead of using process.env['GMTR_API_KEY']. This ensures each API call is authenticated as the actual caller, not as a shared service account. Verify the fix by logging the Authorization header in adminFetch() and confirming it contains the caller's token, not a static service 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 (i.e., the key belongs to the caller). Add an authorization check that compares the key's owner (extracted from the server response or a key metadata store) against the caller's identity before accepting the key as valid. 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 and confirming the validation fails with a 403 Forbidden 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() deletes sensitive files (~/.gramatr.json and settings.json) without documenting this destructive side effect in the function signature or JSDoc comment. Add a JSDoc comment above the clearAll() function that explicitly states `@side-effect Deletes ~/.gramatr.json and settings.json from the filesystem` and rename the function to something more explicit like `clearAllCredentialsAndSettings()` to signal the destructive behavior. This makes the impact visible to callers and code reviewers. Verify the fix by checking that the JSDoc renders correctly in your IDE 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() writes credentials to ~/.gramatr.json without documenting this filesystem side effect in the function signature or JSDoc comment. Add a JSDoc comment above writeKey() that explicitly states `@side-effect Writes API key to ~/.gramatr.json with restricted permissions (0600)` so callers understand the impact. This makes the side effect visible to code reviewers and maintainers. Verify the fix by checking that the JSDoc renders correctly in your IDE and that the filesystem operation is documented.
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() performs multiple filesystem side effects (ensureDir, writeManifest, copyRuntimeFiles, buildArchive) without documenting them in the function's JSDoc comment. Add a JSDoc comment above buildMcpb() that explicitly states `@side-effect Creates directories, writes manifest files, copies runtime files, and builds archive to disk` so callers understand the full scope of effects. This makes the impact visible to code reviewers and maintainers. Verify the fix by checking that the JSDoc renders correctly in your IDE and that all filesystem operations are documented.
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 tool description does not fully disclose that it performs network side effects (callTool invokes bulk_upload MCP tool) and filesystem side effects (writeManifestAtomic writes manifest files). Update the tool description in brain.js to explicitly state `Reads local files and uploads them to the server via bulk_upload MCP tool; writes manifest files to disk` so callers understand the full scope of effects. This makes the impact visible to code reviewers and MCP clients. Verify the fix by checking that the tool description in the MCP manifest includes both network and filesystem side effects.
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() writes credentials to ~/.gramatr.json without documenting this filesystem side effect in the function signature or JSDoc comment. Add a JSDoc comment above writeKey() that explicitly states `@side-effect Writes API key to ~/.gramatr.json with restricted permissions (0600)` so callers understand the impact. This makes the side effect visible to code reviewers and maintainers. Verify the fix by checking that the JSDoc renders correctly in your IDE and that the filesystem operation is documented.
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 file content from the local filesystem and ships raw bytes to the server without clear provenance delimiters or validation, making it difficult to trace which uploaded content came from which file or to detect tampering. Wrap each uploaded file's content with explicit provenance markers (e.g., `[FILE: filename.txt START] <content> [FILE: filename.txt END]`) and include file metadata (hash, size, timestamp) in the upload payload so the server can validate and audit the source. This ensures uploaded content is traceable and tamper-evident. Verify the fix by uploading a test file and confirming the server response includes the provenance markers and file metadata.
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 (node scripts/postinstall.mjs) whenever the package is installed, which poses a supply-chain risk if the script is compromised or contains unintended side effects. Review the contents of scripts/postinstall.mjs to confirm it is necessary; if it only performs optional setup (e.g., optional build steps), move it to a separate setup script that users must explicitly invoke (e.g., `npm run setup`) instead of running automatically. If the hook is truly necessary, add a clear comment in package.json explaining why and document all side effects in the script. Verify the fix by removing the postinstall hook, reinstalling the package, and confirming the package still functions correctly.
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 whether the server relies on network-layer auth, host-level auth, or API keys, which blinds security reviewers. Add an explicit `auth` field to the MCP manifest (e.g., `"auth": {"type": "apiKey", "location": "header"}` or `"auth": {"type": "bearer"}`) that documents the real authentication mechanism used by the tools. This makes the security model visible to reviewers and MCP clients. Verify the fix by checking that the MCP manifest includes an auth field and that it accurately describes how the server authenticates requests.
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, metrics, or trace, which blinds incident response and hides real failures during package installation. Replace the empty catch block with `catch (err) { console.error('postinstall failed:', err); process.exit(1); }` to log the error and exit with a non-zero status, ensuring installation failures are visible and the package installation fails cleanly. This makes failures visible to users and CI/CD systems. Verify the fix by intentionally causing an error in the postinstall script and confirming the error is logged and the installation fails.