Skip to content
Report library
Purpose / Other

Extension Email Marketing Skill Security Audit

What the author says it does (original text)

Send personalised marketing emails to subscribers with an unsubscribe link.

Independent security check

Do not install or run it yet

Files checked
1
Risks found
3
Could it run dangerous commands?Looks for programs run straight after downloading, remote control of your computer, and hidden commands.No risks found
Could it expose your files or keys?Looks for uploads of files containing passwords or keys, and keys written directly in the code.Risks found: 2
High risk

Any caller can reserve someone else’s email and associate it with their own identity

Source references: 3
What we found

The registration endpoint accepts any caller-supplied email, creates the caller-to-email association, reserves the uniqueness record, and adds it to a marketing topic before proving ownership. It sends verification only afterward.

Why this matters

An attacker can register a victim’s address, preventing its real owner from registering because it is already taken. If the owner clicks the received verification link, the address may also become verified for the profile created under the attacker’s principal and eligible for marketing sent through that profile.

Before proving ownership, registration stores any caller-supplied email in that caller's profile and in a global uniqueness set. A later registrant using it receives “Email already taken.” If this example is adopted, a caller could therefore reserve someone else's address and block its real owner; sending verification afterward does not undo that association. Users can ask that addresses not be permanently reserved until verification succeeds, with strict limits and expiry for unverified records.

SKILL.md:186In the instructionsOpen original file
  public shared ({ caller }) func registerUser(name : Text, email : Text) : async () {    // Check if the user already exists    if (userProfiles.containsKey(caller)) {      Runtime.trap("User already registered");    };    // Check if the email is already used    if (emails.contains(email)) {      Runtime.trap("Email already taken");    };    // Add a user record    userProfiles.add(      caller,      {        name;        email;      },    );    emails.add(email);    // Subscribe the user to the Newsletter topic by default
Show 2 other places
SKILL.md:204In the instructionsOpen original file
    emails.add(email);    // Subscribe the user to the Newsletter topic by default    let topicId = EmailSubscribers.getTopicId(emailSubscribers, newsletterTopic)      ?? Runtime.trap("Newsletter topic not found");    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email    let result = await EmailClient.sendVerificationEmail(      "no-reply",      [email],      "Welcome to Our Service",      "Hello " # name # ",<br><br>Thank you for registering with our service.<br><br>Please <a href=\"{{VERIFICATION_URL}}\">click here</a> to verify your email address.<br><br>By clicking on the verification link you also agree to sign-up to the monthly Newsletter which you can unsubscribe from at any time.<br><br>Best regards,<br>The Team",    );
SKILL.md:208In the instructionsOpen original file
    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email    let result = await EmailClient.sendVerificationEmail(      "no-reply",      [email],      "Welcome to Our Service",      "Hello " # name # ",<br><br>Thank you for registering with our service.<br><br>Please <a href=\"{{VERIFICATION_URL}}\">click here</a> to verify your email address.<br><br>By clicking on the verification link you also agree to sign-up to the monthly Newsletter which you can unsubscribe from at any time.<br><br>Best regards,<br>The Team",    );
Medium risk

A verification-email failure may leave partial registration state that cannot be retried

Source references: 2
What we found

The example stores the user, reserves the email, and adds the subscription before crossing an asynchronous call to send verification. Its error branch raises an error without removing that earlier state.

Why this matters

If pre-await state has committed, a temporary email-service failure leaves an unverified but reserved email and user record. Retries by the same caller or address are rejected as already registered or taken, with no shown self-service recovery.

The example writes the profile, unique-email reservation, and subscription before awaiting the email service. On an email error it only traps; the shown source has no cleanup, resend endpoint, or expiry. In deployments where those earlier writes are not automatically rolled back, failure can leave an unverified but reserved address, while this endpoint rejects both the same caller and address on retry. Users can ask the author to document async rollback semantics and provide idempotent retry or cleanup.

SKILL.md:195In the instructionsOpen original file
    };    // Add a user record    userProfiles.add(      caller,      {        name;        email;      },    );    emails.add(email);    // Subscribe the user to the Newsletter topic by default    let topicId = EmailSubscribers.getTopicId(emailSubscribers, newsletterTopic)      ?? Runtime.trap("Newsletter topic not found");    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email
Show 1 other places
SKILL.md:208In the instructionsOpen original file
    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email    let result = await EmailClient.sendVerificationEmail(      "no-reply",      [email],      "Welcome to Our Service",      "Hello " # name # ",<br><br>Thank you for registering with our service.<br><br>Please <a href=\"{{VERIFICATION_URL}}\">click here</a> to verify your email address.<br><br>By clicking on the verification link you also agree to sign-up to the monthly Newsletter which you can unsubscribe from at any time.<br><br>Best regards,<br>The Team",    );    switch (result) {      case (#ok) {};      case (#err(error)) {        Runtime.trap("Failed to send verification email: " # error);      };    };  };
Could it delete files or keep running?Looks for broad file deletion, disk overwrites, and programs set to start automatically.No risks found
Could it bypass safety checks?Looks for skipped website security checks, excessive file access, or actions that skip your approval.No risks found
Could it mislead the AI or hide text?Checks the skill instructions for requests to ignore you, influence the report, or hide text in invisible characters.No risks found
Could it change links or payment recipients without asking?Looks for forced referral or payment changes combined with instructions to hide the change.Risks found: 1
Medium risk

Registration opts users into marketing by default and bundles verification with consent

Source references: 3
What we found

The example immediately adds the address to the Newsletter topic during registration, then states that clicking the verification link also means agreeing to monthly marketing. There is no separate, affirmative marketing choice.

Why this matters

A person who only intends to verify an account email may be recorded as consenting to marketing in the same action, resulting in advertising that does not reflect their actual choice and creating consent-evidence or compliance disputes for the operator.

The example adds the address to Newsletter during registration without a separate marketing choice, then makes one click both verify the address and signify newsletter agreement. The unsubscribe notice and verified-recipient rule reduce unwanted delivery but do not provide separate, explicit marketing consent. If copied into an app, users may be enrolled merely by completing email verification. Users can ask for a separate, unchecked opt-in and records of when and for which topic consent was given.

SKILL.md:204In the instructionsOpen original file
    emails.add(email);    // Subscribe the user to the Newsletter topic by default    let topicId = EmailSubscribers.getTopicId(emailSubscribers, newsletterTopic)      ?? Runtime.trap("Newsletter topic not found");    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email    let result = await EmailClient.sendVerificationEmail(      "no-reply",      [email],      "Welcome to Our Service",      "Hello " # name # ",<br><br>Thank you for registering with our service.<br><br>Please <a href=\"{{VERIFICATION_URL}}\">click here</a> to verify your email address.<br><br>By clicking on the verification link you also agree to sign-up to the monthly Newsletter which you can unsubscribe from at any time.<br><br>Best regards,<br>The Team",    );
Show 2 other places
SKILL.md:208In the instructionsOpen original file
    ignore EmailSubscribers.add(emailSubscribers, topicId, email);    // Send a verification email    let result = await EmailClient.sendVerificationEmail(      "no-reply",      [email],      "Welcome to Our Service",      "Hello " # name # ",<br><br>Thank you for registering with our service.<br><br>Please <a href=\"{{VERIFICATION_URL}}\">click here</a> to verify your email address.<br><br>By clicking on the verification link you also agree to sign-up to the monthly Newsletter which you can unsubscribe from at any time.<br><br>Best regards,<br>The Team",    );    switch (result) {
SKILL.md:24In the instructionsOpen original file
- Users MUST have verified their email address AND MUST be subscribed to a marketing topic before they can receive marketing emails on that topic- Marketing emails MUST contain an unsubscribe link which will unsubscribe the user from the given topic- This component depends on the [extension-email-verification](../extension-email-verification/SKILL.md) for verifying email addresses, be sure to check that too.

Inside this skill

1 instruction sections

The Skill’s example stores subscription state, email-verification state, and user profiles separately, and uses a prefabricated module for topic subscriptions. Marketing recipients are selected only from verified subscribers to the chosen topic.

View source
SKILL.md:261In the instructionsOpen original file
    };    // Get the array of subscriber emails that have been verified    let recipientEmails = EmailSubscribers.verified(emailSubscribers, verifiedEmails, topicId)      ?? Runtime.trap("No verified subscribers found for newsletter topic");    if (recipientEmails.size() == 0) {      Runtime.trap("No verified subscribers found for newsletter topic");    };
SKILL.md:282In the instructionsOpen original file
    );    let result = await EmailClient.sendMarketingEmail(      topicId,      "no-reply",      recipients,      subject,      finalHtmlBody,    );    switch (result) {

Sending marketing email and listing subscribers require administrator permission, while subscribe and unsubscribe operations use the email in the caller’s own profile.

View source
SKILL.md:247In the instructionsOpen original file
  public shared ({ caller }) func subscribeToTopic(topicId : Nat) : async () {    let userProfile = getUserInternal(caller);    ignore EmailSubscribers.add(emailSubscribers, topicId, userProfile.email);  };  public shared ({ caller }) func unsubscribeFromTopic(topicId : Nat) : async () {    let userProfile = getUserInternal(caller);    EmailSubscribers.remove(emailSubscribers, topicId, userProfile.email);  };
SKILL.md:257In the instructionsOpen original file
  public shared ({ caller }) func sendMarketingEmail(topicId : Nat, subject : Text, htmlBody : Text) : async () {    if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {      Runtime.trap("Unauthorized: Only admins can send the newsletter");    };    // Get the array of subscriber emails that have been verified
SKILL.md:301In the instructionsOpen original file
  // Admin function to list topic subscribers and whether the email is verified or not  public query ({ caller }) func listSubscribers(topicId : Nat) : async [(Text, Bool)] {    if (not (AccessControl.hasPermission(accessControlState, caller, #admin))) {      Runtime.trap("Unauthorized: Only admins can list topic subscribers");    };    EmailSubscribers.list(emailSubscribers, verifiedEmails, topicId).get([]);  };

The example appends an unsubscribe link when its placeholder is absent and includes a prefabricated unsubscribe-link handler. The supplied source does not contain that module’s implementation, so its token validation and access controls cannot be independently verified.

View source
SKILL.md:90In the instructionsOpen original file
Use the prefabricated module `caffeineai-email-marketing/unsubscribeMixin.mo` which cannot be modified.The MixinEmailUnsubscribe module handles calls to the unsubscribe link to unsubscribe an email address from a topic.
SKILL.md:267In the instructionsOpen original file
    };    // Ensure the email body contains the unsubscribe link placeholder    let finalHtmlBody = if (htmlBody.contains(#text "{{UNSUBSCRIBE_URL}}")) {      htmlBody;    } else {      htmlBody # "<br><br>To unsubscribe <a href=\"{{UNSUBSCRIBE_URL}}\">click here</a>";    };    // For each recipient specify the NAME substitution to personalise the email
Start here · InstructionsSKILL.md
extension-email-marketing
Lines connect the instruction file to its sections, not an observed execution order. Select a section to read the source.
Files and check records1 files

Coverage and gaps

Content covered in each file

These are the source ranges included in this check, not a guarantee that every issue has been resolved.

  • SKILL.mdFull text included

This report is for the version above. We read the available code and instructions without running the skill or checking extra packages it installs. This is not a promise of safety: a different version or setup may behave differently.

  • SKILL.mdInstructions

Operations mentioned in code and instructions

Connect to websites
SKILL.md:14In the instructionsOpen original file
# Email — MarketingMarketing email extension for [Caffeine AI](https://caffeine.ai?utm_source=caffeine-skill&utm_medium=referral).
Lines read
357
File checksum (to compare versions)
f88f49f3f0556b43df99da027cd61482db4316f08f613834178e1ff85beeeeae