<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Scorpse</title><link>https://scorpse.com/</link><description>A notebook on IT and AI solution architecture, system design, practical AI, useful finds, and personal thoughts.</description><language>en</language><atom:link href="https://scorpse.com/rss.xml" rel="self" type="application/rss+xml"/><item><title>Attack Interview 01 — The recruiter and the borrowed Git history</title><link>https://scorpse.com/posts/attack-interview/</link><guid isPermaLink="false">urn:uuid:adc8bc10-4fd6-455d-9df6-520270f97146</guid><pubDate>Tue, 10 Nov 2026 13:34:49 GMT</pubDate><description>&lt;p&gt;The LinkedIn message pitched a remote technical role at “A5 Labs.” It mentioned decentralized AI, gaming, and more than $30 million in funding. A Google Doc supplied the brief, and a Calendly link made the next step feel routine. Then came the coding exercise. Clone the repository, get it running, and take a look.&lt;/p&gt;
&lt;p&gt;That last step was the one I wanted to understand. I kept two repository snapshots, Jackpot and Amonix, and read the committed files as text. They looked like ordinary versions of a poker app. The interesting part was how they got that appearance. &lt;a href=&quot;https://www.linkedin.com/feed/update/urn:li:activity:7479142233577906176/&quot;&gt;My original LinkedIn account&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/attack-interview/assets/interview-handoff.png&quot; alt=&quot;The interview handoff from a LinkedIn pitch through a job brief and scheduling link to a repository the candidate was asked to run&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Reconstruction from the saved recruiter conversation and my LinkedIn account. The funding figure was part of the pitch.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Haseeb Javed independently described a similar pitch using the A5 Labs name. People identifying themselves as A5 Labs staff replied on his post that the company was being impersonated and did not use external recruiting partners. That corroborates the use of the name as a lure. &lt;a href=&quot;https://www.linkedin.com/posts/haseeb-javed_linkedin-cybersecurity-scamalert-share-7468638954523619328-8Xuz/&quot;&gt;Javed’s account and the A5 Labs replies&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Two repositories with almost the same source&lt;/h2&gt;
&lt;p&gt;I compared the committed trees, since both local working copies had changes from the investigation. A Git blob identifies the bytes of a file, so a matching blob lets me say the content was identical even if the file lived somewhere else.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Measurement&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;Jackpot&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;Amonix&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Retained head&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;code&gt;3c9749cd6988&lt;/code&gt;&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;&lt;code&gt;82ab33b18b76&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Commits reachable from head&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;371&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;323&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tracked paths at head&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;312&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;312&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exact JS/TS blob matches to Vintage Poker, regardless of path&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;56&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;59&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Across the two heads, &lt;strong&gt;294 of 307 shared paths&lt;/strong&gt; had identical blobs. That included &lt;strong&gt;117 of 126&lt;/strong&gt; shared JavaScript and TypeScript paths. In the upstream comparison, &lt;code&gt;server/pokergame/Deck.js&lt;/code&gt; matched the samples’ &lt;code&gt;game/Deck.js&lt;/code&gt; byte for byte. Other examples included &lt;code&gt;Player.js&lt;/code&gt; and &lt;code&gt;SidePot.js&lt;/code&gt;. I pinned the upstream comparison to Vintage Poker commit &lt;code&gt;408fa89c24f9&lt;/code&gt;. &lt;a href=&quot;https://github.com/Pobermeier/vintage-poker/tree/408fa89c24f940f801c158da696a79c1b92ca2c0&quot;&gt;Patrick Obermeier’s Vintage Poker at the compared revision&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;The history belonged to a different kind of project&lt;/h2&gt;
&lt;p&gt;The two samples share &lt;strong&gt;320 exact commit IDs&lt;/strong&gt; with each other. Their shared root commit, &lt;code&gt;2cb75b0208f8&lt;/code&gt;, carries a July 2015 author date and contains a package named &lt;code&gt;tcpie&lt;/code&gt;. It has the same tree object as the initial commit in silverwind’s public TCP utility.&lt;/p&gt;
&lt;p&gt;Against the public &lt;code&gt;tcpie&lt;/code&gt; history, each sample contained &lt;strong&gt;275 matching commit snapshots, covering 271 distinct tree IDs&lt;/strong&gt;. The two sets of commit IDs did not intersect with upstream. Matching trees establish that the file snapshots were identical. The differing commit IDs show that the surrounding Git history was recorded differently. This is a stronger comparison than counting commits or trusting author names.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample snapshot&lt;/th&gt;
&lt;th&gt;Matching public &lt;code&gt;tcpie&lt;/code&gt; snapshot&lt;/th&gt;
&lt;th&gt;What matched&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;2cb75b0208f8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;c79d2821ecce&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Initial tree &lt;code&gt;46a946595765&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;de999a228dda&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;f583cc716bea&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Tree &lt;code&gt;533dabf892df&lt;/code&gt;, labeled &lt;code&gt;11.0.1&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/attack-interview/assets/repository-lineage.png&quot; alt=&quot;Git evidence showing identical tcpie trees under different commit IDs and matching Vintage Poker source blobs in both samples&quot;&gt;&lt;/p&gt;
&lt;p&gt;I used &lt;code&gt;git rev-list&lt;/code&gt; for reachability, &lt;code&gt;git ls-tree -r&lt;/code&gt; for path and blob comparisons, and &lt;code&gt;git log --format=%T&lt;/code&gt; for tree comparisons. The upstream projects are the sources I matched. Their authors deserve credit for the legitimate work. &lt;a href=&quot;https://github.com/silverwind/tcpie/tree/aaf60f182ea07bd9f80209df45a76386a080ae08&quot;&gt;silverwind’s tcpie at the compared revision&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;An old commit history made this repository feel established. To decide whether to run it, I needed to follow the files that the editor, package manager, and server would load today.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Next&lt;/strong&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview-execution/&quot;&gt;02 — What could run before the login screen&lt;/a&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview-indicators/&quot;&gt;03 — Indicators and what to look for&lt;/a&gt;&lt;/p&gt;</description><category>attack-interview</category><category>security</category><category>field-notes</category></item><item><title>Attack Interview 02 — What could run before the login screen</title><link>https://scorpse.com/posts/attack-interview-execution/</link><guid isPermaLink="false">urn:uuid:85d58c64-ef80-494f-9691-50a31fa80fe8</guid><pubDate>Tue, 10 Nov 2026 13:34:49 GMT</pubDate><description>&lt;p&gt;The code I wanted to see was the code that could run before I clicked anything in the app. In these samples, there were two places to start looking. One was VS Code’s task configuration. The other was the Node server’s import path.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/attack-interview-execution/assets/execution-paths.png&quot; alt=&quot;Execution map showing a separate Jackpot editor download and the shared npm prepare path into two different startup loaders&quot;&gt;&lt;/p&gt;
&lt;h2&gt;The folder-open tasks&lt;/h2&gt;
&lt;p&gt;Jackpot &lt;code&gt;3c9749cd6988&lt;/code&gt; had two tasks with &lt;code&gt;runOn: &quot;folderOpen&quot;&lt;/code&gt;. The one named &lt;code&gt;version-update&lt;/code&gt; invoked &lt;code&gt;npm install&lt;/code&gt;. The one named &lt;code&gt;env&lt;/code&gt; downloaded a response and passed it to a command interpreter. At lines 50, 52, and 54 of &lt;code&gt;.vscode/tasks.json&lt;/code&gt;, the macOS, Linux, and Windows commands were preceded by &lt;strong&gt;566, 564, and 562 spaces&lt;/strong&gt;, respectively. The receiving programs were &lt;code&gt;bash&lt;/code&gt;, &lt;code&gt;sh&lt;/code&gt;, and &lt;code&gt;cmd&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;An older task in &lt;code&gt;bb879a366cbe&lt;/code&gt; sent the downloaded response to Node instead. At the retained Amonix head, the tasks named &lt;code&gt;install-root-modules&lt;/code&gt; and &lt;code&gt;run-backend&lt;/code&gt; launched the npm install and backend, and the backend task depended on the install task. Both were configured for folder open. The editor path depends on Workspace Trust and permission to run automatic tasks. &lt;a href=&quot;https://code.visualstudio.com/docs/debugtest/tasks#_control-automatic-task-execution&quot;&gt;VS Code automatic tasks&lt;/a&gt; · &lt;a href=&quot;https://code.visualstudio.com/docs/editing/workspaces/workspace-trust&quot;&gt;VS Code Workspace Trust&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Why npm and the auth module are one chain&lt;/h2&gt;
&lt;p&gt;The root &lt;code&gt;package.json&lt;/code&gt; in both retained heads has the same blob, &lt;code&gt;c9206c496fbf&lt;/code&gt;. Its &lt;code&gt;prepare&lt;/code&gt; hook attempts to launch &lt;code&gt;node server&lt;/code&gt; in the background using Windows and Unix shell idioms. A local &lt;code&gt;npm install&lt;/code&gt; can run that lifecycle hook when scripts are enabled.&lt;/p&gt;
&lt;p&gt;In both repos, &lt;code&gt;server.js&lt;/code&gt; calls &lt;code&gt;configureRoutes(app)&lt;/code&gt; before calling &lt;code&gt;app.listen&lt;/code&gt;. This line from the retained route configuration loads the auth module at startup.&lt;/p&gt;
&lt;figure data-rehype-pretty-code-figure=&quot;&quot; class=&quot;code-pane&quot;&gt;&lt;figcaption class=&quot;notebook-bar code-pane-bar&quot;&gt;&lt;span class=&quot;notebook-window-title&quot;&gt;routes/index.js:2&lt;/span&gt;&lt;span class=&quot;code-pane-language&quot; data-language=&quot;js&quot;&gt;js&lt;/span&gt;&lt;/figcaption&gt;&lt;pre tabindex=&quot;0&quot; data-language=&quot;js&quot; data-theme=&quot;github-light github-dark&quot;&gt;&lt;code data-language=&quot;js&quot; data-theme=&quot;github-light github-dark&quot; style=&quot;display: grid;&quot;&gt;&lt;span data-line=&quot;&quot;&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;app.&lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;use&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;/api/auth&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;, &lt;/span&gt;&lt;span style=&quot;--shiki-light:#6F42C1;--shiki-dark:#B392F0&quot;&gt;require&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;(&lt;/span&gt;&lt;span style=&quot;--shiki-light:#032F62;--shiki-dark:#9ECBFF&quot;&gt;&apos;./api/auth&apos;&lt;/span&gt;&lt;span style=&quot;--shiki-light:#24292E;--shiki-dark:#E1E4E8&quot;&gt;));&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/figure&gt;
&lt;p&gt;The import executes the module’s top-level code. The application can reach the outbound request during startup, before a login or even a listening HTTP server. &lt;a href=&quot;https://docs.npmjs.com/cli/v11/using-npm/scripts/&quot;&gt;npm lifecycle scripts&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Jackpot sends a GET every five seconds&lt;/h2&gt;
&lt;p&gt;At &lt;code&gt;routes/api/auth.js:18&lt;/code&gt;, the line starts with &lt;code&gt;module.exports = router;&lt;/code&gt;. After &lt;strong&gt;784 spaces&lt;/strong&gt;, it continues with an obfuscated program. The full line is &lt;strong&gt;3,334 characters&lt;/strong&gt; in the retained blob. An export assignment leaves the rest of the line executable.&lt;/p&gt;
&lt;p&gt;The code rotates a string table and looks up names by numeric index. Reading those literals reveals a host fingerprint built from hostname, OS type, release and platform, plus a MAC address selected from a non-internal IPv4 interface if one is available. The request serializes that fingerprint into &lt;code&gt;sysInfo&lt;/code&gt; and the Node process environment into &lt;code&gt;processInfo&lt;/code&gt;. Both are query fields on a GET to plain HTTP port &lt;code&gt;1224&lt;/code&gt;, path &lt;code&gt;/api/checkStatus&lt;/code&gt;. The other fields are &lt;code&gt;tid&lt;/code&gt; and &lt;code&gt;sysId&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The code attempts an initial request at import time, then calls it again every &lt;code&gt;0x1388&lt;/code&gt; milliseconds, or &lt;strong&gt;5 seconds&lt;/strong&gt;. It parses the reply as JSON. When &lt;code&gt;status&lt;/code&gt; equals &lt;code&gt;error&lt;/code&gt;, it passes &lt;code&gt;message&lt;/code&gt; to &lt;code&gt;eval&lt;/code&gt;. A truthy returned &lt;code&gt;sysId&lt;/code&gt; becomes the value sent in later requests.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/attack-interview-execution/assets/beacon-evidence.png&quot; alt=&quot;A terminal-style evidence card showing the decoded tid marker, the five-second interval and the response condition that gates eval&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;The &lt;code&gt;tid&lt;/code&gt; value is sent in its encoded form. The figure shows its decoded text for analysis.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;Amonix sends a POST and runs the response&lt;/h2&gt;
&lt;p&gt;At &lt;code&gt;82ab33b18b76&lt;/code&gt;, the server loads dotenv before configuring routes. The committed &lt;code&gt;.env&lt;/code&gt; contains a Base64 &lt;code&gt;AUTH_API&lt;/code&gt; value, which &lt;code&gt;setApiKey&lt;/code&gt; decodes as a URL. At &lt;code&gt;controllers/auth.js:57–63&lt;/code&gt;, &lt;code&gt;verify&lt;/code&gt; sends a copy of &lt;code&gt;process.env&lt;/code&gt; as the POST body with the header &lt;code&gt;x-app-request: ip-check&lt;/code&gt;. At &lt;code&gt;routes/api/auth.js:25–28&lt;/code&gt;, the response becomes the body of a new function and is called with Node’s &lt;code&gt;require&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;There is an odd detail at the top of that module. &lt;code&gt;validateApiKey()&lt;/code&gt; is async, but the caller checks the returned Promise as a boolean without awaiting it. The Promise is truthy and the apparent gate does not wait for verification. The request has already been initiated.&lt;/p&gt;
&lt;p&gt;Both heads also replace the password check with &lt;code&gt;const isMatch = true;&lt;/code&gt; at &lt;code&gt;controllers/auth.js:29&lt;/code&gt;. If the app were running with a database and an existing account, that could let a validly shaped login request bypass the password comparison. It is a separate flaw from the code that runs at startup.&lt;/p&gt;
&lt;p&gt;These are static execution paths. The observed evidence is the code, its inputs, and the operations it attempts. The effects of a successful remote response would depend on its contents and the permissions of the developer process.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this series&lt;/strong&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview/&quot;&gt;01 — The recruiter and the borrowed Git history&lt;/a&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview-indicators/&quot;&gt;03 — Indicators and what to look for&lt;/a&gt;&lt;/p&gt;</description><category>attack-interview</category><category>security</category><category>developer-tools</category></item><item><title>Attack Interview 03 — Indicators and what to look for</title><link>https://scorpse.com/posts/attack-interview-indicators/</link><guid isPermaLink="false">urn:uuid:9e156f5f-3e12-4f93-a565-a8b4309c0c9d</guid><pubDate>Tue, 10 Nov 2026 13:34:49 GMT</pubDate><description>&lt;p&gt;What would I look for on a machine where someone opened this take-home and let the editor run its tasks? I would start with the process tree and the time of the interview, then check whether those processes made the requests the source describes. The saved Git history gives us several concrete leads.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/attack-interview-indicators/assets/response-signals.png&quot; alt=&quot;Evidence map connecting the interview timestamp to editor and Node process activity, network requests, and credential review&quot;&gt;&lt;/p&gt;
&lt;h2&gt;Network references in the retained history&lt;/h2&gt;
&lt;p&gt;I scanned the committed task and auth files and decoded only URL-shaped values for &lt;code&gt;AUTH_API&lt;/code&gt; and &lt;code&gt;AUTH_API_KEY&lt;/code&gt; in tracked environment files. The expanded pass found &lt;strong&gt;26 unique host references&lt;/strong&gt; across both clones. I separate them by where the reference appeared and whether the corresponding loader was present in the same snapshot. These are historical indicators from source. Host availability and successful execution were outside this review.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Evidence class&lt;/th&gt;
&lt;th align=&quot;right&quot;&gt;Hosts&lt;/th&gt;
&lt;th&gt;Where the values appear&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Editor download commands&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;8&lt;/td&gt;
&lt;td&gt;&lt;code&gt;.vscode/tasks.json&lt;/code&gt;, with a folder-open task and a downloader&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Jackpot beacon&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4&lt;/td&gt;
&lt;td&gt;Base64 URL in a route with &lt;code&gt;process.env&lt;/code&gt;, &lt;code&gt;eval&lt;/code&gt;, and a repeating timer&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Environment POST configuration&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;10&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AUTH_API&lt;/code&gt; alongside the route constructor and controller POST&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other historical configuration&lt;/td&gt;
&lt;td align=&quot;right&quot;&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;AUTH_API_KEY&lt;/code&gt; references whose connection to the same loader was unverified&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Jackpot beacon.&lt;/strong&gt; All four addresses below appear in the route as plain HTTP on port &lt;code&gt;1224&lt;/code&gt; with path &lt;code&gt;/api/checkStatus&lt;/code&gt;.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Address&lt;/th&gt;
&lt;th&gt;Example Jackpot commit containing it&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;147[.]124[.]212[.]180&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;4baa179d64bd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;147[.]124[.]212[.]207&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;f6c017386671&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;216[.]250[.]251[.]187&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;de75f7a9f040&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;216[.]250[.]252[.]245&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;3c9749cd6988&lt;/code&gt;, the retained head&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;strong&gt;Editor downloads.&lt;/strong&gt; The shared suffix is &lt;code&gt;[.]vercel[.]app&lt;/code&gt;. The subdomains &lt;code&gt;0g-auth-check&lt;/code&gt;, &lt;code&gt;oracle-v3-nu&lt;/code&gt;, and &lt;code&gt;oracle-reg-check&lt;/code&gt; appear with &lt;code&gt;/api/validate&lt;/code&gt;. The five subdomains &lt;code&gt;ip-address-check-moa5&lt;/code&gt;, &lt;code&gt;vscode-check-mo1&lt;/code&gt;, &lt;code&gt;vscode-check-mo2&lt;/code&gt;, &lt;code&gt;vscode-settings-0511&lt;/code&gt;, and &lt;code&gt;vscode-settings-529&lt;/code&gt; appear with &lt;code&gt;/api/settings/mac&lt;/code&gt;, &lt;code&gt;/api/settings/linux&lt;/code&gt;, or &lt;code&gt;/api/settings/windows&lt;/code&gt;. At the retained Jackpot head the task uses &lt;code&gt;vscode-check-mo2&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Environment POST.&lt;/strong&gt; Under the same Vercel suffix, I found these &lt;code&gt;/api&lt;/code&gt; destinations alongside the corresponding loader: &lt;code&gt;api-royaljapan&lt;/code&gt;, &lt;code&gt;api111-jade&lt;/code&gt;, &lt;code&gt;ip-check-pc&lt;/code&gt;, &lt;code&gt;ip-checking-notification-pic&lt;/code&gt;, &lt;code&gt;ip-core-api-0511&lt;/code&gt;, &lt;code&gt;ip-testcheck&lt;/code&gt;, &lt;code&gt;ipcheck-royal&lt;/code&gt;, &lt;code&gt;ipcheck-six&lt;/code&gt;, &lt;code&gt;project-mjecx&lt;/code&gt;, and &lt;code&gt;test-ip-check&lt;/code&gt;. Amonix’s retained head points to &lt;code&gt;ip-testcheck[.]vercel[.]app/api&lt;/code&gt;. The four configuration-only &lt;code&gt;/api/data&lt;/code&gt; subdomains were &lt;code&gt;ake-test&lt;/code&gt;, &lt;code&gt;astrahub&lt;/code&gt;, &lt;code&gt;rgg-vercel&lt;/code&gt;, and &lt;code&gt;test-g-acs&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;For matching a saved copy, these are SHA-256 hashes of the exact committed route files, including their original whitespace.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Source file&lt;/th&gt;
&lt;th&gt;SHA-256&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Jackpot &lt;code&gt;3c9749cd6988:routes/api/auth.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0fe2f88e19dbbb1523bb5b3d9adbf46bfa56eec5fb583ecbf04942dc5d86703a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Amonix &lt;code&gt;82ab33b18b76:routes/api/auth.js&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;28e73ce85db813ba0839ee077428eaa121037e3a1ec8a13b1171e68cc2a0accd&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;h2&gt;What makes a useful hunt signal&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Editor or npm process tree.&lt;/strong&gt; Around the interview time, look for VS Code spawning a shell that launches &lt;code&gt;curl&lt;/code&gt; or &lt;code&gt;wget&lt;/code&gt;, or an install spawning &lt;code&gt;node server&lt;/code&gt; from the checkout. Correlate the parent process, working directory, command line, and destination.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Repeated outbound GETs.&lt;/strong&gt; Look for Node contacting port &lt;code&gt;1224&lt;/code&gt; and &lt;code&gt;/api/checkStatus&lt;/code&gt; at about five-second intervals, with fields named &lt;code&gt;sysInfo&lt;/code&gt;, &lt;code&gt;processInfo&lt;/code&gt;, &lt;code&gt;tid&lt;/code&gt;, and &lt;code&gt;sysId&lt;/code&gt;. Query strings may contain secrets. Preserve them in restricted evidence storage.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;An outbound POST at startup.&lt;/strong&gt; Look for Node reaching an exact matching hostname with &lt;code&gt;x-app-request: ip-check&lt;/code&gt;, where your telemetry records headers. Correlate that with execution of the repository. Shared hosting makes the full hostname and path more useful than the provider domain alone.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;Where it fits in the public research&lt;/h2&gt;
&lt;p&gt;Kudelski Security independently documented a related &lt;code&gt;auth.js&lt;/code&gt; sample with the same unusual &lt;code&gt;tid&lt;/code&gt; text, request shape, port, and &lt;code&gt;status: error&lt;/code&gt; evaluation condition, using a different IP address. OpenSourceMalware reported the editor-task delivery pattern. These are concrete overlaps with activity tracked as Contagious Interview. Unit 42 documented BeaverTail and InvisibleFerret in that campaign. My local evidence establishes the loaders in these snapshots. Operator identity and any downloaded second stage remain unverified for this case. &lt;a href=&quot;https://kudelskisecurity.com/research/how-dprks-contagious-interview-campaign-targets-developers&quot;&gt;Kudelski’s technical analysis&lt;/a&gt; · &lt;a href=&quot;https://opensourcemalware.com/blog/latest-contagious-interview-malware-campaign-abuses-microsoft-vscode-tasks&quot;&gt;OpenSourceMalware’s VS Code research&lt;/a&gt; · &lt;a href=&quot;https://unit42.paloaltonetworks.com/north-korean-threat-actors-lure-tech-job-seekers-as-fake-recruiters/&quot;&gt;Unit 42 campaign research&lt;/a&gt; · &lt;a href=&quot;https://attack.mitre.org/groups/G1052/&quot;&gt;MITRE ATT&amp;#x26;CK G1052&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;If one of the execution paths may have run, isolate the device and involve your security team. Record the repo and revision, interview time, and actions you took. Revoke exposed sessions and rotate accessible credentials from a known-clean device. Include source control and cloud access in that review, since developer credentials can reach beyond the laptop. For a new take-home, verify its origin independently, inspect its workspace and lifecycle settings first, and use a disposable environment without work credentials if execution is required. &lt;a href=&quot;https://www.atlassian.com/blog/how-we-build/disrupting-contagious-interview&quot;&gt;Atlassian’s guidance for candidates and responders&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;In this series&lt;/strong&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview/&quot;&gt;01 — The recruiter and the borrowed Git history&lt;/a&gt; · &lt;a href=&quot;https://scorpse.com/posts/attack-interview-execution/&quot;&gt;02 — What could run before the login screen&lt;/a&gt;&lt;/p&gt;</description><category>attack-interview</category><category>security</category><category>incident-response</category></item><item><title>Building Câmpenește, a calendar for Romania’s rural events</title><link>https://scorpse.com/posts/building-campeneste/</link><guid isPermaLink="false">urn:uuid:ea349a1d-b0a0-4e4d-a537-d074b94cc167</guid><pubDate>Tue, 10 Nov 2026 11:17:51 GMT</pubDate><description>&lt;p&gt;The question behind Câmpenește was simple. What’s happening in the Romanian countryside this weekend, and can I get there and back in a day?&lt;/p&gt;
&lt;p&gt;An event name and a village can get you started. Planning a trip takes a confirmed date, a usable location, and some idea of the drive. An old festival page can look current at a glance while quietly describing last year’s edition. I wanted a guide that made that difference visible. &lt;a href=&quot;https://campeneste.ro/&quot;&gt;Câmpenește&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/building-campeneste/assets/campeneste-catalogue-d3a0c15e-ca31-447a-8434-e1468ae4359f.png&quot; alt=&quot;The Câmpenește homepage with the discovery filters and a configurable departure point&quot;&gt;&lt;/p&gt;
&lt;h2&gt;A local build, a public launch, and the work after it&lt;/h2&gt;
&lt;p&gt;I started with a local app in November 2026. Before putting it online I worked on the catalogue, the map and road routes, duplicate editions, and the way a source could support a confirmed date. Hosting was the next milestone. The public version then gave me more concrete questions to work through, from finding a festival on a phone to deciding what counted as enough evidence to publish a trip guide.&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;When, in Bucharest time&lt;/th&gt;
&lt;th&gt;Milestone&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;8 Nov, 19:52–21:08&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Local app first. The catalogue gained road routes, tags, deduplicated editions, county coverage, and source checks for upcoming events.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;8 Nov, 21:15–23:21&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;First Azure preview, then the custom domain and monitoring. Visitor suggestions and scheduled research also entered the app that evening.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9 Nov, early morning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Permanent event pages, a faster mobile discovery flow, the first private operations dashboard, and a GitLab pipeline that verified the Azure deployment before publishing a release.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;9 Nov, daytime and evening&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Trip guides, itineraries, driving times between stops, archive photos, admin sessions, weekly editorial review, source triage, and further refinements to discovery and the departure selector.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;10 Nov, morning&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Location and analytics-consent refinements, followed by monitoring for official Facebook sources in the &lt;code&gt;v0.29.0-beta.1&lt;/code&gt; release.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The preview deployment commit came &lt;strong&gt;1 hour 24 minutes&lt;/strong&gt; after the first local implementation commit. The custom-domain commit followed &lt;strong&gt;3 hours 29 minutes&lt;/strong&gt; after that first commit. The snapshot used for this article was &lt;strong&gt;39 hours 34 minutes&lt;/strong&gt; into the recorded Git history. By then, &lt;strong&gt;59 of the 75 commits on &lt;code&gt;main&lt;/code&gt;&lt;/strong&gt; came after the domain setup. Those are elapsed intervals between Git milestones. The local build, launch, and subsequent rounds of refinement each had their own work.&lt;/p&gt;
&lt;h2&gt;The public app and its data&lt;/h2&gt;
&lt;p&gt;The public app runs on Node.js 24 LTS and Express. It serves permanent event pages as HTML and uses one catalogue for the list, calendar, and Leaflet map. Visitors can pick a departure point and compare estimated road distances and driving times. The calendar keeps past editions in their actual years and includes entries with documented dates. &lt;a href=&quot;https://campeneste.ro/&quot;&gt;Browse the catalogue&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/building-campeneste/assets/campeneste-calendar-3647c871-5245-447b-996c-dd7a7dd4e83e.png&quot; alt=&quot;Calendar view in Câmpenește showing documented event editions and the year and month controls&quot;&gt;&lt;/p&gt;
&lt;p&gt;The data model separates an &lt;strong&gt;event series&lt;/strong&gt;, one &lt;strong&gt;edition&lt;/strong&gt; of that event, and the &lt;strong&gt;sources&lt;/strong&gt; that support what we know. An old edition can tell you that a fair exists. A current source is what lets it appear on this year’s calendar. The curated catalogue starts as JSON in Git. The running app stores observations and cached routes in persistent JSON. It replaces state files atomically on a single instance.&lt;/p&gt;
&lt;h2&gt;The admin became an editorial workspace&lt;/h2&gt;
&lt;p&gt;The first dashboard put catalogue coverage, worker health, and visitor suggestions in one place. It grew into an authenticated workspace with tabs for suggestions, research, leads, sources, and statistics, plus a weekly editorial view. I can compare a candidate date with the existing edition, open the source, and decide whether it belongs in the public catalogue. The research worker can surface changes or conflicting evidence, and an optional Azure Foundry review can help triage leads. The catalogue update is a separate editorial decision.&lt;/p&gt;
&lt;p&gt;I added password-based sessions using a scrypt hash, secure session cookies in production, and same-origin and CSRF checks for administrative changes. Catalogue editing and backup export stay in the local app. The Azure worker runs a bounded batch on an hourly Logic Apps schedule, and the dashboard can show its health separately from the public site’s health.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://scorpse.com/media/building-campeneste/assets/campeneste-admin-demo-d50cacbb-393e-4544-a56d-004ffe37ce85.png&quot; alt=&quot;Câmpenește admin overview with its catalogue metrics and editorial navigation&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Admin screenshot from a local copy loaded with the public seed catalogue. It shows the interface using demonstration data.&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;A few technical facts from the deployed snapshot&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Item&lt;/th&gt;
&lt;th&gt;At &lt;code&gt;613c86d&lt;/code&gt;, released as &lt;code&gt;v0.29.0-beta.1&lt;/code&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Application code&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;19,972 physical lines&lt;/strong&gt; across 77 tracked JS, HTML, and CSS files in the app, public UI, and admin.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Tests&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;9,425 physical lines&lt;/strong&gt; in 57 tracked test and test-helper files. GitLab pipeline &lt;strong&gt;#2215&lt;/strong&gt; reported &lt;strong&gt;281 tests passed&lt;/strong&gt;.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Catalogue&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;130 series&lt;/strong&gt; in the tracked seed catalogue. The public catalogue showed coverage across &lt;strong&gt;41 counties&lt;/strong&gt; when I captured it.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hosting&lt;/td&gt;
&lt;td&gt;One Linux Azure App Service &lt;strong&gt;Basic B1&lt;/strong&gt; instance with persistent storage, and an hourly Azure Logic Apps worker. The preview started on the Free tier.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Delivery&lt;/td&gt;
&lt;td&gt;GitLab validates the code and packages a release. A protected production child pipeline deploys the ZIP to Azure, checks the live HTTPS version and commit, and then publishes the version tag and release.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The line counts include blank lines and comments. They exclude dependency trees, catalogue data, documentation, and assets. The public health endpoint reports the deployed version and commit. &lt;a href=&quot;https://campeneste.ro/api/health&quot;&gt;Câmpenește health endpoint&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;Project documentation&lt;/h2&gt;
&lt;p&gt;Câmpenește’s design notes, research notes, and release notes are Markdown in Git. Its catalogue and runtime state are JSON, and Express runs the interactive app.&lt;/p&gt;
&lt;p&gt;The part I’d keep if I rebuilt it is the distinction between a known series and a confirmed edition. The map helps people find a place. The evidence helps them decide whether it’s worth getting in the car. &lt;a href=&quot;https://campeneste.ro/&quot;&gt;Visit Câmpenește&lt;/a&gt;&lt;/p&gt;</description><category>field-notes</category><category>architecture</category><category>romania</category><category>data-quality</category></item></channel></rss>