Back to skill

Security audit

FileChat RAG

Security checks for vulnerabilities and agentic risk

Overview

This Google Drive document-search skill is coherent, but it needs review because it broadly indexes Drive contents, sends content to Gemini or OpenAI, and stores raw document text and API keys locally with weak controls.

Install only if you are comfortable letting this skill recursively read the selected Google Drive folder, including subfolders and shortcuts, send document text/images and search queries to the configured AI provider, and keep a local plaintext index. Avoid using it for regulated or highly confidential files unless you add stronger consent, scoping, local-only processing, secret handling, dependency pinning, and deletion controls.

Vulnerability Patterns
  • Insecure DependenciesIntroduces malicious components through unsafe dependency sources
  • Insecure Skill Coding PracticesFinds exploitable flaws such as hardcoded secrets or command injection
  • Skill Instruction HijackingAlters the agent's session goals or safety constraints when the skill loads
  • Agent Memory PoisoningWrites attacker-controlled rules into memory that affect later sessions
  • Remote Payload Retrieval and ExecutionFetches external code whose behavior can change after review
Findings (3)

T08 · Insecure Dependencies

Warning
Location
sync.js:31
Finding
Unpinned Runtime Package Retrieval and Execution<![CDATA[ ## Vulnerability Details **File Location**: `sync.js:31-45`; related dependency declarations in `SKILL.md:13-20` and `package.json:10-14` **Vulnerability Type**: Remote execution of mutable, unpinned third-party dependencies **Risk Level**: Medium ### Vulnerable Code ```javascript const cmd = `SSL_CERT_FILE=/workspace/cacert.pem npx @googleworkspace/cli drive files list --params '{"q": "${escapedQuery}", "fields": "files(id, name, mimeType, shortcutDetails)"}'`; ``` ```javascript const cmd = `SSL_CERT_FILE=/workspace/cacert.pem npx @googleworkspace/cli drive files get --params '{"fileId": "${fileId}", "alt": "media"}' --output "${dest}"`; ``` The installation metadata also specifies the package without an exact version: ```yaml install: - id: gws kind: node package: "@googleworkspace/cli" bins: ["gws"] label: "Install Google Workspace CLI" ``` The npm dependencies use mutable caret ranges, and no lockfile was present: ```json "dependencies": { "@google/generative-ai": "^0.2.1", "chromadb": "^1.8.1", "pdf-parse": "^1.1.1", "dotenv": "^16.4.5" } ``` ### Technical Analysis The synchronization process executes `npx @googleworkspace/cli` without specifying an exact reviewed version or requiring a previously installed local binary. Depending on the local npm configuration and package availability, `npx` can retrieve and execute the current registry version at runtime. This creates a mutable code-execution channel: the code that executes during synchronization may differ from the code reviewed with this Skill. The risk is increased by the absence of a package lockfile and the use of mutable dependency ranges. This behavior is unnecessary at runtime because the declared setup process can install a reviewed, pinned CLI version before the Skill is invoked. This is primarily an insecure dependency and supply-chain issue. It also has characteristics of remote payload retrieval because executable package contents can be obtained ...[truncated 1283 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Add `@googleworkspace/cli` as an exact-version project dependency instead of resolving it dynamically: ```json { "dependencies": { "@googleworkspace/cli": "REVIEWED_EXACT_VERSION" } } ``` 2. Generate and commit a package lockfile containing integrity hashes. 3. Install dependencies during a controlled setup phase using `npm ci`. 4. Invoke the reviewed local executable directly, such as: ```bash ./node_modules/.bin/gws ``` 5. If `npx` must be retained, use `npx --no-install` so it fails rather than downloading code at runtime. 6. Replace all caret ranges with exact reviewed versions and use automated dependency review before upgrades. 7. Restrict package installation to a trusted registry and consider npm provenance/signature verification. 8. Run synchronization in a sandbox with only the Drive, filesystem, environment, and network access strictly required for the operation. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
sync.js:58
Finding
Sensitive Documents, Images, and Search Queries Are Sent to External AI Providers Without Adequate Data-Handling Controls<![CDATA[ ## Vulnerability Details **File Location**: `sync.js:58-132`, `sync.js:174-176`, `query.js:37-84`, and `image_parse.js:5-21` **Vulnerability Type**: External disclosure of potentially sensitive user content **Risk Level**: Medium ### Vulnerable Code Document chunks are sent to OpenAI when that provider is selected: ```javascript const response = await fetch("https://api.openai.com/v1/embeddings", { method: "POST", headers: { "Content-Type": "application/json", "Authorization": `Bearer ${OPENAI_API_KEY}` }, body: JSON.stringify({ model: "text-embedding-3-small", input: text }) }); ``` Document chunks and queries are sent to Gemini when Gemini is selected: ```javascript const url1 = `https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-2-preview:embedContent?key=${GEMINI_API_KEY}`; const res1 = await fetch(url1, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: "models/gemini-embedding-2-preview", content: { parts: [{ text: text }] } }) }); ``` Complete image contents are encoded and submitted to Gemini for OCR: ```javascript const prompt = "Please transcribe all the text visible in this image accurately. Do not add any extra commentary, just the text."; const imagePart = { inlineData: { data: Buffer.from(fs.readFileSync(filePath)).toString("base64"), mimeType }, }; const result = await model.generateContent([prompt, imagePart]); ``` The ingestion loop submits every nonempty chunk: ```javascript const chunks = chunkText(text); for(let i=0; i<chunks.length; i++) { const c = chunks[i]; const emb = await getEmbedding(c); db.push({ fileId: targetId, filename: filePath, chunkIndex: i, text: c, embedding: emb }); } ``` The query path similarly submits the complete user query: ```javascript const queryEmbedding = await getEmbedding(query); ``` ### Technical Analysis The Skill recursively reads document ...[truncated 2642 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Before the first synchronization, clearly disclose: - Which provider receives data. - That document text, complete images, and search queries leave the local environment. - Whether recursive folders and shortcuts are included. - Applicable provider retention and regional-processing terms. 2. Require explicit informed confirmation before transmitting a folder's contents. 3. Add a local-only embedding and OCR option for sensitive datasets. 4. Support allowlists and exclusions for file types, individual files, subfolders, and shortcuts. 5. Display the discovered synchronization scope and request confirmation before processing. 6. Add optional detection and redaction for credentials, personal identifiers, health information, and other sensitive content. 7. Allow users to disable image OCR independently from text embedding. 8. Minimize transmitted content and avoid sending metadata not required by the provider. 9. Document deletion, retention, and provider-account configuration requirements. 10. Fail closed for unsupported provider values rather than treating every non-`openai` value as Gemini. ]]>

T09 · Insecure Skill Coding Practices

Warning
Location
sync.js:157
Finding
Plaintext Document Database and Predictable Temporary Files Expose Sensitive Drive Content<![CDATA[ ## Vulnerability Details **File Location**: `sync.js:12`, `sync.js:157-184`, and `sync.js:201` **Vulnerability Type**: Insecure storage and unsafe temporary-file handling **Risk Level**: Medium ### Vulnerable Code The database path is predictable and located beside the Skill code: ```javascript const DB_PATH = path.join(__dirname, `vector_db_${ROOT_FOLDER_ID}.json`); ``` Downloaded files use a predictable relative filename derived from the Drive file ID: ```javascript const tmpFile = `./filechat_${targetId}`; ``` Full document text and metadata are placed into the database: ```javascript db.push({ fileId: targetId, filename: filePath, chunkIndex: i, text: c, embedding: emb }); ``` Cleanup occurs only after processing rather than in a `finally` block: ```javascript if(fs.existsSync(tmpFile)) fs.unlinkSync(tmpFile); ``` The database is written without an explicit restrictive mode or encryption: ```javascript fs.writeFileSync(DB_PATH, JSON.stringify(db)); ``` ### Technical Analysis The local vector database contains plaintext chunks of the original documents, filenames, Drive file IDs, and embeddings. It is written using default filesystem permissions, which depend on the process umask and execution environment. There is no encryption, access-control validation, retention policy, or secure deletion mechanism. Downloaded source files are placed in the current working directory using predictable names. The implementation does not create a private temporary directory, use exclusive file creation, verify that the destination is not a symbolic link, or guarantee cleanup through `finally`. Process interruption, abrupt termination, or exceptions outside the inner handling path can leave source documents on disk. In a shared or insufficiently isolated workspace, another local process could pre-create a symbolic link at the predictable path or read residual files and databases. A symlink could potentially redirect CLI output to another fil ...[truncated 1941 chars]
Remediation
<![CDATA[ ## Remediation Suggestions 1. Store databases in a dedicated private data directory created with mode `0700`. 2. Create database files with mode `0600` and verify ownership and permissions before reading them. 3. Encrypt sensitive databases at rest using a key stored separately from the database. 4. Use `fs.mkdtemp` or an equivalent secure temporary-directory API rather than predictable working-directory paths. 5. Open temporary files with exclusive creation and reject symbolic links. 6. Place cleanup in a `finally` block so it runs after parsing, embedding, API, and filesystem errors: ```javascript try { // Download and process the file. } finally { if (fs.existsSync(tmpFile)) { fs.unlinkSync(tmpFile); } } ``` 7. Install signal and shutdown handlers to remove outstanding temporary files where practical. 8. Write the database atomically to a private temporary file and rename it after a successful sync. 9. Provide explicit retention, deletion, and database purge commands. 10. Avoid storing full plaintext chunks when a less sensitive representation can satisfy the retrieval requirement. ]]>
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Supply ChainUnpinned Dependencies, External Script Fetching, Obfuscated Code
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
Findings (28)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding
The declared purpose centers on Google Drive-backed document management and retrieval with semantic search and persistent vector indexing. The supplied code does none of that. Instead, it implements a single OCR-like helper: it reads an image file locally, encodes it, submits it to a Gemini model with a prompt to transcribe visible text, and returns the text. This is a materially different primary purpose and introduces an undeclared capability (image text extraction) while lacking the declared Google Drive, retrieval, indexing, and RAG behaviors.

Credential Access

High
Category
Privilege Escalation
Content
If they are missing, STOP and ask the user to provide them. 
Create the `.env` file like this:
```bash
echo "EMBEDDING_PROVIDER=gemini" > ./skills/filechat/.env
echo "GEMINI_API_KEY=your_key_here" >> ./skills/filechat/.env
```
Confidence
97% confidence
Finding
The skill instructs the agent to collect API keys from the user and write them directly into a plaintext .env file under the workspace. Storing credentials this way exposes them to other tools, processes, logs, backups, or later workspace inspection, creating a straightforward secret-handling risk.

Credential Access

High
Category
Privilege Escalation
Content
Create the `.env` file like this:
```bash
echo "EMBEDDING_PROVIDER=gemini" > ./skills/filechat/.env
echo "GEMINI_API_KEY=your_key_here" >> ./skills/filechat/.env
```

## How to Sync the Library
Confidence
97% confidence
Finding
The example command appends a live API key into a .env file using shell echo, which can leak the secret through shell history, command logs, transcript retention, or file exposure. Because this is presented as the normal setup path, it operationalizes insecure credential storage and handling.

Credential Access

High
Category
Privilege Escalation
Content
const fs = require('fs');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env') });

const folderArg = process.argv[2];
const query = process.argv[3];
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Credential Access

High
Category
Privilege Escalation
Content
const fs = require('fs');
const path = require('path');
require('dotenv').config({ path: path.join(__dirname, '.env') });

const folderArg = process.argv[2];
const query = process.argv[3];
Confidence
60% confidence
Finding
Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The README describes recursively downloading all files from a Google Drive folder, extracting text including OCR from images, generating embeddings, and storing the results in a persistent local vector database, but it does not clearly warn users about the privacy, retention, and sensitive-data exposure implications. This is dangerous because users may authorize indexing of confidential documents without understanding that broad content copies and semantic representations will remain on local storage and may be queried later.

Lp3

Medium
Category
MCP Least Privilege
Confidence
91% confidence
Finding
The skill declares installation and execution steps that use environment variables, external CLIs, and networked services, but it does not define any explicit tool scope or permissions boundaries. That makes the agent's operational authority ambiguous and increases the chance of unintended access to local secrets, remote services, or user data during execution.

Vague Triggers

Medium
Confidence
93% confidence
Finding
The invocation guidance is broad enough to activate the skill for generic requests about saving, retrieving, or asking about files. In context, that can cause the agent to engage Google Drive sync, indexing, or file download flows without sufficiently narrowing user intent, increasing the risk of over-collection or unintended document access.

Missing User Warnings

Medium
Confidence
96% confidence
Finding
The sync instructions direct the agent to download, chunk, embed, and locally index all new or changed files from an entire Google Drive folder, including subfolders, without an up-front warning about the scope of data collection. This is dangerous because users may expect a narrow action but instead authorize bulk ingestion of potentially sensitive folder contents into a persistent local vector store.

Missing User Warnings

Medium
Confidence
93% confidence
Finding
The instructions tell the agent to download a Drive file into the workspace and send it back to the user, but they omit an explicit warning or confirmation step that a local copy will be created and transmitted. For sensitive documents, this can lead to unnecessary local persistence and accidental disclosure if the wrong file is selected or the workspace is accessible to other processes.

Missing User Warnings

Medium
Confidence
95% confidence
Finding
This function sends raw image contents from a local file to Google's Gemini API for OCR/transcription without any visible consent, notice, or policy enforcement in the code path. In the context of a document-storage and retrieval skill that processes user files from Google Drive, images may contain sensitive personal, financial, or corporate data, so undisclosed transfer to a third-party AI service creates a real privacy and data-handling risk.

Missing User Warnings

Medium
Confidence
92% confidence
Finding
The code sends the user's raw search query to third-party embedding providers (OpenAI or Google Gemini) to generate embeddings. In a document-search skill, queries may contain sensitive business, personal, or regulated information, and there is no consent, minimization, redaction, or disclosure mechanism shown before transmitting that data externally.

External Transmission

Medium
Category
Data Exfiltration
Content
async function getEmbedding(text) {
  if (EMBEDDING_PROVIDER === "openai") {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
Confidence
90% confidence
Finding
This fetch call transmits user-supplied query text to OpenAI's embeddings API. In the context of a RAG skill for private Google Drive content, external transmission materially increases confidentiality risk because users may search for sensitive internal document contents and assume the search stays within the local skill boundary.

External Transmission

Medium
Category
Data Exfiltration
Content
async function getEmbedding(text) {
  if (EMBEDDING_PROVIDER === "openai") {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
Confidence
90% confidence
Finding
This fetch call transmits user-supplied query text to OpenAI's embeddings API. In the context of a RAG skill for private Google Drive content, external transmission materially increases confidentiality risk because users may search for sensitive internal document contents and assume the search stays within the local skill boundary.

Context-Inappropriate Capability

Medium
Confidence
87% confidence
Finding
Using `execSync` to shell out to external commands adds unnecessary subprocess capability and expands the attack surface compared with direct API/library calls. Although the immediate interpolation points appear constrained, this design still inherits shell parsing risks, dependency execution risks, and weaker control over error handling and permissions.

Rp1

Medium
Category
MCP Rug Pull
Confidence
89% confidence
Finding
The code invokes `npx @googleworkspace/cli` without pinning an exact version, so each sync may resolve a newer package from the registry. That creates a supply-chain risk where a compromised or breaking upstream release could execute arbitrary code in the skill's environment with access to downloaded files and API keys.

Rp1

Medium
Category
MCP Rug Pull
Confidence
89% confidence
Finding
This second `npx @googleworkspace/cli` call has the same unpinned dependency problem during file download. A malicious or altered upstream package could run arbitrary code when syncing and directly access document contents being retrieved from Drive.

Description-Behavior Mismatch

Medium
Confidence
95% confidence
Finding
The sync process sends extracted document text to OpenAI or Gemini embedding APIs, which is an external transmission of potentially sensitive file contents. That is materially broader than a purely local indexing expectation and increases confidentiality risk for any documents stored in Drive, especially because the skill markets the data as securely stored and isolated.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code transmits document text to external embedding providers without any in-code disclosure, consent gate, or visible warning to the user. In a document-storage and retrieval skill, hidden third-party transmission is especially sensitive because users are likely to assume their files remain within Drive and the local indexer.

External Transmission

Medium
Category
Data Exfiltration
Content
async function getEmbedding(text) {
  if (EMBEDDING_PROVIDER === "openai") {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
Confidence
95% confidence
Finding
The hardcoded OpenAI endpoint confirms that file-derived content is transmitted to an external service boundary. This is risky because the skill processes arbitrary user documents, and the transmission is not limited to metadata but includes substantive extracted text.

External Transmission

Medium
Category
Data Exfiltration
Content
async function getEmbedding(text) {
  if (EMBEDDING_PROVIDER === "openai") {
    const response = await fetch("https://api.openai.com/v1/embeddings", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
Confidence
95% confidence
Finding
The hardcoded OpenAI endpoint confirms that file-derived content is transmitted to an external service boundary. This is risky because the skill processes arbitrary user documents, and the transmission is not limited to metadata but includes substantive extracted text.

Description-Behavior Mismatch

Medium
Confidence
97% confidence
Finding
Image files are sent in full to a generative model for OCR, which exposes the raw image contents—not just derived chunks—to an external provider. This broadens data exposure beyond simple local chunk/embed behavior and may leak screenshots, scans, IDs, or other highly sensitive visual data.

Missing User Warnings

Medium
Confidence
94% confidence
Finding
The code persists a JSON vector database that includes raw text chunks from documents, not just embeddings and metadata. Storing extracted content unencrypted on disk creates a local confidentiality risk if the host is shared, compromised, or backed up to less trusted locations.

Unpinned Dependencies

Low
Category
Supply Chain
Content
"query": "node query.js"
  },
  "dependencies": {
    "@google/generative-ai": "^0.2.1",
    "chromadb": "^1.8.1",
    "pdf-parse": "^1.1.1",
    "dotenv": "^16.4.5"
Confidence
96% confidence
Finding
The dependency is specified with a caret range, which permits automatic installation of newer compatible versions. This weakens supply-chain reproducibility and can unexpectedly pull in a compromised or breaking release if the upstream package or registry is abused.

Unpinned Dependencies

Low
Category
Supply Chain
Content
},
  "dependencies": {
    "@google/generative-ai": "^0.2.1",
    "chromadb": "^1.8.1",
    "pdf-parse": "^1.1.1",
    "dotenv": "^16.4.5"
  }
Confidence
96% confidence
Finding
Using a caret version for chromadb allows semver-compatible updates to be fetched implicitly, which increases supply-chain risk and reduces build determinism. If an upstream release is malicious or vulnerable, environments may ingest it without an explicit review.

Static analysis

Detected: suspicious.dangerous_exec, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
sync.js:33

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
query.js:15

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
sync.js:16