Back to skill

Security audit

Lead Scorer

Security checks for vulnerabilities and agentic risk

Overview

This lead-scoring skill is mostly coherent, but it can create Google Sheets that are publicly writable and can write untrusted lead data as formulas.

Review before installing. Remove the public `anyone` writer share, keep new sheets private or share only with specific trusted accounts, prefer narrower Google Drive permissions, and write lead values as RAW or sanitize formula prefixes. Also keep the service-account credential file outside shared folders and review any sheets previously created with this skill.

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)

T05 · Unauthorized Access and Privilege Escalation

Error
Location
SKILL.md:148
Finding

Newly Created Google Sheets Are Publicly Writable

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 148–153
Vulnerability Type: Anonymous write access and excessive permissions
Risk Level: High

Vulnerable Code

python
try:
    sheet = client.open(sheet_name).sheet1
except gspread.SpreadsheetNotFound:
    # Create the sheet if it doesn't exist
    spreadsheet = client.create(sheet_name)
    spreadsheet.share(None, perm_type="anyone", role="writer")
    sheet = spreadsheet.sheet1
    # Write headers
    sheet.append_row(SHEET_COLUMNS)
    print(f"Created new sheet: {sheet_name}")

Technical Analysis

When the requested spreadsheet does not exist, the code creates one and grants the anonymous public the writer role:

python
spreadsheet.share(None, perm_type="anyone", role="writer")

This sharing configuration breaks the principle of least privilege. Public write access is not required to score leads or record results. Anyone who obtains or discovers the spreadsheet URL can potentially access and modify the stored lead data without authenticating.

The spreadsheet contains business names, URLs, email addresses, telephone numbers, audit results, scores, and operational notes. Anonymous write access therefore affects both the confidentiality and integrity of potentially sensitive business data.

The Google authorization configuration also requests the broad Drive scope:

python
"https://www.googleapis.com/auth/drive"

Although this scope may be needed for some sharing operations, it is broader than necessary for ordinary spreadsheet updates and increases the consequences of service-account credential compromise.

Attack Path

  1. The Skill attempts to open a spreadsheet that does not exist.
  2. The SpreadsheetNotFound handler creates a new spreadsheet.
  3. The code grants writer access to anyone, without requiring authentication.
  4. An attacker obtains or discovers the spreadsheet URL through a ...[truncated 1106 chars]
Remediation
View remediation

Remediation Suggestions

  1. Remove anonymous sharing entirely:

    python
    spreadsheet = client.create(sheet_name)
    sheet = spreadsheet.sheet1
    sheet.append_row(SHEET_COLUMNS)
    
  2. Keep newly created spreadsheets private by default.

  3. If collaboration is required, share only with explicitly configured and validated email addresses.

  4. Grant the minimum required role, such as reader for viewers or writer only for authorized editors.

  5. Require an explicit configuration option before performing any sharing operation.

  6. Reject configurations that request public or domain-wide write access.

  7. Where compatible with the required workflow, replace the full Drive scope with a narrower scope such as:

    python
    scopes = [
        "https://www.googleapis.com/auth/spreadsheets",
        "https://www.googleapis.com/auth/drive.file",
    ]
    
  8. Review spreadsheets previously created by this Skill and revoke existing anyone permissions.

  9. Enable audit logging and periodically inspect permission changes on generated spreadsheets.

T09 · Insecure Skill Coding Practices

Warning
Location
SKILL.md:164
Finding

Spreadsheet Formula Injection Through Untrusted Lead Data

Content
View full analysis

Vulnerability Details

File Location: SKILL.md, lines 164–221
Vulnerability Type: Spreadsheet formula injection
Risk Level: Medium

Vulnerable Code

python
def lead_to_row(lead: dict) -> list:
    """Convert a scored lead dict to a sheet row."""
    return [
        datetime.now().strftime("%Y-%m-%d"),
        lead.get("business_name", ""),
        lead.get("url", ""),
        lead.get("lead_score", 0),
        lead.get("tier", ""),
        lead.get("primary_email", ""),
        ", ".join(lead.get("emails", [])),
        lead.get("primary_phone", ""),
        lead.get("copyright_year", ""),
        ", ".join(lead.get("tech_stack", [])[:3]),          # Top 3 techs
        lead.get("pagespeed_mobile", ""),
        "✅" if lead.get("has_ssl") else "❌",
        "✅" if lead.get("is_mobile_friendly") else "❌",
        " | ".join(lead.get("issues", [])[:5]),             # Top 5 issues
        " | ".join(lead.get("score_breakdown", [])[:5]),
        "New",
        ""
    ]

def write_lead_to_sheet(lead: dict, sheet=None) -> bool:
    """Score the lead and write it to the Google Sheet."""
    if sheet is None:
        sheet = get_sheet()
    ensure_headers(sheet)

    scored = score_lead(lead)
    row = lead_to_row(scored)

    try:
        sheet.append_row(row, value_input_option="USER_ENTERED")
        return True
    except Exception as e:
        print(f"Sheet write error: {e}")
        return False

def write_leads_batch(leads: list[dict], sheet=None) -> int:
    """Write multiple leads at once. Returns count written."""
    if sheet is None:
        sheet = get_sheet()
    ensure_headers(sheet)

    rows = []
    for lead in leads:
        scored = score_lead(lead)
        if scored.get("lead_score", 0) >= 0:  # Always write (filter upstream)
            rows.append(lead_to_row(scored))

    if rows:
        sheet.append_rows(rows, value_input
...[truncated 3041 chars]
Remediation
View remediation

Remediation Suggestions

  1. Write untrusted lead data using raw input mode:

    python
    sheet.append_row(row, value_input_option="RAW")
    sheet.append_rows(rows, value_input_option="RAW")
    
  2. Add a centralized sanitizer for every untrusted string:

    python
    def sanitize_sheet_value(value):
        if not isinstance(value, str):
            return value
    
        stripped = value.lstrip()
        if stripped.startswith(("=", "+", "-", "@")):
            return "'" + value
    
        return value
    
  3. Apply the sanitizer to all values in lead_to_row() before returning the row:

    python
    row = [
        # Construct fields here
    ]
    return [sanitize_sheet_value(value) for value in row]
    
  4. Prefer RAW mode even when sanitization is present, unless formula evaluation is explicitly required.

  5. Validate field types and lengths before writing them to the spreadsheet.

  6. Treat all website-derived and enrichment-derived values as untrusted, including values inside lists.

  7. Add tests covering values beginning with =, +, -, and @, including variants preceded by whitespace or control characters.

  8. Inspect existing spreadsheets for formula-bearing cells in columns populated from external website data.

  9. Avoid relying solely on upstream sanitization; enforce protection at the final spreadsheet output boundary.

Vulnerability Patterns
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • MCP Tool PoisoningHidden Instructions, Unicode Deception, Parameter Description Injection
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
Findings (6)

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 · SKILL.md (reported line 130)May include surrounding context.

md
def get_sheet(sheet_name: str = None, creds_file: str = None):
    """Authenticate and return the target Google Sheet."""
    sheet_name = sheet_name or os.environ.get("GOOGLE_SHEET_NAME", "Website Leads")
    creds_file = creds_file or os.environ.get("GOOGLE_CREDS_FILE", "credentials.json")
    
    scopes = [
        "https://www.googleapis.com/auth/spreadsheets",

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 · SKILL.md (reported line 225)May include surrounding context.

md
def get_sheet(sheet_name: str = None, creds_file: str = None):
    """Authenticate and return the target Google Sheet."""
    sheet_name = sheet_name or os.environ.get("GOOGLE_SHEET_NAME", "Website Leads")
    creds_file = creds_file or os.environ.get("GOOGLE_CREDS_FILE", "credentials.json")
    
    scopes = [
        "https://www.googleapis.com/auth/spreadsheets",

Description-Behavior Mismatch

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

When the target spreadsheet does not exist, the skill creates a new one and immediately shares it with perm_type="anyone" and role="writer". That exposes all lead data to unauthenticated modification by the public, enabling data tampering, spam insertion, or destruction of the sheet contents far beyond the stated purpose of merely recording scored leads.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

High
Category
Not specified by scanner
Confidence
100% confidence
Finding

The code path at this line makes the spreadsheet writable by anyone on the internet. Public write access is a direct integrity and confidentiality risk because lead records, emails, phone numbers, and notes can be altered, deleted, or poisoned by arbitrary third parties.

Content

No source excerpt is available for this finding.

Missing User Warnings

High
Category
Not specified by scanner
Confidence
99% confidence
Finding

The skill silently enables public write access during sheet creation without any user warning or consent. This is dangerous in context because the sheet is intended to store prospect and contact data, so unauthorized users could corrupt lead pipelines, inject malicious formulas/content, or overwrite business records.

Content

No source excerpt is available for this finding.

Context-Inappropriate Capability

Low
Category
Not specified by scanner
Confidence
82% confidence
Finding

The manifest says the skill scores leads, assigns tiers, and writes the result to a Google Sheet. This additional function modifies spreadsheet presentation rules through the Sheets API and requires extra tooling beyond the listed packages, which is outside the declared core scope of scoring and writing results.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.