[ ⌘K ]
← BACK TO SEARCH

piyushfgk/pitools

critical

MCP Server to run mutiple tools, helps in coding

PiTools MCP is a server that enhances AI coding assistants by providing real-time web search via DuckDuckGo and the ability to post content (text, art...

purpose: PiTools MCP is a server that enhances AI coding asthreat: network exposed
TypeScript0May 20, 2026May 20, 2026GITHUB
5/20/2026
high1 finding
src/lib/linkedin-tool.ts
107async function uploadImageAndGetUrnInternal(accessToken: string, authorUrn: string, imagePath: string): Promise<string> {
108  const initializeUploadUrl = 'https://api.linkedin.com/rest/images?action=initializeUpload';
109  const initUploadBody = { initializeUploadRequest: { owner: authorUrn } };
110  const initResponse = await fetch(initializeUploadUrl, {
111    method: 'POST',
112    headers: { 'Authorization': 'Bearer ' + accessToken, 'Content-Type': 'application/json', 'LinkedIn-Version': '202504', 'X-Restli-Protocol-Version': '2.0.0' },
113    body: JSON.stringify(initUploadBody)
114  });
115  ...
116  const imageBuffer = await fs.readFile(imagePath);
src/index.ts:10-26src/lib/linkedin-tool.ts:107

// Network-exposed MCP; exploitable by any user sending prompts to the MCP server.

The linkedin_post_image and linkedin_post_video tools accept a filePath parameter that is used directly in fs.readFile() without any validation or sanitization. An attacker can provide an arbitrary path (e.g., /etc/passwd, ../config.json) to read any file on the system and upload it to LinkedIn, effectively exfiltrating the file contents.

ImpactAn attacker (compromised LLM or malicious prompt) can read arbitrary files from the server's filesystem and exfiltrate them by uploading to LinkedIn. This includes sensitive files like SSH keys, environment files, or configuration secrets.

FixValidate that the filePath is within an allowed directory (e.g., a designated uploads folder) and reject paths containing '..' or starting with '/'. Use path.resolve and path.normalize to restrict access.

high1 finding
src/lib/linkedin-tool.ts
266async function postVideoUpdateInternal(accessToken: string, authorUrn: string, videoPath: string, commentaryText: string): Promise<{ success: boolean; message: string; postId?: string }> {
267  const initializeUploadUrl = 'https://api.linkedin.com/rest/videos?action=initializeUpload'; 
268  const postApiUrl = 'https://api.linkedin.com/rest/posts';
269  try {
270    const videoStatsForInit = await stat(videoPath);
271    ...
272    const videoBuffer = await fs.readFile(videoPath);
src/index.ts:10-26src/lib/linkedin-tool.ts:266

// Network-exposed MCP; exploitable by any user sending prompts to the MCP server.

Similar to the image upload, the video upload function uses the user-supplied videoPath directly in fs.readFile() and fs.stat() without any path validation. This allows reading arbitrary files from the filesystem.

ImpactAn attacker can read any file on the system by providing its path as the videoPath parameter, leading to exfiltration of sensitive data.

FixApply the same path validation as for image uploads: restrict to a specific directory and reject path traversal attempts.

high1 finding
src/lib/linkedin-tool.ts
172async function postArticleUpdateInternal(
173  accessToken: string, 
174  authorUrn: string, 
175  articleUrl: string, 
176  commentaryText: string, 
177  articleTitle: string,
178  thumbnailFilePath?: string,
179  thumbnailAltText?: string
180): Promise<{ success: boolean; message: string; postId?: string }> {
181  ...
182  if (thumbnailFilePath) {
183    try {
184      const thumbnailUrn = await uploadImageAndGetUrnInternal(accessToken, authorUrn, thumbnailFilePath);
src/index.ts:10-26src/lib/linkedin-tool.ts:172

// Network-exposed MCP; exploitable by any user sending prompts to the MCP server.

The linkedin_post_article tool accepts an optional thumbnailFilePath parameter that is passed directly to uploadImageAndGetUrnInternal, which reads the file with fs.readFile(). No path validation is performed, allowing arbitrary file reads.

ImpactAn attacker can read arbitrary files by providing a path as thumbnailFilePath, exfiltrating them via LinkedIn upload.

FixValidate thumbnailFilePath similarly to other file paths: restrict to allowed directory and reject traversal.

medium1 finding
src/lib/linkedin-tool.ts
58async function getLinkedInAuthDetailsAndUrn(): Promise<{ accessToken: string; authorUrn: string; userName?: string }> {
59  const accessToken = process.env.LINKEDIN_ACCESS_TOKEN;
60  if (!accessToken) {
61    console.error("Missing environment variable: LINKEDIN_ACCESS_TOKEN");
62    throw new Error("LinkedIn Access Token not configured in environment variables.");
63  }
64  const userInfo = await getLinkedInUserInfoInternal(accessToken);
65  const authorUrn = 'urn:li:person:' + userInfo.urn;
66  return { accessToken, authorUrn, userName: userInfo.name };
67}
src/index.ts:10-26src/lib/linkedin-tool.ts:58

// Network-exposed MCP; tokens are accessible to anyone who can trigger file read or view logs.

LinkedIn and Instagram access tokens are read from environment variables and used directly in API calls. While this is a common pattern, the tokens are stored in plaintext in the environment and could be exposed through error messages or logging. Additionally, the tokens are passed in HTTP headers and could be logged by the MCP server or network intermediaries.

ImpactIf an attacker gains access to the environment (e.g., via file read vulnerability), they can extract the tokens and post to LinkedIn/Instagram on behalf of the user. Also, tokens may be leaked in error messages returned to the user.

FixConsider using a secrets manager or encrypted storage. Avoid logging tokens. Ensure error messages do not include the token value.

medium1 finding
src/lib/instagram-tool.ts
33export async function postInstagramImage(
34  input: InstagramPostImageInput
35): Promise<{ content: { type: 'text'; text: string }[]; isError?: boolean }> {
36  const accessToken = process.env.INSTAGRAM_ACCESS_TOKEN;
37  const instagramPostingUserId = process.env.INSTAGRAM_USER_ID_FOR_POSTING; 
38
39  if (!accessToken) {
40    return { 
41      content: [{ type: 'text', text: 'Error: INSTAGRAM_ACCESS_TOKEN is not set in environment variables.' }],
42      isError: true 
43    };
44  }
45  if (!instagramPostingUserId) {
46    return { 
47      content: [{ type: 'text', text: 'Error: INSTAGRAM_USER_ID_FOR_POSTING is not set in environment variables.' }],
48      isError: true 
49    };
50  }
src/index.ts:29-33src/lib/instagram-tool.ts:33

// Network-exposed MCP; tokens are accessible to anyone who can trigger file read or view logs.

Instagram access token and user ID are stored in plaintext environment variables and used directly in API calls. They could be exposed through error messages or logging.

ImpactAn attacker with access to the environment can use the token to post to Instagram on behalf of the user.

FixUse a secrets manager or encrypted storage. Avoid logging tokens. Ensure error messages do not include the token value.

medium1 finding
src/lib/instagram-tool.ts
49  const { imageUrl, caption } = input;
50
51  // Step 1: Create Media Container
52  const mediaContainerUrl = `https://graph.instagram.com/${instagramPostingUserId}/media`;
53   
54  const mediaContainerParams = new URLSearchParams();
55  mediaContainerParams.append('image_url', imageUrl);
56  mediaContainerParams.append('access_token', accessToken);
57  if (caption) {
58    mediaContainerParams.append('caption', caption);
59  }
60
61  try {
62    console.error(`Attempting to create media container for image: ${imageUrl}`);
63    const containerResponse = await fetch(mediaContainerUrl, {
64      method: 'POST',
65      body: mediaContainerParams.toString(),
66      headers: {
67        'Content-Type': 'application/x-www-form-urlencoded',
68      }
69    });
src/index.ts:29-33src/lib/instagram-tool.ts:49

// Network-exposed MCP; attacker can provide arbitrary URLs to be fetched by Instagram's servers.

The instagram_post_image tool accepts an imageUrl parameter that is sent to the Instagram Graph API. While the API itself will fetch the URL, an attacker could provide a URL pointing to an internal service (e.g., http://169.254.169.254/latest/meta-data/) to trigger a server-side request forgery (SSRF) via Instagram's servers, potentially leaking internal data. Additionally, the URL is not validated beyond being a valid URL string.

ImpactAn attacker could use the Instagram API as a proxy to scan internal networks or access cloud metadata endpoints, though the impact is limited by Instagram's own restrictions. More directly, the MCP server itself does not fetch the URL, so SSRF risk is lower but still present via the API call.

FixValidate the imageUrl against an allowlist of allowed domains or ensure it does not point to private IP ranges. Consider fetching the image server-side to validate content before sending to Instagram.

shell.execenv.exposurenetwork.httpfilesystem.read
100
LLM-based
high findings+75
medium findings+45