Back to skill

Security audit

Newsletter Automation

Security checks for vulnerabilities and agentic risk

Overview

This is a coherent newsletter automation skill, but a broadcast-authentication flaw could let a misconfigured public endpoint send email to every confirmed subscriber.

Review before installing. Do not activate the broadcast workflow until NEWSLETTER_SECRET is set to a strong random value and the public fallback is removed; put the webhooks behind authentication or rate limiting, sanitize subscriber-supplied fields before email rendering, add signup abuse controls, and fix the drip state so welcome emails are sent only once. Also publish a privacy/consent notice and restrict access to the subscriber Sheet.

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

T09 · Insecure Skill Coding Practices

Error
Location
workflows/03-broadcast-sender.json:23
Finding

Broadcast Authentication Bypass Through a Public Fallback Secret

Content
View full analysis
Attacker-controlled HTML" ...[truncated 733 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
workflows/01-subscriber-signup.json:23
Finding

Confirmation Tokens Generated With a Non-Cryptographic PRNG

Content
View full analysis
Math.random().toString(36).charAt(2) ).join(''); return [{ json: { email, name: name || 'Subscriber', token, status: 'pending', source: body.source || 'website', subscribed_at: new Date().toISOString(), confirmed: false, valid: true } }]; ``` ### Technical Analysis `Math.random()` is not a cryptographically secure pseudorandom number generator. Its output is not designed to resist state inference or prediction by an adversary. Calling it once per character does not convert it into a secure token source. The generated token is intended to authorize a subscriber-confirmation operation. It is stored in Google Sheets and placed in a URL sent through email. Security-sensitive tokens should have cryptographically strong entropy, a limited validity period, one-time-use enforcement, and secure storage. The supplied project does not include the confirmation endpoint claimed by its documentation. Consequently, direct exploitation cannot be completed using only the included files. The weakness becomes exploitable when this token is accepted by an external or subsequently added confirmation handler. ### Attack Path 1. An attacker triggers signups and collects tokens sent to email addresses under the attacker's control. 2. The attacker uses observed output and runtime-specific PRNG behavior to attempt prediction or reduction of the search space for another generated token. 3. The atta ...[truncated 903 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
workflows/01-subscriber-signup.json:5
Finding

Public Signup Webhook Can Be Used for Unsolicited Email and Resource Abuse

Content
View full analysis
Welcome, {{ $json.name }}!

Thanks for subscribing to our newsletter.

Please confirm your subscription by clicking the link below:

Confirm Subscription

If you didn't subscribe, you can safely ignore this email.

", "options": {} } } ``` ### Technical Analysis A newsletter signup endpoint must normally be public, but the workflow provides no compensating abuse controls. There is no CAPTCHA, signed-form verification, IP or address-based rate limit, replay prevention, cooldown, request-size limit, or robust email schema validation. The only validation is whether the supplied value contains `@`. Every accepted request can write a record to Google Sheets and trigger an SMTP email. Because storage uses `appendOrUpdate` with the email address as the matching field, repeated requests can also replace pending subscriber data and confirmation tokens. The network operation itself is necessary for double opt-in, but allowing unlimited anonymous callers to invoke it excee ...[truncated 1010 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
workflows/01-subscriber-signup.json:23
Finding

Subscriber-Controlled Values Are Embedded Into HTML Email Without Escaping

Content
View full analysis
Welcome, {{ $json.name }}!

Thanks for subscribing to our newsletter.

``` The welcome workflow uses the stored name directly in HTML templates: ```js welcome: { subject: `Welcome aboard, ${item.name}!`, body: `

Welcome to the newsletter!

Hi ${item.name},

Thanks for confirming your subscription. Here's what to expect:

  • Weekly insights and tips
  • Exclusive resources and guides
  • Early access to new content

Stay tuned — your first tips are coming in a few days.

` }, tips: { subject: `3 tips to get you started, ${item.name}`, body: `

Quick Start Tips

Hi ${item.name},

Here are 3 tips to get the most from your subscription:

  1. Reply to any email — we read every response
  2. Star our emails — so they never go to spam
  3. Share with a friend — help us grow the community

More great content coming your way soon!

` } ``` Stored names and broadcast content are embedded directly into broadcast HTML: ```html

Hi {{ $json.name }},

{{ $json.content ...[truncated 2812 chars]
Remediation
View remediation

T09 · Insecure Skill Coding Practices

Warning
Location
workflows/02-welcome-sequence.json:44
Finding

Day-Zero Drip State Causes Repeated Welcome Emails

Content
View full analysis
i.json); const toSend = []; for (const sub of subscribers) { if (sub.status !== 'confirmed' && sub.confirmed !== 'true' && sub.confirmed !== true) continue; const subscribedAt = new Date(sub.subscribed_at); const daysSince = Math.floor((now - subscribedAt) / (1000 * 60 * 60 * 24)); const lastDrip = parseInt(sub.last_drip_day || '0', 10); // Drip schedule: Day 0 (welcome), Day 3 (tips), Day 7 (resources) let dripDay = null; let dripType = null; if (lastDrip < 0.5 && daysSince >= 0) { dripDay = 0; dripType = 'welcome'; } else if (lastDrip < 3 && daysSince >= 3) { dripDay = 3; dripType = 'tips'; } else if (lastDrip < 7 && daysSince >= 7) { dripDay = 7; dripType = 'resources'; } if (dripDay !== null) { toSend.push({ email: sub.email, name: sub.name || 'Subscriber', drip_day: dripDay, drip_type: dripType }); } } ``` After sending the Day-zero message, the same zero value is written back: ```json "value": { "email": "={{ $json.email }}", "last_drip_day": "={{ $json.drip_day }}", "last_drip_at": "={{ new Date().toISOString() }}" } ``` ### Technical Analysis The expression: ```js parseInt(sub.last_drip_day || '0', 10) ``` maps both an unset initial state and a stored Day-zero state to `0`. The Day-zero send condition accepts every value below `0.5`. After the welcome message is sent, `last_drip_day` is set to `0`, which still satisfies the same condition on the next scheduled run. Because the workflow runs every six hours, a confirmed subscriber can repeatedly receive the welcome message instead of advancing toward the Day-three state. This is an idempotency defect in an exte ...[truncated 984 chars]
Remediation
View remediation
Vulnerability Patterns
  • Data ExfiltrationExternal Transmission, Env Variable Harvesting, File System Enumeration
  • Excessive AgencyUnrestricted Tool Access, Autonomous Decision Making, Scope Creep
  • Trigger AbuseOverly Broad Trigger, Shadow Command Trigger, Keyword Baiting Trigger
  • Prompt InjectionInstruction Override, Hidden Instructions, Exfiltration Commands
  • Privilege EscalationExcessive Permissions, Sudo/Root Execution, Credential Access
Findings (11)

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

The skill describes collecting subscriber emails, names, sources, confirmation status, tokens, and analytics in Google Sheets, but it does not prominently warn users that personal data will be stored and used for automated campaigns and reporting. This creates a privacy and consent risk because operators may deploy the workflow without adequately informing subscribers or configuring appropriate data handling safeguards.

Content

No source excerpt is available for this finding.

Autonomous Decision Making

Medium
Category
Excessive Agency
Confidence
80% confidence
Finding

Skill enables autonomous high-impact decisions without human-in-the-loop verification. Critical operations (destructive commands, financial transactions, data deletion) should require explicit user confirmation.

Content

Scanner excerpt · SKILL.md (reported line 30)May include surrounding context.

md
## What It Does

1. **Double Opt-In Signup** — Webhook receives signups, validates email, sends confirmation link, stores in Sheets
2. **Welcome Drip Sequence** — Automatically sends Day 0 (welcome), Day 3 (tips), Day 7 (resources) emails
3. **Broadcast Sender** — API-triggered broadcast to all confirmed subscribers with unsubscribe links
4. **Daily Analytics** — Subscriber counts, growth metrics, confirmation rates, top sources

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
89% confidence
Finding

The broadcast feature is presented as a normal capability, but the documentation does not clearly warn that invoking it will send mass email to all confirmed subscribers. Without an explicit caution, an operator could trigger large-scale messaging unintentionally or without appropriate review, creating spam, reputational, and compliance risks.

Content

No source excerpt is available for this finding.

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

4. Test Signup

bash
curl -X POST https://your-n8n.com/webhook/newsletter/signup \
  -H "Content-Type: application/json" \
  -d '{"email": "test@example.com", "name": "Test User", "source": "api-test"}'

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
85% confidence
Finding

This is a JSON manifest/workflow file, so vague-trigger checks apply. The path "newsletter/signup" indicates activation on a generic signup POST endpoint, but the file provides no explicit constraints, negative examples, or scoping details about who may invoke it or under what contexts, which can make activation expectations ambiguous.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
92% confidence
Finding

This workflow automatically sends email to subscriber addresses on a schedule without any built-in user-facing disclosure, approval checkpoint, or operational safeguard in the workflow itself. In a newsletter skill this behavior is expected, but if the subscriber sheet is populated incorrectly or abused, the system can send unsolicited messages at scale, creating spam, compliance, and reputation risk.

Content

No source excerpt is available for this finding.

Vague Triggers

Medium
Category
Not specified by scanner
Confidence
84% confidence
Finding

This is a manifest/config file, so vague-trigger checks apply. The activation path "newsletter/broadcast" identifies a high-level action but provides no explicit constraints, negative examples, or narrower invocation context, which can make the skill easier to invoke unintentionally in systems that route by descriptive trigger names.

Content

No source excerpt is available for this finding.

Missing User Warnings

Medium
Category
Not specified by scanner
Confidence
91% confidence
Finding

This workflow sends emails to subscriber addresses and includes personalized content, which is a safety-relevant network operation involving user data. In this file there is no confirmation step, user-visible logging, or inline warning/comment disclosing that subscriber email addresses and message content will be transmitted via SMTP.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
97% confidence
Finding

The workflow hard-codes the subject and HTML message in English. Under the natural-language policy check, forcing a specific language without offering user choice or documenting a justified locale constraint is a policy violation.

Content

No source excerpt is available for this finding.

Vague Triggers

Low
Category
Not specified by scanner
Confidence
84% confidence
Finding

This is a manifest-style JSON file, so vague-trigger checks apply. The schedule is defined only as a 24-hour interval, but the file does not specify when it should run, under what conditions it should be disabled, or any limiting context, which can make invocation scope overly broad.

Content

No source excerpt is available for this finding.

Natural-Language Policy Violations

Low
Category
Not specified by scanner
Confidence
67% confidence
Finding

SQP-3 only covers natural-language language or locale policy violations. The content here does not show a language/locale restriction, so this is only marginally applicable; however, there is no actual locale policy issue visible in the file.

Content

No source excerpt is available for this finding.

Static analysis

No suspicious patterns detected.