How My Unicorn Founder Friend Was Phished
Founder of Segment and Charm Industrial, Peter Reinhardt had no reason to be suspicious about an interview request from a journalist writing a piece on carbon removal, Charm Industrial's mission. The message came from the verified X account of a well-known journalist associated with WIRED, a reputable publication. Likewise, a tech CEO isn't the stereotypical naive target of cyberattacks.
However, as these attacks become increasingly sophisticated, you won't just worry about your clueless father-in-law or boomer coworker falling prey to phishing scams, but also your tech founder friends. Indeed, when Reinhardt clicked the Calendly booking link to select a time, his account was taken over for only a few minutes.
Co-founder of Segment, which Twilio acquired in a deal valued at approximately $3.2 billion, Peter is a friend of the Casco team, so our CEO René introduced me to Peter and asked me to investigate the cyberattack.
I'm Anthony Gibbs, an offensive security engineer at Casco. Basically, my job is to hack software before the bad guys hack it first. My first question to Peter: what had the initial booking request asked him to do? His answer would fundamentally change the next steps in my investigation.
If he entered a password on an unfamiliar page, I would inspect that page for credential collection. If he approved an application, I would check X’s records for the app’s identity and granted permissions.
- René Brandel1:42 PM
Hi Peter Anthony is looking into your phishing attack
- Anthony Gibbs1:45 PM
Hey Peter! What Rene said - I was curious about the initial steps, particularly whether or not there was a phishing link involved, third party app integration request, or coercion to input credentials at the identified domain which Rene has already shared with me in a DM.
- Peter Reinhardt1:50 PM
for sure a lot more info here:
Asteria Phishing DossierResearch dossier shared by Peter - Anthony Gibbs1:50 PM
This is excellent, just what I was looking for. Thank you
Peter shared a dossier detailing all his interactions with the hacker. The original messages showed an account presenting itself as WIRED journalist Simon Hill, moving the conversation from an interview proposal to a routing-form link on the genuine calendly[.]com domain. Peter confirmed that the flow went through a real Calendly page. The sender’s identity remained unverified.
When I began investigating, the application no longer had access to Peter’s account. I started with the remaining infrastructure: a domain, a login page, and the booking flow.
Over September 9–11, I traced five publication-branded lures to a shared backend called Asteria and verified that its X application requested read, write, and direct-message permissions. Parts of the campaign went offline as I kept probing. I submitted abuse reports on September 17; by my September 18 follow-up, all observed portions of the campaign were offline. The status table below records the outcome and the provider acknowledgments I received.
- Other founders have seen this pitch
- The attack: an interview with an authorization gate
- Step by step: from booking page to account access
- Following the trail back to the operator’s application
- What happened when I kept probing
- Infrastructure status after the abuse reports
Other founders have seen this pitch
The interview invitation is a recurring lure. Britney Muller described a closely matching attempt: someone presenting as a Bloomberg journalist contacted her on X about an AI and SEO article. The profile had a history of sharing Bloomberg coverage. A real Calendly link led to a booking page, then redirected her to a Calendly imitation asking her to sign in with X. The domain change and unexpected sign-in request made her pause. Her account describes an attempt, with no reported compromise.
Carlos Domingo, Securitize’s co-founder, also reported receiving a similar approach. Paubox’s roundup reproduces his account: he declined when he could not verify the supposed reporter’s identity and later noted that the account had been suspended. TechCrunch itself warned in March 2026 that impersonators were targeting companies with fake newsroom outreach.
There are confirmed compromises too. In January 2024, BleepingComputer reported that attackers took over CertiK’s X account after an attacker used a compromised journalist’s account to propose a Forbes interview and send a malicious scheduling link. The attackers then posted a link to a cryptocurrency wallet drainer from the company’s account.
These reports show how a credible publication name can turn an unsolicited message into a welcome opportunity. They do not establish a shared operator or a common OAuth implementation. To understand what happened in Peter’s case, I needed to follow the booking flow and inspect the access it requested.
The attack: an interview with an authorization gate
The attack began with a real Calendly page. That familiar domain gave the scheduling request credibility before the flow reached the attacker’s WIRED-branded booking page. The recovered page imitated a 30-minute booking flow with a blurred calendar and required the visitor to connect an X account to continue.
The page made authorizing an X application a requirement for scheduling the interview. By this point, the visitor had already discussed the story with the supposed reporter and agreed to find a time. The account connection appeared to be part of that booking process.
The campaign directed the visitor to the genuine X authorization service to approve an application named CalendarBookings. In my controlled test, X confirmed that this application received permission to read, write, and access direct messages.
In this form of OAuth consent phishing, the attacker obtains access through the permissions the target grants on the real provider’s consent screen. A visitor who checks the X domain can still authorize a malicious application, so the app’s identity and requested permissions also need scrutiny.
Asteria’s “What this authorises” summary mentioned profile and tweet access, posting and deletion, and social actions. It left out the direct-message access listed on X’s consent screen.
The application’s displayed website imitated Calendly: bookings.caIendly[.]com. The character after ca was an uppercase I, where a reader would expect a lowercase l. Its normalized spelling is bookings.caiendly[.]com. X displayed it as the app’s configured website, although the hostname did not resolve in the saved checks.
After authorization, my research browser reached a real Calendly event. Calendly appeared at both ends of the attack: the genuine routing form in Peter’s original message and the configured event after consent. The attacker placed the authorization request between those familiar scheduling steps.
Step by step: from booking page to account access
Peter’s original entry point was the real Calendly routing form. I reconstructed the later stages from the files the sites served to an unauthenticated visitor, then followed the OAuth flow with a dedicated research account and checked X’s own records before and after consent. The captured sequence below begins at the known OAuth start URL, after the initial Calendly step.
1. The booking script supplies the route
Three assignments in booking.js identified the OAuth backend, a public campaign selector, and the final calendar destination. Pages on publication-branded hosts still pointed their connection buttons to asteriapanls[.]com.
The same script could personalize the gate with a name and avatar supplied through URL parameters. A button that said “Continue as” followed by someone’s name could appear before X had authenticated anyone.
Scroll through the explanation to highlight each part of the recovered code. These are selected statements, with omissions marked and hostnames defanged. Line numbers refer to this excerpt.
Step 1: Follow the button
The booking button routes authorization through Asteria.
The custom booking script hardcodes the Asteria backend, a public link selector, and the final Calendly event. The selector chooses a campaign route; it is visible to every visitor and is not an OAuth credential.
Lines 1–5
Step 2: Read the URL
The invitation supplies the identity cues.
The page reads names and handles from query parameters. The first handle alias, utm_content, also appears in the original outreach. The code can use this input to look up an avatar before X authenticates the visitor.
Lines 7–12
Step 3: Personalize the gate
The supplied name activates the personalized gate.
A supplied name enables the personalized presentation. The authorization link still uses CONNECT_URL. In isolated fixtures, a handle alone kept the generic gate: the name was what activated this branch.
Lines 14–18
Step 4: Make it recognizable
The avatar is another URL-derived cue.
An explicit HTTP(S) avatar URL takes priority. Otherwise the code builds an avatar lookup from the supplied X handle. Fourteen network-isolated fixtures checked these rules across both variants, with synthetic image requests intercepted and blocked.
Lines 20–25
Step 5: Render the name
The button displays the name from the URL.
The button combines a fixed label with the supplied name through textContent before any X authorization. The name is used for display, while the authorization link retains the fixed CONNECT_URL value.
Lines 27–29
See the synthetic personalization example
2. Five branded hosts converge on the same backend
I recovered WIRED, TechCrunch, and Bloomberg branding across five hosts under 30min-meetings[.]com. Each custom script matched its corresponding copy on Asteria byte-for-byte. Across the two script variants, the complete diff changed only the public selector.
Host under 30min-meetings[.]com | Page path | Selector group |
|---|---|---|
wired | /booking-preview/ | 1 |
techcrunch | /booking/ | 1 |
bloomberg | /booking2/ | 1 |
thewired | /booking3/ | 2 |
tech-crunch | /booking4/ | 2 |
Matching custom code, backend configuration, and destination tied these pages together more strongly than a shared IP address alone. The selector groups describe routing differences in the recovered kit; they do not establish separate attack waves.
3. Asteria sends the browser to the real X consent screen
The OAuth start route returned an HTTP 307 redirect to api.x.com/oauth/authorize, carrying a temporary request token. The protocol in the capture was OAuth 1.0a.
The sequence below follows my September 11 controlled run, beginning at the known start URL. I had already recorded an empty connected-app list for the research account. Select a phase to follow the authorization, return to booking, or verification.
Read the sequence as text
- The browser requests Asteria’s OAuth start route with the campaign selector.
- Asteria redirects the browser to the genuine X authorization service with a temporary request token.
- The researcher approves CalendarBookings on X using the dedicated research account.
- X returns an HTML document whose meta refresh navigates to Asteria’s callback, carrying the same request token and a verifier.
- The browser requests Asteria’s callback with that token and verifier.
- Asteria responds with a redirect to its completion page.
- The completion page loads successfully. Its fallback handles the same-tab case.
- The completion document navigates the browser to the configured Calendly event; no meeting is booked.
- A fresh X connected-app response identifies CalendarBookings and confirms read, write, and direct-message permissions.
- The researcher revokes the grant and checks a fresh network-sourced connected-app list, which is empty.
4. The same temporary token returns with a verifier
After consent, X returned 200 HTML containing a meta refresh to Asteria’s callback. That navigation carried the original temporary request token and an oauth_verifier. Asteria’s callback then returned the 307 redirect to /booking-preview/popup-close.html.
# Selected browser-capture fields; credentials replaced with role labels.
GET asteriapanls[.]com/api/oauth/twitter/callback
?oauth_token=<REQUEST_TOKEN_A>
&oauth_verifier=<VERIFIER>
HTTP/2 307
Location: /booking-preview/popup-close.htmlX’s return navigation came from an HTML document, followed by Asteria’s HTTP redirect. Matching the temporary token across the start, consent, and callback connected the observed browser events. The backend’s server-to-server token exchange was outside this capture.
5. The completion page takes the visitor back to scheduling
The completion document tried to close the authorization window and notified the opening tab. It used a fixed message marker, a broadcast channel, and a local-storage marker. For the same-tab case, it fell back to navigating directly to the configured Calendly event.
In my run, the same-tab fallback took the browser to calendly[.]com/30min-calls/interviews at 00:42:08 UTC. The resulting event lookup matched the event ID, UUID, user, organization, and embedded profile I had mapped earlier. I did not book a meeting.
The saved lure also accepted connected=1 in its query string and displayed “X account connected.” I reproduced that message in isolated browser fixtures without granting any remote access.
Compare the default gate with the isolated success-state fixture
I checked X’s connected-app record to establish whether authorization had actually succeeded.
6. X confirms the application’s permissions
At 00:42:23 UTC, a fresh X connected-app response identified CalendarBookings, application ID 33286598, and its permissions. I omit credential fields below.
{
"app_id": "33286598",
"name": "CalendarBookings",
"token_type": "oauth1",
"can_read": true,
"can_write": true,
"can_read_dms": true,
"email_access": false
}The provider’s response confirmed read, write, and direct-message access for my research account. Actions taken against Peter’s account were outside this controlled test.
I verified this app ID for the first selector. Evidence for the second selector was limited to matching visible app presentation in earlier captures.
7. I revoke the research grant and verify the cleanup
I removed the research grant and requested the connected-app list again. At 00:42:40 UTC, the fresh network response was empty: {}.
The temporary request token used during authorization differed from the grant token submitted for revocation. The public selector, temporary token, and grant token served separate roles in campaign routing, authorization, and revocation.
Following the trail back to the operator’s application
The consent flow explained how the attacker could obtain account access. I also wanted to understand the service behind the lures.
The unauthenticated login page gave me the first clues. Its JavaScript posted to /api/auth/login. Its stylesheet contained names such as account-card.is-revoked, composer-tweet, public-scope, and link-url. Those suggested linked accounts, a post composer, and public authorization links.
Next.js route-filter data supplied candidate paths. I treated those as hypotheses because Bloom filters can produce false positives, then checked the routes with bounded HTTP requests. Across 19 recorded paths, a seven-method matrix helped distinguish nonexistent resources from methods the application simply did not accept.
For example, /api/accounts/import rejected GET with 405, advertised POST, and returned an authentication error when I sent it malformed JSON. The endpoint existed, but the tested request still met an authentication gate.
OPTIONS /api/accounts/import
HTTP/2 204
Allow: OPTIONS, POST
GET /api/accounts/import
HTTP/2 405
# Recorded POST with malformed JSON
POST /api/accounts/import
HTTP/2 401
{"ok":false,"error":"Not authorised."}The management endpoints required authentication
I tested a known selector as an account-path value, a query parameter, a bearer credential, and an asteria_session cookie. Each request returned 401 with “Not authenticated.” Other tested object IDs, invalid authentication values, and method overrides continued to meet authentication gates or method rejection.
The React Server Component response for /links returned 200 with a login redirect in its body:
5:E{"digest":"NEXT_REDIRECT;replace;/login;307;"}Reading the response body confirmed that the route continued to enforce authentication despite the 200 status.
Callback tests showed how campaign state survived
Before granting access, I started one unapproved OAuth request per selector and returned each token with an invalid verifier and no browser cookies. Each callback redirected to the error page for its originating selector. Repeating the requests produced the same routes.
The repeated redirects showed that the backend could associate a request token with its campaign route without a browser cookie.
Infrastructure records supported the application-level connection. The two parent domains, 30min-meetings[.]com and asteriapanls[.]com, were registered four seconds apart on August 2, 2026. The checked lure hosts shared Asteria’s IPv4 address, and the domains shared nameservers and an IPv6 address. Those facts corroborated the shared code and routing; the operator’s identity remained unknown.
Investigation indicators and scope
| Indicator | Observed role |
|---|---|
asteriapanls[.]com | Shared OAuth backend and management application |
30min-meetings[.]com | Parent of five recovered publication-branded lures |
CalendarBookings / 33286598 | Provider-confirmed app identity for the first-selector grant |
asteria-booking-preview | Completion-message source marker |
asteria-oauth | Broadcast channel used by the completion implementation |
asteria_oauth_connected | Local-storage completion marker |
111.90.148[.]101 | IPv4 address in the saved September 9 records |
2a06:1700:0:91c::f766:a952 | IPv6 address in the saved records |
calendly[.]com/30min-calls/interviews | Configured final destination on a legitimate service |
I conducted the technical research September 9–11, 2026, submitted abuse reports September 17, and followed up on the campaign’s status September 18. I defang campaign hostnames and omit credential values here. I used a dedicated research account for the controlled grant and revoked the grant afterward. The operator’s identity and total victim count remain unknown; the investigation did not establish access to the management panel or a permanent end to the operation.
What happened when I kept probing
As I continued probing, parts of the campaign went offline. By the night of September 11, portions were unavailable at the locations I had used. I assess that the investigation prompted the operator to pull back, based on that timing.
Visible analysis can make an attacker nervous about how much of their operation investigators have exposed. Following redirects, connecting branded pages to a shared backend, and examining the application can signal to the operator that someone is scrutinizing the campaign. An operator may decide to pull a lure before the investigation leads to wider reporting or action by a service provider. Even light probing can create that pressure.
Infrastructure status after the abuse reports
On September 17, I reported the campaign to the domain registrars, Calendly, X, and Google. In my September 18 follow-up to René, I reported that every observed portion of the campaign was offline. The table records that follow-up, alongside the narrower evidence available for individual services.
| Component | Report destination | Status documented on September 18 |
|---|---|---|
Five publication-branded lure hosts under 30min-meetings[.]com | Domain registrars; Google | Reported offline in the follow-up. Registrar suspension and Google warning status were unconfirmed. |
Asteria backend at asteriapanls[.]com | Tucows Compliance | Reported offline. The screenshot below confirms Tucows created an abuse-report ticket; it does not state an enforcement action. |
Calendly routing form at calendly[.]com/d/dvrt-y6w-vb4 and event at calendly[.]com/30min-calls/interviews | Calendly | Included in the report and the overall offline follow-up. Calendly acknowledged the report and said it would pass it to a specialist team. Account suspension was unconfirmed. |
X application CalendarBookings, app ID 33286598 | X | Report submitted. The follow-up described the overall flow as offline; a separate X app suspension was unconfirmed. |
| Hosting behind the campaign domains | No separate hosting-provider acknowledgment in the evidence | The associated web endpoints were reported offline. Server or hosting-account termination was unconfirmed. |
The offline status comes from my follow-up checks. The two screenshots below establish that Tucows and Calendly received the reports. Their replies leave the specific enforcement actions unresolved, so I cannot attribute each outage to a provider decision.
How to spot the attack
The warning signs in this case are useful the next time someone offers to feature you or your company:
- Verify the reporter independently. Find their author page and contact details through the publication’s own website. Confirm the request through that channel. A convincing profile, relevant pitch, and history of sharing articles can all be part of the impersonation.
- Watch where the booking link takes you. Check the full hostname when the page changes, especially before signing in. Peter’s attack began on a real Calendly page, and Britney Muller described a similar route through a genuine booking link. In my investigation, an uppercase
Ialso helped a look-alike hostname resemble Calendly. - Read the provider’s permission request. Permission to post from your X account or read your direct messages is a reason to stop an interview booking. In this campaign, the application requested those permissions on the genuine X consent screen, and the lure’s own summary left out direct-message access.
- Check connected apps if you already approved access. Open X directly and use its connected-app controls to review permissions and revoke the suspicious application. That is how I removed my research grant and verified the cleanup.
If you are a security engineer, start investigating
A suspicious booking link gives you a concrete place to begin. Preserve the original message and URL, then examine the publicly served page and JavaScript in an isolated research browser. Trace the redirects and record the domains, application names, and requested permissions. In this case, a few assignments in booking.js connected the publication-branded page to the shared OAuth backend.
Compare related pages for matching code and destinations. Use bounded probes to understand which endpoints are exposed, and keep any consent testing confined to dedicated research accounts. Record timestamps and responses as you go so you can show when a lure changes or disappears. The resulting evidence also gives the impersonated publication, hosting provider, and authorization platform something specific to investigate.
The presence of that investigation can change an operator’s willingness to keep a campaign running. Pulling pages, replacing domains, and rebuilding lures costs them time and interrupts the paths they use to reach people. A small amount of careful analysis can make continuing an attack less attractive and help protect the next person who receives the pitch.
About Casco
Casco tests applications, APIs, infrastructure, and AI systems to find vulnerabilities and validate their impact. Talk to the Casco team.