Anti GhostGPT. Как ловить невидимых помощников на техническом собеседовании. VPN + DNS‑журнал вместо прокторинга
Дисклеймер. Все, что описано ниже, мысленный эксперимент и собранный на коленке стенд «а что, если». Никакая реальная компания, команда или процесс найма здесь не описаны, любые совпадения случайны. IP‑адреса, домены и пароли в примерах вымышлены. Если решите повторить что‑то подобное на живых людях, сначала поговорите с юристом и предупредите кандидатов.
Типичное собеседование в 2026 году: кандидат расшарил экран, бодро рассуждает о сагах и шардировании, проходит live‑кодинг сессии без единой паузы. А если еще и хороший актер, то можно не сразу заподозрить его в читинге и использовании разного рода помощников. А там и до волчизма недалеко.
Инструменты вроде Ghost GPT и его многочисленные аналоги умеют слушать звонок, распознавать вопрос и показывать готовый ответ в окне, которое не попадает в захват экрана. Классические меры против них почти бессильны:
-
Демонстрация экрана — оверлей помощника исключен из захвата на уровне ОС.
-
Покажите, что у вас открыто — помощник может работать на втором мониторе, телефоне или в отдельном процессе без окна.
-
Прокторинг‑софт — тяжело, дорого, требует установки агента и сразу отпугивает сильных кандидатов.
-
Наблюдение за глазами и паузами — субъективно и легко объясняется волнением.
Но у всех этих помощников есть общая слабость: им нужна связь. Распознать речь, сходить в LLM и получить ответ, это все сетевые запросы.
А что если…
.на время интервью кандидат подключается к корпоративному VPN, то и весь его трафик проходит через сервера компании. Тогда интервьюер сможет видеть журнал запросов в реальном времени и может сопоставить их с тем, что происходит в разговоре.
Захватывать можно весь трафик целиком, но анализировать ip‑адреса в real‑time такое себе удовольствие. Поэтому логичнее анализировать DNS‑запросы, которые проще анализировать через web‑интерфейс, понятный HR и интервьюеру. На роль такого анализатора может подойти AdGuard (или любая иная альтернатива). Возможность «горячей» блокировки трафика прилагается.
Чтобы у VPN был естественный смысл, тестовые задания живут внутри него: доска для system design, live‑кодинга, и.т.д. Без VPN они просто не открываются. Побочный плюс — задания не утекают в паблик и не индексируются.
Дополнительные требования для этого решения:
-
Никакого стороннего софта у кандидата, только встроенный в ОС клиент. Соответственно ограничиваемся IKEv2/L2TP.
-
Отдельная учетная запись на каждого кандидата, чтобы события в журнале однозначно относились к конкретному интервью. После встречи доступ удаляется.
Архитектура
Кандидат и интервьюер подключаются к одному IKEv2-шлюзу. DNS кандидата уходит в AdGuard, HTTP к code‑server в nginx на loopback‑адресе, все остальное в интернет через NAT.
Шаг 1. IKEv2-сервер на strongSwan
Далее в примерах: 203.0.113.10 — IP‑адрес сервера, vpn.example.com — домен VPN, 10.10.10.0/24 — пул адресов клиентов. A‑запись vpn.example.com → 203.0.113.10 заведена заранее.
Выпуск сертификатов
В качестве примера используем Let’s Encrypt из коробки:
apt update && apt upgrade -y
apt install -y strongswan strongswan-pki libcharon-extra-plugins
iptables-persistent certbot
certbot certonly --standalone --key-type rsa --rsa-key-size 4096
-d vpn.example.com
cp /etc/letsencrypt/live/vpn.example.com/fullchain.pem /etc/ipsec.d/certs/vpn-cert.pem
cp /etc/letsencrypt/live/vpn.example.com/privkey.pem /etc/ipsec.d/private/vpn-key.pem
chmod 600 /etc/ipsec.d/private/vpn-key.pem
Не забудьте deploy‑hook в certbot, который копирует обновленный сертификат и делает ipsec reload, иначе через 3 месяца все тихо сломается.
Конфиг /etc/ipsec.conf
config setup
uniqueids=no
conn ikev2-vpn
auto=add
keyexchange=ikev2
type=tunnel
fragmentation=yes
forceencaps=yes
dpdaction=clear
dpddelay=300s
rekey=no
left=%any
leftid=@vpn.example.com
leftcert=vpn-cert.pem
leftsendcert=always
leftsubnet=0.0.0.0/0 # весь трафик клиента в туннель
right=%any
rightid=%any
rightsourceip=10.10.10.0/24
rightdns=203.0.113.10 # DNS = AdGuard
eap_identity=%identity
leftauth=pubkey
rightauth=eap-mschapv2
ike=aes256-sha256-modp2048,aes128-sha1-modp1024
esp=aes256-sha256,aes128-sha1
Ключевые строки для нашей задачи leftsubnet=0.0.0.0/0 (full tunnel, а не split) и rightdns. Первая заставляет клиента гнать весь трафик через сервер, вторая выдает ему наш DNS‑сервер.
StrongSwan выдает клиентам адреса из пула начиная с .1. Поэтому в rightdns проще всего указать публичный IP сервера — запросы к нему все равно идут внутри туннеля.
Учетные записи кандидатов
/etc/ipsec.secrets — по учетке на каждого кандидата:
: RSA vpn-key.pem
login : EAP "<passwd>"
Маршрутизация, NAT и firewall
Схема «плоская»: трафик клиентов просто маскарадится и выходит в интернет с IP самого сервера.
echo 'net.ipv4.ip_forward=1' > /etc/sysctl.d/99-vpn.conf
sysctl --system
# eth0 внешний интерфейс, см. `ip route | grep default`
iptables -t nat -A POSTROUTING -s 10.10.10.0/24 -o eth0 -j MASQUERADE
netfilter-persistent save
ufw allow 22/tcp
ufw allow 80/tcp # certbot и заглушка nginx
ufw allow 500/udp
ufw allow 4500/udp
ufw enable
systemctl restart strongswan-starter
ipsec statusall
Настройка клиента
|
Server address |
|
|
User authentication |
Username |
|
Username / Password |
выданные кандидату |
Альтернатива для mac — собрать .mobileconfig‑профиль на каждого кандидата: двойной клик, Install, готово.
Шаг 2. AdGuard: DNS, журнал и блокировки
AdGuard здесь делает три вещи: резолвит запросы клиентов, пишет их в Query Log и подменяет ответы для наших внутренних доменов (DNS rewrite, понадобится в шаге 3).
Установка
cd /opt
curl -s -S -L https://raw.githubusercontent.com/AdguardTeam/AdGuardHome/master/scripts/install.sh | sh -s -- -v
Порт 53 обычно занят systemd-resolved. Отключаем его и прописываем серверу обычные резолверы:
systemctl disable --now systemd-resolved
rm -f /etc/resolv.conf
printf 'nameserver 1.1.1.1nnameserver 8.8.8.8n' > /etc/resolv.conf
В мастере настройки (:3000) DNS‑сервер вешаем на 0.0.0.0:53, веб‑интерфейс на 0.0.0.0:3000. Дальше после расшифровки IPsec источником запроса будет адрес из пула, так что открываем 53 только для него:
ufw allow from 10.10.10.0/24 to any port 53 proto udp
ufw allow from 10.10.10.0/24 to any port 53 proto tcp
ufw allow from 10.10.10.0/24 to any port 3000 proto tcp # админка тоже только из VPN
Админку для интервьюеров тоже лучше держать за VPN или ходить через ssh‑туннель.
Проверка
Переподключаем VPN на клиенте и смотрим, какой сервер реально отвечает:
dig google.com | grep SERVER
# ;; SERVER: 203.0.113.10#53
Запросы должны появиться в Query Log с клиентом 10.10.10.x.
Upstream и фильтры
В Settings → DNS settings ставим upstream по DoH, например https://dns.cloudflare.com/dns-query и https://dns.google/dns-query. Рекламные фильтр‑листы можно не включать, нам нужна не чистка рекламы, а чистый журнал и точечные блокировки по ситуации. А вот срок хранения Query Log стоит урезать до суток: держать чужую историю запросов дольше, чем нужно, незачем.
Шаг 3. Live‑coding в code‑server, доступный только из VPN
На примере code‑server разберем задачу self‑hosted решения для live‑coding.
Похожую схему можно использовать и для других песочниц для проверки разных навыков (system design, db, etc).
DNS rewrite и loopback‑адрес
Чтобы *.lab.example.com открывался только из VPN, вешаем на lo адрес из пула, который strongSwan никому не выдаст, 10.10.10.254:
cat >/etc/systemd/system/lab-loopback-ip.service <<'EOF'
[Unit]
Description=Loopback IP for lab services
After=network.target
[Service]
Type=oneshot
ExecStart=/bin/sh -c 'ip addr add 10.10.10.254/32 dev lo 2>/dev/null || true'
ExecStop=/bin/sh -c 'ip addr del 10.10.10.254/32 dev lo 2>/dev/null || true'
RemainAfterExit=yes
[Install]
WantedBy=multi-user.target
EOF
systemctl daemon-reload && systemctl enable --now lab-loopback-ip
Затем в AdGuard (Filters → DNS rewrites) добавляем *.lab.example.com → 10.10.10.254. Клиент в VPN получает внутренний адрес и добирается до него по туннелю. Для всех остальных учебный домен либо не резолвится, либо ведет на публичный IP, где nginx отдает 403.
code‑server в Docker
code‑server это VS Code (сборка OSS) в браузере. Кандидат получает привычную IDE с терминалом, а интервьюер может открыть тот же адрес и следить за кодом. Встроенных AI‑помощников в нем нет.
Нужные языки и расширения ставим заранее, на этапе сборки образа:
# /opt/lab/Dockerfile
FROM codercom/code-server:latest
USER root
RUN apt-get update
&& apt-get install -y --no-install-recommends python3 python3-pip
&& rm -rf /var/lib/apt/lists/*
USER coder
RUN code-server --install-extension ms-python.python
И compose:
# /opt/lab/docker-compose.yml
services:
code-server:
build: .
restart: unless-stopped
environment:
- PASSWORD=${CODE_SERVER_PASSWORD} # из .env, новый на каждое интервью
command:
- --auth=password
- --disable-telemetry
- --disable-update-check
- --disable-workspace-trust
volumes:
- ./workspace:/home/coder/project
ports: ["127.0.0.1:8443:8080"]
dns: [10.10.10.254] # DNS контейнера тоже через AdGuard
extra_hosts: # маркетплейс расширений в никуда
- "open-vsx.org:0.0.0.0"
- "marketplace.visualstudio.com:0.0.0.0"
Зачем dns и extra_hosts. Расширения и терминал code‑server работают на сервере, а не на машине кандидата. Если кандидат поставит AI‑расширение или сделает curl к API из терминала, эти запросы пойдут с сервера и в журнал клиента 10.10.10.x не попадут. Поэтому DNS контейнера тоже заводим в AdGuard (в Query Log он будет виден как клиент 172.x.x.x), а маркетплейс расширений отрезаем. ufw должен пускать к AdGuard и Docker‑сети:
ufw allow from 172.16.0.0/12 to any port 53 proto udp
ufw allow from 172.16.0.0/12 to any port 53 proto tcp
Если задаче не нужен интернет (pip install и тому подобное), новые исходящие соединения из контейнеров можно закрыть совсем:
iptables -I DOCKER-USER -s 172.16.0.0/12 -m conntrack --ctstate NEW -j DROP
nginx
Общие заголовки прокси вынесем в сниппет /etc/nginx/snippets/lab-proxy.conf. Строки с Upgradeобязательны: code‑server живет на WebSocket.
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
И сам конфиг:
# /etc/nginx/sites-available/lab
server {
listen 10.10.10.254:80;
server_name code.lab.example.com;
client_max_body_size 50m;
location / {
proxy_pass http://127.0.0.1:8443;
include snippets/lab-proxy.conf;
}
}
# Все, что пришло на публичный IP, 403
server {
listen 203.0.113.10:80 default_server;
server_name _;
return 403;
}
Кандидат открывает http://code.lab.example.com/?folder=/home/coder/project, а пароль получает в начале интервью.
Чистый стенд перед каждым интервью
Заготовка задачи лежит в /opt/lab/template. Перед встречей скрипт восстанавливает рабочую папку и выдает новый пароль, чтобы следующий кандидат не увидел чужое решение:
#!/bin/sh
# /opt/lab/reset.sh
set -e
cd /opt/lab
PASS=$(openssl rand -base64 12)
echo "CODE_SERVER_PASSWORD=$PASS" > .env
docker compose stop code-server
rm -rf workspace && cp -a template workspace && chown -R 1000:1000 workspace
docker compose up -d code-server
echo "Пароль для кандидата: $PASS"
Часть функций VS Code (буфер обмена, webview, preview Markdown) требует secure context, и code‑server предупреждает об этом. Домен резолвится в приватный адрес, поэтому HTTP-01 не сработает, выпускайте wildcard *.lab.example.com через DNS-01.
Как проводить интервью
Инфраструктура это половина дела. Вторая половина — процесс для HR и интервьюера.
До встречи HR заранее предупреждает кандидата: задания живут во внутренней среде, на время интервью нужно подключение к корпоративному VPN, а действия кандидата в это время могут логироваться.
Во время встречи HR или интервьюер держит открытым Query Log с фильтром по клиенту 10.10.10.x и обращает внимание на домены:
-
AI‑чатов и интервью‑копилотов, в том числе их API и сервисов распознавания речи;
-
remote desktop и screen sharing, кроме самого созвона;
-
мессенджеров и pastebin‑подобных сервисов;
-
поисковиков, если всплески совпадают с моментами, когда звучит новый вопрос.
Блокировка на лету
Если подозрение серьезное, домен можно заблокировать прямо из Query Log (⋮ → Block), через Filters → Blocked services (там есть готовые переключатели для популярных AI‑сервисов и мессенджеров)
или списком в Custom filtering rules:
||chatgpt.com^
||openai.com^
||claude.ai^
||gemini.google.com^
||perplexity.ai^
||*ghost*gpt*^
||telegram.org^
||t.me^
Любая блокировка сработает не мгновенно, а когда истечет DNS‑кэш в ОС и браузере кандидата, а это может занять несколько минут.
Если после блокировки «беглость» кандидата заметно пропадает, это наблюдение, а не доказательство. Ковровые блокировки целых категорий «на всякий случай» делать не стоит. Честный кандидат может просто подсмотреть синтаксис в документации.
Нюансы использования
Предложенное решение это не детектор волков, а еще один сигнал для интервьюера. И его тоже можно обойти.
|
Способ обхода |
Видно ли в журнале |
Что можно сделать |
|
Помощник на втором устройстве (телефон, ноутбук) |
Нет |
Ничего сетевого; только формат вопросов |
|
DNS‑over‑HTTPS в браузере или приложении |
Виден только запрос к DoH‑провайдеру |
Замечать сам факт; при желании блокировать известные DoH‑домены и |
|
iCloud Private Relay, сторонний VPN поверх нашего |
Видны домены самого сервиса |
Это тоже сигнал |
|
Захардкоженные IP и собственный резолвер у помощника |
Нет |
Смотреть уже не DNS, а conntrack/NetFlow на сервере |
|
Разделение туннеля (split tunnel) на клиенте |
Частично |
Понять и простить |
Технические ограничения стенда:
-
Весь трафик кандидата идет через сервер, включая сам видеозвонок. Далекий или слабый сервер даст задержку и будет шакалить звук.
-
Трафик из code‑server виден хуже. Запросы из терминала и расширений code‑server идут с сервера и в Query Log видны одним общим клиентом Docker‑сети. Надежнее всего закрыть контейнеру исходящий интернет целиком.
Этика и закон
Речь идет о сборе данных о сетевой активности человека, так что тут нет простора для действий втихую.
-
Кандидат заранее и прямо уведомляется о том, что его активность может логироваться, и дает согласие. Альтернатива для несогласных это очная встреча или другой формат.
-
Просите установить и включить VPN только на время интервью: все, что до и после, вас не касается.
-
Храните журнал минимальное время, доступ к админке только у тех, кто проводит интервью.
-
Не принимайте решение по одному DNS‑запросу. Системные службы и фоновые приложения сами ходят куда угодно, в том числе к AI‑доменам.
-
Перед реальным применением проверьте с юристом требования к обработке персональных данных в вашей юрисдикции (152-ФЗ, GDPR и.т.п.).
Итоги
Один стенд, четыре open‑source компонента и вечер настройки дают интервьюеру то, чего не дает демонстрация экрана: журнал сетевых обращений кандидата в реальном времени.
Это не панацея, но еще один инструмент, с котором есть возможность повысить качество принимаемых решений по найму. Всегда нужно помнить, что второй телефон на столе может свести все на нет.
А как вы боретесь с AI‑помощниками?
Автор: MSoldatkin

