Back to skill

Security audit

Formora Survey

Security checks for vulnerabilities and agentic risk

Overview

The skill mostly matches its survey purpose, but its paid-ad launch safeguards can fail and allow repeated live campaigns beyond the stated shared budget caps.

Review this skill carefully before installing if you will connect real Google Ads or Meta Ads accounts. Keep ads disabled unless needed, leave paused creation enabled, use small test budgets, and do not rely on the advertised shared budget caps as the only spending control. Treat exported responses and the ads state file as sensitive local data, and be aware that QR generation can fall back to a third-party QR service.

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 (1)

T09 · Insecure Skill Coding Practices

Error
Location
scripts/formora.py:143
Finding

Shared Advertising Budget Caps Can Be Bypassed Through Repeated Launches

Content
View full analysis
= day_start: daily += amount if ts >= week_start: weekly += amount return { "daily_spent_cny": round(daily, 2), "weekly_spent_cny": round(weekly, 2), "daily_remaining_cny": round(max(0.0, SAFE_DAILY_CAP_CNY - daily), 2), "weekly_remaining_cny": round(max(0.0, SAFE_WEEKLY_CAP_CNY - weekly), 2), "shared_daily_cap_cny": SAFE_DAILY_CAP_CNY, "shared_weekly_cap_cny": SAFE_WEEKLY_CAP_CNY, } def validate_ads_budget(daily_budget_cny, weekly_budget_cny, state): errors = [] if daily_budget_cny is None or weekly_budget_cny is None: errors.append("Both --daily-budget-cny and --weekly-budget-cny are required for ads-launch.") return errors if daily_budget_cny <= 0 or weekly_budget_cny <= 0: errors.append("Budgets must be positive.") if daily_budget_cny > SAFE_DAILY_CAP_CNY: errors.append(f"Requested daily budget {daily_budget_cny} exceeds shared daily cap {SAFE_DAILY_CAP_CNY} CNY.") if weekly_budget_cny > SAFE_WEEKLY_CAP_CNY: errors.append(f"Requested weekly budget {weekly_budget_cny} exceeds shared weekly cap {SAFE_WEEKLY_CAP_CNY} CNY.") summary = get_spend_summary(state) if daily_budget_cny > summary["daily_remaining_cny"]: ...[truncated 5478 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
  • Behavioral ASTexec() Call, eval() Call, Dynamic Import
  • Taint TrackingDirect Taint Flow, Variable-Mediated Taint Flow, Credential Exfiltration Chain
  • MCP Least PrivilegeUnderdeclared Capability, Wildcard Permission, Missing Permission Declaration
Findings (21)

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 99)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.read() if binary else json.loads(resp.read())
    except urllib.error.HTTPError as e:
        raw = e.read()

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 477)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.read() if binary else json.loads(resp.read())
    except urllib.error.HTTPError as e:
        raw = e.read()

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 498)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.read() if binary else json.loads(resp.read())
    except urllib.error.HTTPError as e:
        raw = e.read()

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 520)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.read() if binary else json.loads(resp.read())
    except urllib.error.HTTPError as e:
        raw = e.read()

Tainted flow: 'req' from os.environ.get (line 316, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 745)May include surrounding context.

python
headers["Content-Type"] = "application/json"
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    try:
        with urllib.request.urlopen(req) as resp:
            return resp.read() if binary else json.loads(resp.read())
    except urllib.error.HTTPError as e:
        raw = e.read()

Tainted flow: 'req' from os.environ.get (line 429, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 116)May include surrounding context.

python
body = json.dumps(data).encode("utf-8")
        headers.setdefault("Content-Type", "application/json")
    req = urllib.request.Request(url, data=body, headers=headers, method=method)
    with urllib.request.urlopen(req) as resp:
        raw = resp.read()
        if not raw:
            return {}

Tainted flow: 'req' from os.environ.get (line 351, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
93% confidence
Finding

If local QR generation fails, the code falls back to a third-party QR service and transmits the full survey URL externally. That can leak survey identifiers, embedded tokens, or otherwise sensitive distribution links to an unnecessary external provider without an explicit user opt-in.

Content

Scanner excerpt · scripts/formora.py (reported line 317)May include surrounding context.

python
except Exception:
        qr_api = f"https://api.qrserver.com/v1/create-qr-code/?size=400x400&data={urllib.parse.quote(url)}"
        req = urllib.request.Request(qr_api, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req) as resp, open(out_path, "wb") as f:
            f.write(resp.read())
        return out_path

Tainted flow: 'req' from os.environ.get (line 351, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 357)May include surrounding context.

python
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
                    method="POST",
                )
                with urllib.request.urlopen(req) as resp:
                    results.append(json.loads(resp.read()))
            else:
                results.append(http_json(

Tainted flow: 'req' from os.environ.get (line 351, credential/environment) → urllib.request.urlopen (network output)

Critical
Category
Data Flow
Confidence
90% confidence
Finding

Credentials or environment variables flow to a network sink. This is a high-confidence indicator of credential exfiltration.

Content

Scanner excerpt · scripts/formora.py (reported line 439)May include surrounding context.

python
headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
                    method="POST",
                )
                with urllib.request.urlopen(req) as resp:
                    results.append(json.loads(resp.read()))
            else:
                results.append(http_json(

Tp4

High
Category
MCP Tool Poisoning
Confidence
95% confidence
Finding

The skill description omits that it can retrieve and export survey responses, which are likely to contain user-submitted data and potentially sensitive information. This hidden data-access behavior can mislead operators and users about the privacy and exfiltration risk of enabling the skill, especially because exports write data to local files and responses may be transmitted or further processed.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

High
Category
Not specified by scanner
Confidence
98% confidence
Finding

The natural-language heuristic treats any CJK, Japanese, or Korean character as a signal to return zh-CN. This can incorrectly force Chinese for Japanese- or Korean-language users, which is a locale policy violation because no user choice or opt-in is offered.

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 · scripts/formora.py (reported line 464)May include surrounding context.

python
def google_ads_get_token():
    """Exchange refresh token for a short-lived access token."""
    data = urllib.parse.urlencode({
        "client_id": os.environ["FORMORA_GOOGLE_ADS_CLIENT_ID"],
        "client_secret": os.environ["FORMORA_GOOGLE_ADS_CLIENT_SECRET"],

Undeclared Tool Scope

Medium
Category
MCP Least Privilege
Confidence
95% confidence
Finding

The skill declares no explicit tool scope or permission boundaries even though it clearly requires shell execution, network access, filesystem reads/writes, and environment-variable secrets. This creates an over-privileged integration surface where a host agent may grant broader capabilities than necessary, increasing the chance of unintended command execution, secret exposure, or external actions.

Content

No source excerpt is available for this finding.

Tainted flow: 'ADS_STATE_PATH' from os.environ.get (line 50, credential/environment) → pathlib.Path.write_text (file write)

Medium
Category
Data Flow
Confidence
88% confidence
Finding

ADS_STATE_PATH is fully controlled by an environment variable and is used for arbitrary file writes. In environments where untrusted users can influence environment variables or launch configuration, this can overwrite unintended files, corrupt application state, or write sensitive campaign metadata into unsafe locations.

Content

Scanner excerpt · scripts/formora.py (reported line 138)May include surrounding context.

python
def save_ads_state(state):
    ADS_STATE_PATH.parent.mkdir(parents=True, exist_ok=True)
    ADS_STATE_PATH.write_text(json.dumps(state, ensure_ascii=False, indent=2), encoding="utf-8")


def get_spend_summary(state, now=None):

External Transmission

Medium
Category
Data Exfiltration
Confidence
92% confidence
Finding

The QR-code fallback sends the survey URL to a third-party service outside Formora's primary infrastructure. Because survey URLs can identify unpublished or private campaign assets, this creates an unnecessary external disclosure path that is more sensitive in a survey/response-management skill.

Content

Scanner excerpt · scripts/formora.py (reported line 315)May include surrounding context.

python
img.save(out_path)
        return out_path
    except Exception:
        qr_api = f"https://api.qrserver.com/v1/create-qr-code/?size=400x400&data={urllib.parse.quote(url)}"
        req = urllib.request.Request(qr_api, headers={"User-Agent": "Mozilla/5.0"})
        with urllib.request.urlopen(req) as resp, open(out_path, "wb") as f:
            f.write(resp.read())

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/formora.py (reported line 352)May include surrounding context.

python
f"--{boundary}--\r\n".encode(),
                ])
                req = urllib.request.Request(
                    f"https://api.telegram.org/bot{token}/sendPhoto",
                    data=b"".join(parts),
                    headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
                    method="POST",

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/formora.py (reported line 362)May include surrounding context.

python
f"--{boundary}--\r\n".encode(),
                ])
                req = urllib.request.Request(
                    f"https://api.telegram.org/bot{token}/sendPhoto",
                    data=b"".join(parts),
                    headers={"Content-Type": f"multipart/form-data; boundary={boundary}"},
                    method="POST",

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/formora.py (reported line 381)May include surrounding context.

python
msg = f"Subject: {subject}\nTo: {', '.join(recipients)}\n\n{body}\n"
    try:
        proc = subprocess.run(
            ["himalaya", "template", "send"],
            input=msg.encode("utf-8"),
            stdout=subprocess.PIPE,

Tainted flow: 'msg' from os.environ.get (line 379, credential/environment) → subprocess.run (code execution)

Medium
Category
Data Flow
Confidence
65% confidence
Finding

Data from a source is assigned to a variable that is later passed to a sink, creating a variable-mediated taint flow.

Content

Scanner excerpt · scripts/formora.py (reported line 381)May include surrounding context.

python
msg = f"Subject: {subject}\nTo: {', '.join(recipients)}\n\n{body}\n"
    try:
        proc = subprocess.run(
            ["himalaya", "template", "send"],
            input=msg.encode("utf-8"),
            stdout=subprocess.PIPE,

External Transmission

Medium
Category
Data Exfiltration
Confidence
60% confidence
Finding

Data is being sent to an external URL. This could be legitimate telemetry or data exfiltration. Manual review is recommended.

Content

Scanner excerpt · scripts/formora.py (reported line 426)May include surrounding context.

python
ats = os.environ.get("FORMORA_X_ACCESS_TOKEN_SECRET")
    if not all([ck, cs, at, ats]):
        return {"sent": False, "reason": "FORMORA_X_* credentials are not fully configured"}
    url = "https://api.twitter.com/2/tweets"
    auth = _oauth1_header("POST", url, ck, cs, at, ats)
    try:
        req = urllib.request.Request(

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

Exported survey responses may contain sensitive respondent data, and the command writes them directly to disk without warning, redaction, or permission controls. In shared workstations, CI runners, or agent environments, this increases the chance of unintended local disclosure or persistence of personal data.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.