agentsclimarketplace

Temp mail pentest

Skill prof-ramos/skills/skills/security/temp-mail-pentest

Creates and manages temporary email addresses via the Guerrilla Mail API for pentesting and bugbounty workflows. Use when you need to register on a site requiring email confirmation, receive password reset links, test for data leakage in outgoing emails, validate that an email-sending endpoint actually triggers, or test rate limits/spam handling by receiving multiple emails. Saves inbox state to a per-target JSON file for evidence and session reuse. Supports uv runtime.From its SKILL.md

Install
npx -y skills add prof-ramos/skills --skill temp-mail-pentest

Assembled from the repository path, not quoted from the project. Check it against their README if it does not work.

One thing to look at

  • 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.

What its file declares

Copied from the file, not written here

The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.

SKILL.md

17.5 KB, ~4.3k tokens by cl100k_base, as published. Nobody here has run it

Temp Mail Pentest

Create disposable inboxes on demand. Use the Guerrilla Mail API to receive real emails during active pentesting — registration confirmations, password resets, data leakage verification, and rate-limit testing. Each target gets its own JSON state file so evidence stays organized per engagement.

Reference implementation: This skill ships with a full installable CLI tool alongside this file (tempmail/). Run it directly with uv run -p <skill-dir>/tempmail tempmail init --target example.com or install globally with uv tool install <skill-dir>/tempmail. The templates below teach an agent how to implement the same logic from scratch when more flexibility is needed.

When to Use

This skill activates when the user asks to:

  • Register on a target site that requires email confirmation
  • Receive and extract a password reset link or token
  • Test whether sensitive data leaks in outgoing transactional emails
  • Validate that an email-sending endpoint (contact form, invite, notification) actually fires
  • Test rate limits or spam handling by receiving multiple emails to the same inbox

Do not use this skill for general email automation, email marketing, non-security email verification, or any task unrelated to authorized security testing.

API Overview

Base URL: http://api.guerrillamail.com/ajax.php

All requests require these parameters:

ParamValue
fFunction name (see API Reference)
ipPlaceholder: "0.0.0.0"
agentPlaceholder: "Mozilla_pentest_agent"

Session: The server sets a PHPSESSID cookie on first call. Capture it from the Set-Cookie response header and send it back as Cookie: PHPSESSID=<sid> on every subsequent call. The SID can change at any response — always check the response headers for a new one.

Note: The API uses plain HTTP. Some pentest environments may block it. If the API is unreachable, inform the user and suggest alternatives (e.g., a different temp-mail provider).

JSON State File

Every target gets its own state file in the current working directory:

inbox-<normalized_target>.json

The target name is derived from the engagement — prompt the user for a label or derive it from the target domain (e.g., inbox-target.com.json).

Schema:

{
  "session": {
    "email": "[email protected]",
    "sid": "phpsessid_value",
    "created_at": 1717200000
  },
  "inbox": [
    {
      "id": 425,
      "from": "[email protected]",
      "subject": "Confirm your registration",
      "excerpt": "Click the link below to activate your account...",
      "ts": 1717200100,
      "read": false,
      "body": "<html>...</html>"
    }
  ]
}
  • If the file exists and the session is still valid (< 18 min inactivity, email not expired), reuse it.
  • Append new emails as they arrive. Do not overwrite existing messages.
  • Mark read: true after the agent fetches and processes the email body.
  • Save after every meaningful state change (new email fetched, session created).

Session Management

Use this template to initialize or resume a session:

import requests
import json
import time
import os
import re

API_BASE = "http://api.guerrillamail.com/ajax.php"

def get_or_create_session(state_file: str) -> dict:
    """Load existing state or create a fresh session."""
    if os.path.exists(state_file):
        with open(state_file) as f:
            state = json.load(f)
        # Session may have expired — attempt email check to validate
        return state

    params = {"f": "get_email_address", "ip": "0.0.0.0", "agent": "Mozilla_pentest_agent"}
    resp = requests.get(API_BASE, params=params)
    sid = extract_phpsessid(resp)
    data = resp.json()

    state = {
        "session": {
            "email": data["email_addr"],
            "sid": sid,
            "created_at": data["email_timestamp"]
        },
        "inbox": []
    }
    save_state(state_file, state)
    print(f"[+] Session created: {data['email_addr']} (SID: {sid[:8]}...)")
    return state

def extract_phpsessid(resp: requests.Response) -> str:
    """Extract PHPSESSID from Set-Cookie header."""
    if "Set-Cookie" not in resp.headers:
        return ""
    match = re.search(r"PHPSESSID=([a-zA-Z0-9]+);", resp.headers["Set-Cookie"])
    return match.group(1) if match else ""

def save_state(path: str, state: dict):
    with open(path, "w") as f:
        json.dump(state, f, indent=2)

Instructions to the agent:

  • Always capture PHPSESSID from the Set-Cookie header on every response — it can change at any time
  • The Set-Cookie header is in the HTTP response headers, not the JSON body
  • Pass the SID via the Cookie header on every request:
    cookies = {"PHPSESSID": state["session"]["sid"]}
    requests.get(API_BASE, params=params, cookies=cookies)
    
  • If the SID changes, update state["session"]["sid"] immediately and save
  • If a request returns a 200 but the response is empty or unexpected, the session likely expired — create a new one and inform the user

Polling Loop

Use this template to wait for a specific email. Set timeout based on the use case (default: 60s).

def poll_for_email(
    state: dict,
    state_file: str,
    timeout: int = 60,
    match_from: str = "",
    match_subject: str = "",
    match_body: str = "",
    interval: int = 5
) -> dict | None:
    """Poll check_email until a matching email arrives or timeout."""
    import urllib.parse

    params = {
        "f": "check_email",
        "ip": "0.0.0.0",
        "agent": "Mozilla_pentest_agent",
        "seq": "0"
    }
    cookies = {"PHPSESSID": state["session"]["sid"]}

    deadline = time.time() + timeout
    while time.time() < deadline:
        resp = requests.get(API_BASE, params=params, cookies=cookies)

        # Update SID if server changed it
        new_sid = extract_phpsessid(resp)
        if new_sid and new_sid != state["session"]["sid"]:
            state["session"]["sid"] = new_sid
            cookies["PHPSESSID"] = new_sid

        data = resp.json()
        remaining = int(deadline - time.time())
        print(f"  [~] {len(data.get('list', []))} emails, {remaining}s remaining")

        for mail in data.get("list", []):
            if mail["mail_id"] in [m["id"] for m in state["inbox"]]:
                continue  # already processed
            if match_from and match_from.lower() not in mail.get("mail_from", "").lower():
                continue
            if match_subject and match_subject.lower() not in mail.get("mail_subject", "").lower():
                continue

            # Matched — fetch full body
            mail["read"] = False
            state["inbox"].append({
                "id": mail["mail_id"],
                "from": mail["mail_from"],
                "subject": mail["mail_subject"],
                "excerpt": mail["mail_excerpt"],
                "ts": mail["mail_timestamp"],
                "read": False,
                "body": None  # fetched on demand
            })
            save_state(state_file, state)
            return mail

        time.sleep(interval)

    print("  [!] Timeout reached — email did not arrive")
    return None

Instructions to the agent:

  • Start polling after the action that should trigger the email (e.g., after submitting a registration form)
  • Log each poll attempt so the user sees progress
  • Use match_from (sender domain), match_subject (keyword), or match_body (keyword in excerpt) — at least one filter
  • If the target sends multiple emails (e.g., welcome + confirmation), poll in a loop calling fetch_email for each
  • The mail_excerpt field is HTML-entity-escaped — use html.unescape() if needed

Fetching Email Body

Once a matching email is found in the inbox list, fetch its full body:

def fetch_email_body(state: dict, mail_id: int) -> str | None:
    """Fetch the full HTML body of an email by ID."""
    params = {
        "f": "fetch_email",
        "ip": "0.0.0.0",
        "agent": "Mozilla_pentest_agent",
        "email_id": mail_id
    }
    cookies = {"PHPSESSID": state["session"]["sid"]}
    resp = requests.get(API_BASE, params=params, cookies=cookies)
    data = resp.json()

    # The body is in data["mail_body"] or similar — inspect the response
    if isinstance(data, list):
        data = data[0]
    body = data.get("mail_body", data.get("body", ""))
    return body

Instructions to the agent:

  • Always fetch the body after matching an email in the poll loop
  • Save the body to the inbox entry and mark read: true
  • The GM API returns the email body in mail_body field of the fetch response
  • The body is already HTML-filtered by Guerrilla Mail (scripts, iframes, applets removed)

Displaying Images (res.php replacement)

Guerrilla Mail replaces image sources with links to their proxy:

/res.php?r=1&n=img&q=<original_url_encoded>

To view real images, replace these references:

import urllib.parse
import html

def resolve_gm_images(html_body: str) -> str:
    """Replace Guerrilla Mail image proxies with original URLs."""
    # Match within double quotes
    html_body = re.sub(
        r'"/res\.php\?r=1&amp;n=[a-z]+&amp;q=([^"^&]+)"',
        lambda m: '"' + urllib.parse.unquote(m.group(1)) + '"',
        html_body
    )
    # Match within HTML-encoded quotes
    html_body = re.sub(
        r'&quot;/res\.php\?r=1&amp;n=[a-z]+&amp;q=([^"]+)&quot;',
        lambda m: '&quot;' + urllib.parse.unquote(m.group(1)) + '&quot;',
        html_body
    )
    return html_body

Only apply this replacement when the user asks to see images. By default, save the body with proxy URLs intact.

Use Cases

Registration Confirmation

Scenario: User wants to register on a target site and confirm the account via email.

Workflow:

def workflow_registration(target_url: str, state_file: str):
    # 1. Create session
    state = get_or_create_session(state_file)
    print(f"[+] Using email: {state['session']['email']}")
    print(f"[+] Register at: {target_url}")
    print("[!] PAUSE — complete the registration form manually, then press Enter")
    input()

    # 2. Poll for confirmation email
    mail = poll_for_email(
        state, state_file,
        match_subject="confirm",
        match_from=extract_domain(target_url),
        timeout=60
    )
    if not mail:
        print("[-] Confirmation email not received")
        return

    # 3. Fetch body and extract link
    body = fetch_email_body(state, mail["mail_id"])
    confirm_link = extract_link_from_body(body)
    if confirm_link:
        print(f"[+] Confirmation link: {confirm_link}")
    else:
        print("[!] No link found in email body — check manually")

def extract_domain(url: str) -> str:
    from urllib.parse import urlparse
    return urlparse(url).netloc

def extract_link_from_body(html_body: str) -> str | None:
    """Find first https?:// link in HTML body."""
    match = re.search(r'https?://[^\s"\'<>]+', html_body)
    return match.group(0) if match else None

Password Reset

Scenario: User wants to trigger and capture a password reset link.

Workflow:

Same base as registration, but:

  • Match subject on "reset" or "password"
  • Extract the reset token/link from the body
  • Optionally replace the reset link domain to test SSRF or host-header injection if applicable
def workflow_password_reset(target_url: str, state_file: str):
    state = get_or_create_session(state_file)
    print(f"[+] Using email: {state['session']['email']}")
    print(f"[+] Trigger password reset at: {target_url}")
    print("[!] PAUSE — submit the password reset form, then press Enter")
    input()

    mail = poll_for_email(
        state, state_file,
        match_subject="reset",
        timeout=60
    )
    if not mail:
        print("[-] Reset email not received")
        return

    body = fetch_email_body(state, mail["mail_id"])
    reset_link = extract_link_from_body(body)
    if reset_link:
        print(f"[+] Reset link: {reset_link}")
    return body  # return for further analysis

Data Leakage Detection

Scenario: User wants to test what sensitive data the target leaks in transactional emails.

Workflow:

  1. Create session and register for an action that triggers an email
  2. Fetch the full email body
  3. Scan for sensitive patterns:
LEAK_PATTERNS = {
    "email": r"[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}",
    "phone": r"\+?[\d\s\-\(\)]{7,20}",
    "cpf": r"\d{3}\.?\d{3}\.?\d{3}-?\d{2}",
    "credit_card": r"\d{4}[-\s]?\d{4}[-\s]?\d{4}[-\s]?\d{4}",
    "ip_internal": r"(10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})",
    "api_key": r"(?i)(api[_-]?key|apikey|secret|token)[:=]\s*['\"]?[a-zA-Z0-9_\-]{16,}"
}

def scan_for_leaks(body: str) -> dict:
    findings = {}
    for name, pattern in LEAK_PATTERNS.items():
        matches = re.findall(pattern, body)
        if matches:
            findings[name] = list(set(matches))
    return findings

Document any findings in the inbox state file notes and report to the user.

Endpoint Validation

Scenario: User wants to confirm that an email-sending endpoint actually fires (e.g., contact form, invite system, notification mechanism).

Workflow:

def workflow_validate_endpoint(target_url: str, state_file: str):
    state = get_or_create_session(state_file)
    print(f"[+] Target email: {state['session']['email']}")
    print(f"[+] Submit at:    {target_url}")
    print("[!] PAUSE — trigger the endpoint (submit the form), then press Enter")
    input()

    # Check for any incoming email (no specific match filter)
    mail = poll_for_email(state, state_file, timeout=90)
    if mail:
        print(f"[+] Email received from: {mail['mail_from']}")
        print(f"[+] Subject:             {mail['mail_subject']}")
        return True
    else:
        print("[-] No email received — endpoint may not be firing")
        return False

Rate Limit / Spam Testing

Scenario: User wants to test how many emails the target sends before rate-limiting or to collect a sample.

Workflow:

def workflow_rate_limit_test(target_url: str, state_file: str, max_emails: int = 20):
    state = get_or_create_session(state_file)
    print(f"[+] Using email: {state['session']['email']}")
    print(f"[+] Target:      {target_url}")
    print(f"[+] Collecting up to {max_emails} emails")
    print("[!] PAUSE — trigger the email-generating actions, then press Enter")
    input()

    collected = 0
    deadline = time.time() + 180  # 3 min max for batch

    while collected < max_emails and time.time() < deadline:
        mail = poll_for_email(state, state_file, timeout=30, interval=5)
        if not mail:
            break
        body = fetch_email_body(state, mail["mail_id"])
        collected += 1
        print(f"  [{collected}/{max_emails}] {mail['mail_from']} - {mail['mail_subject']}")

    print(f"\n[+] Collected {collected} emails total")
    print(f"[+] State saved in: {state_file}")

API Reference Summary

FunctionParamsReturnsNotes
get_email_addresslang (optional)email_addr, email_timestampInitializes session. Returns existing session if SID is valid.
set_email_useremail_user, langSame as get_email_addressSets a custom email username. Requires valid SID.
check_emailseqlist, count, email, tsReturns up to 20 newest messages.
get_email_listoffset, seqSame as check_emailPaginated email list. offset=0 → first 20.
fetch_emailemail_idFull email with bodyOnly emails owned by current session.
forget_meemail_addrtrueForgets current address, keeps session alive.
del_emailemail_ids[]Array of deleted IDsDelete one or more emails.
extend(none)expired, email_timestamp, affectedExtends address lifetime by 1h (max 2h).

Troubleshooting

Problem: requests not installed Solution: uv pip install requests or uv add requests

Problem: API returns empty or unexpected response Causes:

  • Session expired (SID invalid after ~18 min inactivity) → create new session
  • IP blocked (rate limiting) → wait 30s and retry
  • HTTP (not HTTPS) blocked by environment → inform user, suggest alternative

Problem: Set-Cookie header not found Cause: Python's requests by default does not expose Set-Cookie. Use resp.headers.get("Set-Cookie", "") or access resp.cookies directly:

sid = resp.cookies.get("PHPSESSID", "")

Problem: PHPSESSID changed mid-session Fix: Always extract SID from the last response before each request. Update state immediately.

Problem: Email never arrives (poll timeout) Check:

  • Is the user's trigger action correct? (form submitted, email sent?)
  • Is the match filter too narrow? Try matching on fewer fields
  • Is the target actually sending emails? Sometimes they queue or delay

What ships with it: 10 files

28.2 KB alongside SKILL.md, 8 of them executable

tempmail/

Keep looking

Skills are one crate of 326,835. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.