Back to skill

Security audit

Google Health

Security checks for vulnerabilities and agentic risk

Overview

This Google Health reader discloses most sensitive behavior, but needs review because configuration can send logged-in requests to a non-Google server or ask for broader Google permissions.

Review carefully before installing. Only use it with your own Google account, protect config.json and token.json, avoid running it from untrusted directories, and do not set or accept custom base_url, GOOGLE_HEALTH_BASE_URL, or unexpected scopes unless you fully trust that configuration. Check the Google consent screen for exactly the intended read-only Health scopes.

Vulnerability Patterns
  • Unauthorized Access and Privilege EscalationObtains permissions beyond the task's legitimate needs
  • 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 (2)

T09 · Insecure Skill Coding Practices

Error
Location
internal/config/config.go:199
Finding

Arbitrary API base URL can receive authenticated Google Health requests

Content
View full analysis
Remediation
View remediation

T05 · Unauthorized Access and Privilege Escalation

Warning
Location
internal/config/config.go:199
Finding

Configurable OAuth scopes can exceed the declared read-only Google Health permissions

Content
View full analysis
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Rogue AgentSelf-Modification, Session Persistence
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
Findings (34)

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is about a self-contained read-only client for Google Health v4 that authenticates, reads health data, and returns JSON. The supplied code chunk instead implements a shell-completion command for the CLI. This is a materially different purpose from the declared functionality and introduces an undeclared capability (generation of shell completion scripts). While shell completion can be a supporting feature of a CLI, this specific chunk does not perform any of the described Google Health behaviors, so the code chunk does not accurately represent the declared description.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
96% confidence
Finding

The declared description presents the skill as a self-contained, read-only client for fetching Google Health API data and arbitrary read-only v4 API paths, emphasizing that it does no filtering, merging, or writing. The supplied code chunk instead defines a doctor command whose primary function is local diagnostics: it resolves config, loads a cached token, checks whether a current token can be obtained, and emits JSON containing token validity plus local file paths, configured user, scopes, base URL, and version. It also prints remediation guidance for missing OAuth client credentials or missing authentication. While token handling is a supporting aspect of an OAuth-based client, this code's behavior is materially different from the declared health-data retrieval purpose and includes output of sensitive local/account metadata not mentioned in the description. Therefore this chunk is a description-behavior mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
98% confidence
Finding

This code chunk does not implement the declared read-only Google Health client behavior. Instead, it is a documentation test/guardrail ensuring certain warning text exists in markdown files. That is a materially different primary purpose from reading Google Health data, authenticating with OAuth2, or exposing v4 API GET functionality. The file access shown is limited to local repository metadata and documentation, which is inconsistent with the declared external API-reading behavior. While such tests may be part of the same repository, this specific supplied code chunk does not match the declared skill functionality.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code chunk does not implement a Google Health API client, OAuth2 authentication, JSON data retrieval, daily roll-ups, exercise parsing, or read-only API path access. Instead, it is a unit/privacy test that walks local testdata files and checks them for realistic identifiers to enforce synthetic fixture hygiene. This is a materially different purpose and behavior from the declared description, so it is a clear mismatch.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The supplied code is a narrow CLI utility command for outputting build version metadata. Its behavior is unrelated to the declared primary purpose of a self-contained Google Health read-only client. While a larger project could still contain the declared functionality elsewhere, this specific code chunk does not match that description and instead implements an unrelated command.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a self-contained read-only Google Health API client. The supplied code chunk does not implement any health-data access, API communication, OAuth, JSON API retrieval, or read-only endpoint handling. Instead, it is a version metadata utility that exposes build information such as version, commit, date, and Go runtime version. This is a materially different primary purpose, so the description does not accurately represent this code chunk.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a self-contained, read-only Google Health API client. The supplied code does something entirely different: it is a Python script for detecting drift and synchronizing skill package files across repository subtrees. It interacts with the local filesystem and Git, not Google services; it has no OAuth, HTTP requests, API client logic, or health-data parsing. It also supports destructive actions such as copying and deleting files when run with --fix, which is materially inconsistent with the declared read-only health-data access purpose.

Content

No source excerpt is available for this finding.

Tp4

High
Category
MCP Tool Poisoning
Confidence
99% confidence
Finding

The declared description is for a standalone, read-only Google Health API client. The supplied code chunk is instead a Python test script for a repository skill-sync gate. Its primary purpose, resources accessed, and behavior are entirely different: it creates temporary git repositories, copies scripts, edits and deletes local files, runs subprocesses, and verifies sync/fix behavior across harness directories. None of the described Google Health, OAuth2, read-only API, or JSON-emitting functionality is present. This is a clear description-behavior mismatch.

Content

No source excerpt is available for this finding.

Vague Triggers

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The instruction to treat broad phrases like "can you...", "I want to...", and "help me..." as authorization triggers is overly permissive and can cause the skill to interpret ordinary conversational text as approval to act. In an agent setting, this increases the chance of unintended execution scope, especially when combined with the document's guidance to continue work independently and persist authorization across turns.

Content

No source excerpt is available for this finding.

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 · internal/auth/tokencache.go (reported line 118)May include surrounding context.

go
// persistingSource wraps a refreshing TokenSource and re-saves the token to disk
// whenever .Token() yields a newer one (an access-token rotation after a
// refresh). x/oauth2 handles refresh-token rotation internally; we only need to
// persist the result so the on-disk access token stays fresh between runs
// (GOAL.md §8 — ghealth doesn't do this; we should).
type persistingSource struct {
	base   oauth2.TokenSource

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 · internal/auth/tokencache.go (reported line 142)May include surrounding context.

go
// persistingSource wraps a refreshing TokenSource and re-saves the token to disk
// whenever .Token() yields a newer one (an access-token rotation after a
// refresh). x/oauth2 handles refresh-token rotation internally; we only need to
// persist the result so the on-disk access token stays fresh between runs
// (GOAL.md §8 — ghealth doesn't do this; we should).
type persistingSource struct {
	base   oauth2.TokenSource

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 · internal/cli/auth.go (reported line 119)May include surrounding context.

go
// persistingSource wraps a refreshing TokenSource and re-saves the token to disk
// whenever .Token() yields a newer one (an access-token rotation after a
// refresh). x/oauth2 handles refresh-token rotation internally; we only need to
// persist the result so the on-disk access token stays fresh between runs
// (GOAL.md §8 — ghealth doesn't do this; we should).
type persistingSource struct {
	base   oauth2.TokenSource

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
94% confidence
Finding

The guide tells users to place an OAuth client ID and client secret into config.json or environment variables but does not explicitly warn that these values are sensitive and must be protected from source control, logs, backups, and shared directories. Even though this is a personal desktop OAuth client and not a high-privilege server credential, disclosure could let another party impersonate the app configuration or abuse the OAuth setup in combination with user interaction or stolen refresh tokens.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The document tells the user to click through an 'unsafe' browser warning, which is a security-relevant action affecting account privacy, but it does not prominently warn that this is only acceptable because the project was created by the same user for their own account. Without a clear caution, readers may normalize bypassing similar warnings in less safe contexts.

Content

No source excerpt is available for this finding.

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
70% confidence
Finding

Without declared permissions the skill's intent is opaque and cannot be validated.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
93% confidence
Finding

These tests validate SaveToken writing token.json and enforcing file permissions, demonstrating intentional local file creation/modification. The manifest's absolute wording about doing no writing is misleading unless interpreted as only applying to remote API data, because the implementation clearly writes OAuth cache files locally.

Content

No source excerpt is available for this finding.

Description-Behavior Mismatch

Medium
Category
Not specified by scanner
Confidence
87% confidence
Finding

The manifest frames the skill as a read-only Google Health API client and explicitly says it does no writing. This test covers DeleteToken, which removes a local cached OAuth token file, showing the implementation includes local state mutation beyond pure read-only data access.

Content

No source excerpt is available for this finding.

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · internal/config/config.go (reported line 45)May include surrounding context.

go
// DefaultScopes is the full set of read-only Google Health scopes the tool
// requests at login, so an authorized token can read every data type the API
// exposes. Read-only only — the tool never requests a write scope.
var DefaultScopes = []string{
	"https://www.googleapis.com/auth/googlehealth.profile.readonly",
	"https://www.googleapis.com/auth/googlehealth.settings.readonly",

Session Persistence

Medium
Category
Rogue Agent
Confidence
60% confidence
Finding

Skill establishes unauthorized persistence across sessions via cron jobs, startup scripts, or state files. Session persistence allows an attacker to maintain access beyond the current interaction.

Content

Scanner excerpt · internal/config/config_test.go (reported line 15)May include surrounding context.

go
t.Helper()
	path := filepath.Join(dir, "config.json")
	if err := os.WriteFile(path, []byte(body), 0o600); err != nil {
		t.Fatalf("write config: %v", err)
	}
	return path
}

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
85% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 22)May include surrounding context.

python
SUBTREE = "skills"

# A reference to something INSIDE the harness's own skills tree, in either slash flavor — the
# skills are written on Windows and quote both `.claude/skills/verify-openloop/...` and
# `pwsh <repo>\.claude\skills\run-e2e\...`. A deeper path is required so that prose enumerating
# the three trees ("`.claude/skills/`, `.cursor/skills/`, `.codex/skills/`", which harness-sync's
# own SKILL.md does) is left alone: that is a list of all three, not a pointer at one.

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 65)May include surrounding context.

python
since a new skill written into one harness is exactly what needs propagating.
    """
    top = f".{harness}/{SUBTREE}"
    out = subprocess.run(["git", "ls-files", "-z", "--cached", "--others", "--exclude-standard", top],
                         cwd=ROOT, check=True, capture_output=True, text=True).stdout
    return {p[len(top) + 1:]: ROOT / p for p in out.split("\0") if p}

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 83)May include surrounding context.

python
three harnesses (a brand-new skill and the copies a previous --fix wrote look identical to it),
    and the caller reports that case rather than picking a winner from no evidence.
    """
    default = subprocess.run(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=ROOT,
                             capture_output=True, text=True).stdout.strip()
    base = subprocess.run(["git", "merge-base", default or "origin/main", "HEAD"], cwd=ROOT,
                          capture_output=True, text=True).stdout.strip()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 85)May include surrounding context.

python
"""
    default = subprocess.run(["git", "symbolic-ref", "refs/remotes/origin/HEAD"], cwd=ROOT,
                             capture_output=True, text=True).stdout.strip()
    base = subprocess.run(["git", "merge-base", default or "origin/main", "HEAD"], cwd=ROOT,
                          capture_output=True, text=True).stdout.strip()
    paths = [f".{h}/{SUBTREE}/{rel}" for h in HARNESSES for rel in rel_paths]
    changed = set()

subprocess module call

Medium
Category
Dangerous Code Execution
Confidence
70% confidence
Finding

subprocess module calls execute external commands. Without careful input validation, this enables command injection.

Content

Scanner excerpt · scripts/sync-harness-skills.py (reported line 94)May include surrounding context.

python
cmds = ([["git", "diff", "--name-only", base, "--", *paths]] if base else []) + \
           [["git", "status", "--porcelain", "--", *paths]]
    for cmd in cmds:
        out = subprocess.run(cmd, cwd=ROOT, capture_output=True, text=True).stdout
        for line in out.splitlines():
            line = line[3:] if cmd[1] == "status" else line          # strip porcelain XY prefix
            for h in HARNESSES:

Skill Enumeration

Medium
Category
Agent Snooping
Confidence
80% confidence
Finding

Skill enumerates or reads other installed skills. Access to other skills' SKILL.md files or the skills directory reveals prompt instructions, capabilities, and secrets that should be invisible to peer skills.

Content

Scanner excerpt · scripts/test-sync-harness-skills.py (reported line 38)May include surrounding context.

python
SCRIPT = Path(__file__).resolve().parent / "sync-harness-skills.py"
HARNESSES = ("claude", "cursor", "codex")
SKILL = "skills/demo/SKILL.md"


def build_repo(tmp):

Static analysis

No suspicious patterns detected.