Back to skill

Security audit

gogcli-mcp-contacts

Security checks for vulnerabilities and agentic risk

Overview

This appears to be a real Google Contacts/People MCP skill, but some destructive contact operations can run without enforced user confirmation.

Review this before installing on any account where contacts matter. Use GOG_READONLY=1 for lookup-only use, restrict OAuth scopes and the configured account, and avoid enabling write access on shared or autonomous-agent hosts until delete and dedupe-apply require explicit confirmation.

Vulnerability Patterns
  • 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
  • Embedded Malicious CodeShips malicious scripts inside the skill and executes them locally
Findings (2)

T09 · Insecure Skill Coding Practices

Error
Location
src/tools/contacts-extra.ts:109
Finding

Single-contact deletion bypasses enforced user confirmation

Content
View full analysis

Vulnerability Details

File Location: src/tools/contacts-extra.ts, lines 109–117
Vulnerability Type: Missing authorization confirmation for a destructive operation
Risk Level: High

Complete Code Snippet

ts
server.registerTool('gog_contacts_delete', {
  description: 'Delete a Google Contact by resource name.',
  annotations: { destructiveHint: true, openWorldHint: true },
  inputSchema: z.object({
    resourceName: z.string().describe('Contact resource name (people/...)'),
    account: accountParam,
  }),
}, async ({ resourceName, account }) => {
  return runOrDiagnose(['contacts', 'delete', pos(resourceName), '--force'], { account }); // gog gates this op; without --force the runner's --no-input makes it refuse
});

Technical Analysis

The MCP tool accepts a contact resource name and immediately invokes the authenticated gog client with contacts delete and --force. The inline comment confirms that --force is deliberately included to bypass the underlying command’s interactive safety gate, which would otherwise refuse deletion because the runner uses --no-input.

The destructiveHint annotation only describes the operation to MCP clients; it does not enforce approval. Unlike the batch-delete implementation in the same file, this handler does not call requireDispatchConfirmation, require a one-time token, or otherwise prove that the user approved the exact account and contact being deleted.

The relevant trust boundary is between an MCP caller or autonomous agent action and the user’s authenticated Google Contacts account. A caller able to invoke the tool can perform the deletion using the account credentials already available to the MCP server.

Attack Path

  1. The server is configured with an authenticated Google account.
  2. An MCP caller, including an agent acting on mistaken or untrusted instructions, obtains or selects a contact resource name.
  3. The caller invokes gog_contacts_delete with that resource nam ...[truncated 765 chars]
Remediation
View remediation

Remediation Suggestions

Use requireDispatchConfirmation before invoking the command, following the existing batch-delete pattern. Bind the confirmation to:

  • The exact contact resource name
  • The selected Google account
  • The delete action
  • A short expiration period
  • One-time token consumption

Only append --force after the confirmation succeeds. If confirmation is unavailable, omit --force so the underlying non-interactive command refuses the operation. Add tests proving that an initial unconfirmed call cannot reach runOrDiagnose, that a valid token authorizes only the exact bound target, and that replayed or modified confirmations are rejected.

T09 · Insecure Skill Coding Practices

Error
Location
src/tools/contacts-extra.ts:194
Finding

Destructive contact deduplication can be applied without preview-bound confirmation

Content
View full analysis

Vulnerability Details

File Location: src/tools/contacts-extra.ts, lines 194–217
Vulnerability Type: Missing authorization confirmation for broad destructive mutation
Risk Level: High

Complete Code Snippet

ts
server.registerTool('gog_contacts_dedupe', {
  description: 'Find likely duplicate personal contacts. Defaults to a read-only preview of the merge plan; set apply to actually merge each duplicate group and delete the redundant contacts (etag-checked, with ambiguous or unmergeable groups refused). Scope a risky apply with resource. Always preview first.',
  annotations: { destructiveHint: true, openWorldHint: true },
  inputSchema: z.object({
    match: z.string().optional().describe('Match fields, comma-separated from email,phone,name (default: email,phone)'),
    max: z.number().optional().describe('Max contacts to scan (0 = all). Mutually exclusive with resource — gog rejects passing both.'),
    resource: z.array(z.string()).optional().describe('Limit dedupe to these exact contact resource names (e.g. people/c123), repeatable — useful to scope an apply to known duplicates. Mutually exclusive with max.'),
    apply: z.boolean().optional().describe('Merge each duplicate group and DELETE the redundant contacts. Without this the tool only previews the plan. Destructive — preview first.'),
    failEmpty: z.boolean().optional().describe('Exit with an error (code 3) when no duplicates are found, instead of succeeding empty.'),
    account: accountParam,
  }),
}, async ({ match, max, resource, apply, failEmpty, account }) => {
  const args: GogArg[] = ['contacts', 'dedupe'];
  if (match) args.push(`--match=${match}`);
  if (max !== undefined) args.push(`--max=${max}`);
  if (resource) for (const r of resource) args.push(`--resource=${r}`);
  if (apply) args.push('--apply');
  if (failEmpty) args.push('--fail-empty');
  if (apply) args.push('--force'); // gog gates this op; without --force the runner's --no-input makes it refuse
  return
...[truncated 2062 chars]
Remediation
View remediation

Remediation Suggestions

Implement a two-stage, preview-bound authorization flow:

  1. Run deduplication without --apply and return the proposed merge plan.
  2. Compute a stable digest over the exact plan, account, matching criteria, and resource scope.
  3. Ask the user to approve that specific plan through requireDispatchConfirmation.
  4. Issue a short-lived, one-time confirmation token bound to the digest.
  5. Permit --apply --force only when a valid token accompanies unchanged arguments and an unchanged plan.

For broad operations, require an explicit account-wide scope selection rather than treating an omitted resource list as sufficient authorization. Add tests confirming that apply: true cannot directly reach runOrDiagnose, modified arguments invalidate approval, expired or replayed tokens fail, and the preview remains the default behavior.

Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • System Prompt LeakageDirect Leakage, Indirect Extraction, Tool-Based Exfiltration
  • YARA SignaturesMalware Match, Webshell Match, Cryptominer Match
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
Findings (49)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This is a clear description-behavior mismatch. The declared purpose is an integration for searching/managing Google Contacts and the People API, which would imply API calls, contact/directory resource handling, query construction, or authentication around Google services. Instead, the code is generic application/library code from Zod, focused on validating and transforming in-memory data structures and generating validation errors/locales. No Google API access, contact lookup logic, People API operations, or directory-management behavior appears in the provided chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This is a clear description-behavior mismatch. The declared purpose says the skill should search/manage Google Contacts and People API data. However, the code shown only defines localized error message formatters for validation errors (from Zod locales such as en, es, fr, ja, etc.). There is no evidence of API clients, Google authentication, HTTP requests, contact CRUD operations, directory search, or any People API query logic. The code’s primary purpose is validation/i18n support, which is unrelated to the declared skill description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

This code does not implement any contact lookup, contact management, Google API integration, Workspace directory search, or People API queries. Instead, it defines locale-specific error-message handlers for validation issues such as invalid types, formats, sizes, and keys. That is a materially different primary purpose from the declared description, so this is a clear mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The code shown is unrelated to Google Contacts or the People API. It contains locale-specific error formatters (Thai, Turkmen, Turkish, Ukrainian, Urdu, Uzbek, Vietnamese, Chinese, Yoruba, etc.) and Zod core registry/compile/validation functions for strings, numbers, arrays, objects, unions, records, maps, sets, transforms, and other schema features. There are no API calls, OAuth flows, contact lookups, directory queries, profile retrieval, or Workspace/People API operations. Therefore the actual behavior materially differs from the declared purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill is for searching and managing Google Contacts and People API data. However, the supplied code is unrelated infrastructure code from the Zod library: it defines validators (strings, numbers, arrays, objects, unions, etc.), checks like min/max/regex, transformations, and conversion to/from JSON Schema. There are no Google API calls, no HTTP requests, no OAuth/auth handling, no contact fields or directory queries, and no resource access consistent with Contacts/People API. This is a clear material mismatch in primary purpose and capabilities.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The code shown is unrelated to Google Contacts or the Google People API. It primarily defines functions like convertSchema/fromJSONSchema, traverses and rewrites Zod schemas, and includes extensive MCP request/response schema definitions, validation helpers, protocol version handling, and OAuth metadata/error structures. There are no visible Google API calls, People API endpoints, contact-management operations, authentication flows specific to Google Contacts, or triggers tied to contact lookup tasks. This is a material purpose mismatch, not just an internal implementation detail.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code chunk contains no Google Contacts, Google People API, Workspace directory, contact lookup, or Google API integration behavior. Instead, it is broad protocol infrastructure code for validating and handling MCP/JSON-RPC messages, schemas, notifications, subscriptions, and transport semantics. This is a materially different primary purpose from the declared description, so the description does not accurately represent the code's behavior.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill should search or manage Google Contacts and the People API. However, the supplied code is infrastructure code for a protocol SDK/runtime, not application logic for Google APIs. There are no calls to Google Contacts, People API, Workspace directory endpoints, OAuth scopes, contact fields, or query operations by name/email/phone. Instead, the code focuses on transport-layer messaging, handler setup, validation, buffering, and general-purpose schema/codegen utilities. That is a materially different primary purpose, so this is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description says this skill should handle Google Contacts and People API queries, including contact lookup and Workspace directory search. The actual code does not interact with Google APIs, contacts, directories, network services, or user profile data at all. It is purely a test configuration file for Vitest, establishing a filesystem-related environment variable and coverage requirements. This is a materially different primary purpose, so the description does not accurately represent the code chunk.

Content

No source excerpt is available for this finding.

YARA rule 'agent_skill_prompt_injection_hidden_instructions': Prompt injection or hidden instructions embedded in AI agent skill text [agent_skills]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · dist/index.js (reported line 43)May include surrounding context.

js
../node_modules/@chrischall/mcp-utils/dist/caller/index.js
import { AsyncLocalStorage as AsyncLocalStorage2 } from "node:async_hooks";
var storage2 = new AsyncLocalStorage2();
function withCallerCapabilities(capabilities, fn) {
  return capabilities ? storage2.run(capabilities, fn) : fn();
}
function currentCallerCapabilities() {
  return storage2.getStore();
}
var ENVELOPE_CAPABILITIES_KEY = "io.modelcontextprotocol/clientCapabilities";
var ELICITATION_MODES = ["form", "url"];
function isRecord(value) {
  return typeof value === "object" && value !== null && !Array.isArray(value);
}
function callerCapabilities(ctx) {
  const envelope = isRecord(ctx) && isRecord(ctx.mcpReq) ? ctx.mcpReq.envelope : void 0;
  if (isRecord(envelope)) {
    const declared = envelope[ENVELOPE_CAPABILITIES_KEY];
    if (isRecord(declared))
      return declared;
  }
  return currentCallerCapabilities();
}
function callerAcceptsFormElicitation(ctx) {
  const capabilities = callerCapabilities(ctx);
  if (!capa

YARA rule 'exploit_framework': Exploit framework components and payloads [hacktools]

High
Category
YARA Match
Confidence
80% confidence
Finding

YARA rule matched a hack tool or exploit indicator (offensive tools, reconnaissance, privilege escalation, or exploit frameworks).

Content

Scanner excerpt · dist/index.js (reported line 18139)May include surrounding context.

js
ams);
}
function keyof(schema) {
  const shape = schema._zod.def.shape;
  return _enum2(Object.keys(shape));
}
var ZodObject = /* @__PURE__ */ $constructor("ZodObject", (inst, def) => {
  _ensureDefaultMemoizer();
  $ZodObjectJIT.init(inst, def);
  ZodType.init(inst, def);
  inst._zod.processJSONSchema = (ctx, json2, params) => objectProcessor(inst, ctx, json2, params);
  util_exports.installLazyProp(inst, "shape", (self) => self._zod.def.shape, false);
}, {
  keyof() {
    return _enum2(Object.keys(this._zod.def.shape));
  },
  catchall(catchall) {
    return this.clone(util_exports.mergeDefs(this._zod.def, { catchall }));
  },
  passthrough() {
    return this.clone(util_exports.mergeDefs(this._zod.def, { catchall: unknown() }));
  },
  loose() {
    return this.clone(util_exports.mergeDefs(this._zod.def, { catchall: unknown() }));
  },
  strict() {
    return this.clone(util_exports.mergeDefs(this._zod.def, { catchall: never() }));
  },
  strip() {
    return this.clone(util_exports

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · dist/index.js (reported line 27088)May include surrounding context.

js
function validateAsync3() {
      const ruleErrs = gen.let("ruleErrs", null);
      gen.try(() => assignValid((0, codegen_1._)`await `), (e) => gen.assign(valid, false).if((0, codegen_1._)`${e} instanceof ${it.ValidationError}`, () => gen.assign(ruleErrs, (0, codegen_1._)`${e}.errors`), () => gen.throw(e)));
      return ruleErrs;
    }
    function validateSync() {
      const validateErrs = (0, codegen_1._)`${validateRef}.errors`;

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · dist/index.js (reported line 33507)May include surrounding context.

js
if (typeof promptName !== "string") return;
        const prompt = this._registeredPrompts[promptName];
        if (prompt === void 0 || !prompt.enabled) return;
        return prompt.scopeChallenge?.(context);
      }
      default:
        return;

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · dist/index.js (reported line 33709)May include surrounding context.

js
if (typeof promptName !== "string") return;
        const prompt = this._registeredPrompts[promptName];
        if (prompt === void 0 || !prompt.enabled) return;
        return prompt.scopeChallenge?.(context);
      }
      default:
        return;

Direct Prompt Extraction

High
Category
System Prompt Leakage
Confidence
85% confidence
Finding

Skill contains instructions that could directly expose system prompts, internal rules, or hidden instructions to users or external parties.

Content

Scanner excerpt · dist/index.js (reported line 34642)May include surrounding context.

js
return walk(value, keep, drop);
}
function normalizeRules(rules) {
  return rules.map((rule) => typeof rule === "string" ? rule.toLowerCase() : new RegExp(rule.source, rule.flags));
}
function matchesRule(key, rules) {
  const lower = key.toLowerCase();

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · dist/index.js (reported line 35806)May include surrounding context.

js
var TOKEN_LEFT_BOUNDARY = "(?<![A-Za-z0-9+/])";
var GOOGLE_TOKEN_PATTERNS = [
  new RegExp(`${TOKEN_LEFT_BOUNDARY}ya29\\.[A-Za-z0-9._\\-]+`, "g"),
  // OAuth2 access tokens
  new RegExp(`${TOKEN_LEFT_BOUNDARY}1//[A-Za-z0-9._\\-]+`, "g")
  // OAuth2 refresh tokens
];

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 51)May include surrounding context.

yaml
The Google account to act as. Required for the startup auth bootstrap
      (the refresh token is imported under this email); otherwise defaults to
      gog's single/most recent account.
  - name: GOG_KEYRING_BACKEND
    required: false
    help: >-
      gog's credential store. Set to "file" on a headless host — there is no OS

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 56)May include surrounding context.

yaml
The Google account to act as. Required for the startup auth bootstrap
      (the refresh token is imported under this email); otherwise defaults to
      gog's single/most recent account.
  - name: GOG_KEYRING_BACKEND
    required: false
    help: >-
      gog's credential store. Set to "file" on a headless host — there is no OS

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 61)May include surrounding context.

yaml
The Google account to act as. Required for the startup auth bootstrap
      (the refresh token is imported under this email); otherwise defaults to
      gog's single/most recent account.
  - name: GOG_KEYRING_BACKEND
    required: false
    help: >-
      gog's credential store. Set to "file" on a headless host — there is no OS

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 55)May include surrounding context.

yaml
required: false
    help: >-
      gog's credential store. Set to "file" on a headless host — there is no OS
      keychain — so the keyring lives on the persistent data dir.
  - name: GOG_KEYRING_PASSWORD
    secret: true
    required: false

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · dist/index.js (reported line 36423)May include surrounding context.

js
import { registerExtraContactsTools } from './tools/contacts-extra.js';


// Seed gog's keyring from GOG_CLIENT_ID/SECRET/REFRESH_TOKEN/ACCOUNT when the host injects them.
await bootstrapGogAuth();

await runMcp({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · dist/index.js (reported line 36434)May include surrounding context.

js
import { registerExtraContactsTools } from './tools/contacts-extra.js';


// Seed gog's keyring from GOG_CLIENT_ID/SECRET/REFRESH_TOKEN/ACCOUNT when the host injects them.
await bootstrapGogAuth();

await runMcp({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 16)May include surrounding context.

yaml
import { registerExtraContactsTools } from './tools/contacts-extra.js';


// Seed gog's keyring from GOG_CLIENT_ID/SECRET/REFRESH_TOKEN/ACCOUNT when the host injects them.
await bootstrapGogAuth();

await runMcp({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 20)May include surrounding context.

yaml
import { registerExtraContactsTools } from './tools/contacts-extra.js';


// Seed gog's keyring from GOG_CLIENT_ID/SECRET/REFRESH_TOKEN/ACCOUNT when the host injects them.
await bootstrapGogAuth();

await runMcp({

Credential Access

High
Category
Privilege Escalation
Confidence
70% confidence
Finding

Code accesses credential files (SSH keys, AWS credentials, etc.). This could indicate credential theft attempts.

Content

Scanner excerpt · mint.yaml (reported line 26)May include surrounding context.

yaml
import { registerExtraContactsTools } from './tools/contacts-extra.js';


// Seed gog's keyring from GOG_CLIENT_ID/SECRET/REFRESH_TOKEN/ACCOUNT when the host injects them.
await bootstrapGogAuth();

await runMcp({

Static analysis

Detected: suspicious.dangerous_exec, suspicious.dynamic_code_execution, suspicious.env_credential_access

Shell command execution detected (child_process).

Critical
Code
suspicious.dangerous_exec
Location
dist/index.js:35905

Dynamic code execution detected.

Critical
Code
suspicious.dynamic_code_execution
Location
dist/index.js:27959

Environment variable access combined with network send.

Critical
Code
suspicious.env_credential_access
Location
dist/index.js:35040