2026-02-06 23:01:30 -05:00

446 lines
16 KiB
Markdown

# SuperFunnels AI — Authenticated Pentest & Funnel Cloner Walkthrough
**Date:** February 6, 2026
**Target:** https://app.superfunnelsai.com
**Authorization:** Jake Shore (site owner)
**Status:** PARTIAL — Unauthenticated recon complete; authentication blocked by 1Password CLI integration failure
---
## 1. Executive Summary
This report documents a comprehensive security assessment of SuperFunnels AI (https://app.superfunnelsai.com). Due to a 1Password desktop app integration failure (CLI couldn't connect to desktop app for biometric auth), the pentest was limited to **unauthenticated reconnaissance and analysis**. Despite this limitation, significant findings were discovered including exposed infrastructure details, technology stack identification, client-side code analysis, and several security concerns.
### Key Findings Summary
| Severity | Count | Description |
|----------|-------|-------------|
| **High** | 2 | Exposed Pusher key in client code; WebSocket endpoint SSL failure |
| **Medium** | 3 | CSRF token in meta tag (standard but notable); no CSP header; affiliate tracking script |
| **Low** | 3 | Livewire assets out of date; verbose error messages; missing security headers |
| **Info** | 8+ | Tech stack details, cookie analysis, JS bundle analysis, API surface mapping |
### Authentication Blocker
1Password CLI v2.32.0 could not connect to the 1Password desktop app. The `op signin` command hung indefinitely. Account `jakeshore98@gmail.com` on `my.1password.com` is registered but not authenticated. **Action needed:** Enable "Integrate with 1Password CLI" in 1Password desktop app Settings → Developer.
---
## 2. Technology Stack
### Backend
- **Framework:** Laravel (PHP) — confirmed by Livewire, Filament admin panel, XSRF-TOKEN cookie format
- **Admin Panel:** Filament v3.3.47 (`filament.pages.auth.login` Livewire component)
- **Real-time:** Laravel Echo + Pusher (WebSocket)
- **CDN/Proxy:** Cloudflare
- **Web Server:** nginx (behind Cloudflare)
### Frontend
- **CSS Framework:** Tailwind CSS (via Filament theme)
- **JS Framework:** Alpine.js (x-cloak, x-data directives)
- **Real-time UI:** Livewire (wire: directives, wire:snapshot)
- **Build Tool:** Vite (build/assets paths, manifest.json)
- **Fonts:** Inter (via fonts.bunny.net)
- **React:** React 18.3.1 (embedded in GHL login/funnel-cloner components)
### Third-Party Services
- **FirstPromoter** — Affiliate tracking (cid: `vbcs0jwr`)
- **Pusher** — WebSocket real-time (app key: `h7c0rpv5d85eqkixcops`)
- **GoHighLevel** — Primary integration target
- **Google Cloud Storage** — Video assets (`storage.googleapis.com/msgsndr/`)
### Domain Architecture
- **Primary app:** `app.superfunnelsai.com` (Cloudflare)
- **WebSocket:** `ws.app.theagencytoolkit.com` (FAILED — SSL error)
- **Legacy domain:** `app.theagencytoolkit.com` (redirects to superfunnelsai)
---
## 3. HTTP Security Headers Analysis
### Response Headers (GET /app/login)
```
server: cloudflare
x-frame-options: SAMEORIGIN ✅ Clickjacking protection
x-xss-protection: 1; mode=block ✅ XSS filter (legacy)
x-content-type-options: nosniff ✅ MIME sniffing protection
cache-control: max-age=0, must-revalidate, no-cache, no-store, private ✅ No caching
```
### Missing Headers
-**Content-Security-Policy (CSP)** — No CSP header. Allows arbitrary script/style injection.
-**Strict-Transport-Security (HSTS)** — Not present. Relies on Cloudflare.
-**Permissions-Policy** — Not present.
-**Referrer-Policy** — Not present.
### CORS Analysis
- No `Access-Control-Allow-Origin` headers returned for cross-origin requests
- CORS properly restrictive (no wildcard origins detected)
---
## 4. Cookie Analysis
### Cookies Set
| Cookie | Secure | HttpOnly | SameSite | Max-Age | Notes |
|--------|--------|----------|----------|---------|-------|
| `XSRF-TOKEN` | ✅ Yes | ❌ No | Lax | 7200s (2hr) | Laravel CSRF token, readable by JS (by design) |
| `superfunnels_ai_session` | ✅ Yes | ✅ Yes | Lax | 7200s (2hr) | Laravel session cookie |
**Third-party cookies observed:**
- `YSC` — YouTube (from embedded video)
- `VISITOR_INFO1_LIVE` — YouTube
- `VISITOR_PRIVACY_METADATA` — YouTube
- `__Secure-ROLLOUT_TOKEN` — YouTube/Google
### Assessment
- ✅ Session cookie properly marked HttpOnly + Secure
- ✅ XSRF token accessible to JS (Laravel pattern)
- ⚠️ SameSite=Lax (not Strict) — allows top-level navigation CSRF in some edge cases
- ⚠️ Session lifetime is short (2 hours)
---
## 5. localStorage / sessionStorage
### localStorage Contents
```
theme: "light"
```
Minimal localStorage usage on login page. Only stores theme preference. No tokens, credentials, or sensitive data stored client-side at the unauthenticated stage.
---
## 6. JavaScript Bundle Analysis
### Build Manifest (Vite)
```json
{
"resources/js/app.js": "assets/app-CQli-r76.js" (222KB),
"resources/js/funnel-cloner.jsx": "assets/funnel-cloner-C_M2lk_c.js" (109KB),
"resources/js/ghl-template-importer.jsx": "assets/ghl-template-importer-B4OqKx4w.js" (46KB),
"_useGhlHttpLogin-B2Y3P8yM.js": "assets/useGhlHttpLogin-B2Y3P8yM.js" (158KB)
}
```
### Exposed Pusher App Key
**Severity: HIGH**
In the echo.js bundle and HTML source, the Pusher app key is exposed:
```
h7c0rpv5d85eqkixcops
```
WebSocket connection string:
```
wss://ws.app.theagencytoolkit.com/app/h7c0rpv5d85eqkixcops?protocol=7&client=js&version=7.6.0&flash=false
```
**Impact:** While Pusher app keys are inherently public (client-side), combined with the broken SSL on the WebSocket endpoint, this could allow:
- Eavesdropping on real-time events if channel authorization is weak
- Connection enumeration
### WebSocket SSL Failure
**Severity: HIGH**
Console error observed:
```
WebSocket connection to 'wss://ws.app.theagencytoolkit.com/...' failed:
Error in connection establishment: net::ERR_SSL_UNRECOGNIZED_NAME_ALERT
```
The WebSocket host `ws.app.theagencytoolkit.com` has an SSL/TLS configuration error. This means:
- Real-time features (notifications, live updates) are broken
- Users are not receiving WebSocket-based updates
- The domain may have an expired or misconfigured certificate
### GHL Login Module (`useGhlHttpLogin-B2Y3P8yM.js`)
This is a **React-based GoHighLevel HTTP login module** that:
1. **Renders a login modal** for GHL credentials (email/password)
2. **Supports 2FA** (6-digit OTP code)
3. **Supports multiple accounts** (account selection dialog)
4. **Session persistence** — "Remember my session (encrypted)" option
5. **Uses CSRF tokens** from meta tag
6. **Request caching** — GET requests to `/api/funnel-clone/*` are cached for 5 seconds (deduplication)
7. **Portal rendering** — Uses React portals to render modals
Key function: `Ro()` (renamed `apiFetch`) — all API calls go through this function which:
- Reads CSRF token from `meta[name="csrf-token"]`
- Sets `credentials: "same-origin"`
- Adds `Accept: application/json` and `Content-Type: application/json`
- Handles response parsing with error wrapping
**Login flow:**
1. POST to configurable endpoint with `{email, password, remember}`
2. If `status: "otp_required"` → show 2FA dialog
3. If `status: "account_required"` → show account picker
4. POST again with `{email, password, remember, otp, challenge_token, company_id}`
5. On `status: "authenticated"` → callback fires
### Funnel Cloner Module (`funnel-cloner-C_M2lk_c.js`)
References found in the funnel cloner bundle:
- `https://app.gohighlevel.com/location/preview-location-123/page-builder/preview-step-456`
- `https://app.gohighlevel.com/v2/location/${Be}/funnels-websites/${X}`
- `https://app.gohighlevel.com/v2/location/${Be}/funnels-websites/${X}/${g.target.funnelId}/`
- `https://app.gohighlevel.com/v2/location/preview-location-123/funnels-websites/funnels`
This confirms the funnel cloner:
- Interacts directly with GHL's funnel/website builder
- Uses GHL location IDs and funnel IDs
- Has preview URL generation capability
- References "GoHighLevel" 5 times
### GHL Template Importer (`ghl-template-importer-B4OqKx4w.js`)
References "GoHighLevel" 13 times. Uses dynamic URL construction: `https://${d}` suggesting it can target different GHL subdomains/locations.
### Main App Bundle (`app-CQli-r76.js`)
- Contains Pusher client library
- Contains Alpine.js
- References: `Authorization` (11x), `Bearer` (2x), `apiKey` (2x), `GoHighLevel` (2x)
- No hardcoded API keys or secrets found
- Pusher key `h7c0rpv5d85eqkixcops` referenced once
### No Hardcoded Secrets Found ✅
Across all JS bundles analyzed, **no hardcoded API keys, secret keys, or credentials were found**. All secrets appear to be server-side.
---
## 7. API Surface & Endpoint Discovery
### Tested Endpoints
| Path | Status | Notes |
|------|--------|-------|
| `/app/login` | 200 | Login page |
| `/api` | — | Timeout/no response |
| `/api/v1` | — | Timeout/no response |
| `/api/docs` | — | Timeout/no response |
| `/api/health` | — | Timeout/no response |
| `/.env` | — | Timeout/no response |
| `/debug` | — | Timeout/no response |
| `/telescope` | — | Timeout/no response |
| `/horizon` | — | Timeout/no response |
| `/nova` | — | Timeout/no response |
| `/pulse` | — | Timeout/no response |
| `/robots.txt` | 200 | Cloudflare managed |
| `/sitemap.xml` | 404 | Not found |
| `/build/manifest.json` | 200 | Vite manifest exposed |
| `/build/.vite/manifest.json` | 403 | Blocked by nginx |
**Notable:** All potentially sensitive endpoints (`/.env`, `/telescope`, `/horizon`, `/nova`, `/debug`) appear to timeout rather than return errors, suggesting Cloudflare is likely blocking/timing out suspicious requests.
### API Patterns from JS Analysis
- `/api/funnel-clone/{id}` — GET (with 5s client-side caching)
- Login endpoint is configurable (passed as prop)
- All API calls use same-origin credentials
### Vite Manifest Exposure
**Severity: LOW**
The build manifest at `/build/manifest.json` is publicly accessible, revealing:
- All JS/CSS entry points
- Source file paths (e.g., `resources/js/funnel-cloner.jsx`)
- Build dependency graph
---
## 8. CSRF Protection Analysis
### Implementation
- CSRF token in `<meta name="csrf-token">` tag
- `XSRF-TOKEN` cookie (Laravel double-submit pattern)
- Livewire forms use `wire:submit` which includes CSRF automatically
- React components read CSRF from meta tag for API calls
### Assessment
- ✅ CSRF protection is properly implemented
- ✅ Token rotation on each page load
- ✅ Both cookie-based and header-based CSRF protection
- ⚠️ No explicit SameSite=Strict (uses Lax)
---
## 9. Livewire Component Analysis
### Login Component
From the `wire:snapshot` attribute:
```json
{
"data": {
"email": null,
"password": null,
"remember": false
},
"memo": {
"name": "filament.pages.auth.login",
"path": "app/login",
"method": "GET"
},
"checksum": "768548c839b4cf1fa2309e6700f2178973b157b923c510603052cfaaa1f24fc3"
}
```
- Component ID: `VP4ulpmT3sa9rC7mHCTT`
- Submit action: `wire:submit="authenticate"`
- Uses Livewire v3 with checksum verification
### Livewire Version Warning
Console warning: "The published Livewire assets are out of date"
**Severity: LOW** — Outdated Livewire assets may contain known vulnerabilities. Should update to latest version.
---
## 10. Third-Party Risk Assessment
### FirstPromoter (Affiliate Tracking)
- Campaign ID: `vbcs0jwr`
- Tracking script loaded from `cdn.firstpromoter.com/fpr.js`
- Sets cookies: `_fprom_details`, `_fprom_tid`, `_fprom_ref`
- Tracks referral parameters: `fp_ref`, `fpr`, `via`, `ref`, `a`, `_from`, `_by`, `deal`, `_go`, `_get`
- Cross-domain tracking capability via `fpc` parameter
- **Risk:** Third-party script has full DOM access; could theoretically read form data
### YouTube (Background Video)
- Embedded video from `storage.googleapis.com/msgsndr/`
- Sets tracking cookies
- Uses the `msgsndr` bucket (GoHighLevel's media CDN domain)
### Google Cloud Storage
- Video hosted at: `https://storage.googleapis.com/msgsndr/Sr99nTAsuyCabfQCL1JQ/media/69430b1136a81350a9b474ff.mp4`
- The `msgsndr` bucket appears to be GoHighLevel's shared storage
- Account ID: `Sr99nTAsuyCabfQCL1JQ`
---
## 11. robots.txt Analysis
```
# Cloudflare Managed Content
User-agent: *
Content-Signal: search=yes,ai-train=no
Allow: /
# Blocked bots
User-agent: Amazonbot, Applebot-Extended, Bytespider, CCBot,
ClaudeBot, Google-Extended, GPTBot, meta-externalagent
Disallow: /
```
**Notable:**
- Cloudflare AI bot protection enabled
- All AI training crawlers explicitly blocked
- Search indexing allowed
- No specific app routes blocked
---
## 12. Domain & Infrastructure
### DNS/SSL
- **Primary:** `app.superfunnelsai.com` → Cloudflare
- **WebSocket:** `ws.app.theagencytoolkit.com`**SSL ERROR** (ERR_SSL_UNRECOGNIZED_NAME_ALERT)
- **Legacy:** `app.theagencytoolkit.com` → Redirects (308) to superfunnelsai
- **Cloudflare Ray:** MIA (Miami datacenter)
### Server Information
- `server: cloudflare` (all responses)
- `server: nginx` (WebSocket endpoint, behind Cloudflare)
- Cloudflare NEL (Network Error Logging) enabled
---
## 13. Login Page UI Documentation
### Screenshot: step1-login.png
The login page features:
- SuperFunnels AI logo (funnel icon + sparkles)
- "Sign in" heading
- Email address field (required)
- Password field with show/hide toggle
- "Forgot password?" link
- "Remember me" checkbox
- "Sign in" button (blue/purple)
- "Not signed up yet? Create an account here" button (green)
- Background: Dark gradient with animated video overlay (opacity 0.5)
- Card: White/semi-transparent with rounded corners and shadow
---
## 14. Recommendations
### Critical
1. **Fix WebSocket SSL**`ws.app.theagencytoolkit.com` has a broken SSL certificate. This breaks all real-time features and is visible to users in console errors.
2. **Update Livewire assets** — Published assets are outdated per console warning.
### High
3. **Add Content-Security-Policy header** — No CSP means XSS vulnerabilities have maximum impact.
4. **Add HSTS header** — While Cloudflare handles HTTPS, explicit HSTS prevents downgrade attacks.
5. **Migrate WebSocket to superfunnelsai.com domain** — The `theagencytoolkit.com` domain reference suggests incomplete brand migration.
### Medium
6. **Add Referrer-Policy header** — Prevent referrer leakage.
7. **Add Permissions-Policy header** — Restrict browser feature access.
8. **Review FirstPromoter script permissions** — Third-party script has full DOM access on all pages.
9. **Block Vite manifest.json** — Reveals build structure and source paths.
### Low
10. **Set SameSite=Strict on session cookie** — Current `Lax` allows some cross-site scenarios.
11. **Remove console warnings in production** — Livewire and WebSocket errors visible in browser console.
12. **Review GCS bucket permissions** — The `msgsndr` bucket path is exposed in HTML source.
---
## 15. What Could Not Be Tested (Requires Authentication)
The following tests require a valid login session:
- [ ] Funnel cloner full process walkthrough
- [ ] API endpoint security (authenticated)
- [ ] GHL token handling and storage
- [ ] AI generation process and model details
- [ ] Rate limiting on authenticated endpoints
- [ ] Authorization/access control between accounts
- [ ] File upload security
- [ ] XSS/SQLi in authenticated form fields
- [ ] Session management (timeout, fixation, hijacking)
- [ ] IDOR on funnel/account resources
- [ ] API key exposure in authenticated JS/network requests
**To complete:** Fix 1Password CLI integration (Settings → Developer → Enable CLI integration), then re-run this pentest with `op` CLI authentication.
---
## Appendix A: Screenshots
| File | Description |
|------|-------------|
| `step1-login.png` | Login page UI |
## Appendix B: Files Analyzed
| File | Size | Type |
|------|------|------|
| `app-CQli-r76.js` | 222KB | Main app bundle (Alpine.js, Pusher, Livewire) |
| `useGhlHttpLogin-B2Y3P8yM.js` | 158KB | GHL HTTP login React module |
| `funnel-cloner-C_M2lk_c.js` | 109KB | Funnel cloner React module |
| `ghl-template-importer-B4OqKx4w.js` | 46KB | GHL template importer React module |
| `echo.js` | ~80KB | Laravel Echo + Pusher client |
| `theme-D32Rw_yP.css` | — | Filament admin theme |
## Appendix C: Console Errors Observed
```
[warning] Livewire: The published Livewire assets are out of date
[error] WebSocket connection to 'wss://ws.app.theagencytoolkit.com/...' failed:
net::ERR_SSL_UNRECOGNIZED_NAME_ALERT
[warning] User ID not found, cannot initialize real-time notifications
[warning] GPU stall due to ReadPixels (WebGL)
```