Skip to content

Commit 1f0f80c

Browse files
committed
feat(safety): probe browser that clicks, and sibling links in the bundle
The investigator judged what a button does from script tags, because its only render was a one-shot snapshot. A fake video player wired to ad networks became "designed to trigger malicious downloads" without anyone clicking it. The new browser service loads the page in our own Chromium, clicks its prominent controls and records pop-ups, navigations, downloads, dialogs, clipboard writes and notification prompts; fetch_page reports that under Observed behaviour and the prompt makes it the only source for what a click does. Small hosts also get every sibling link resolved into the bundle, so nine links from one creator in three minutes reads as one campaign. Cloudflare snapshots stay as the fallback.
1 parent 31d6e82 commit 1f0f80c

20 files changed

Lines changed: 1631 additions & 94 deletions

File tree

.github/workflows/deploy.yml

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,29 @@ jobs:
7878
cache-to: type=gha,mode=max
7979
platforms: linux/amd64
8080

81+
# The safety tier's probe browser ships beside the app under the same
82+
# tags, so one IMAGE_TAG names a working pair.
83+
- name: Extract metadata (browser)
84+
id: meta_browser
85+
uses: docker/metadata-action@v6
86+
with:
87+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-browser
88+
tags: |
89+
type=semver,pattern={{version}}
90+
type=sha,prefix=,format=short
91+
type=raw,value=latest,enable=${{ github.event_name == 'release' }}
92+
93+
- name: Build and push (browser)
94+
uses: docker/build-push-action@v7
95+
with:
96+
context: browser
97+
push: true
98+
tags: ${{ steps.meta_browser.outputs.tags }}
99+
labels: ${{ steps.meta_browser.outputs.labels }}
100+
cache-from: type=gha,scope=browser
101+
cache-to: type=gha,mode=max,scope=browser
102+
platforms: linux/amd64
103+
81104
deploy:
82105
needs: build
83106
# run when build succeeded OR was skipped (rollback path)
@@ -125,7 +148,10 @@ jobs:
125148
}
126149
127150
set_tag "${IMAGE_TAG}"
128-
docker compose --env-file .env -f docker-compose.prod.yml pull app
151+
docker compose --env-file .env -f docker-compose.prod.yml pull app browser
152+
# The probe browser is stateless and health-gated by the worker's
153+
# fallback to Cloudflare snapshots, so it swaps first, ungated.
154+
docker compose --env-file .env -f docker-compose.prod.yml up -d --no-deps browser
129155
docker compose --env-file .env -f docker-compose.prod.yml up -d --no-deps app
130156
131157
if ! wait_healthy spoo_app; then

.github/workflows/image.yml

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,3 +74,25 @@ jobs:
7474
cache-from: type=gha
7575
cache-to: type=gha,mode=max
7676
platforms: linux/amd64
77+
78+
# The safety tier's probe browser ships beside the app under the same
79+
# tags, so one IMAGE_TAG names a working pair.
80+
- name: Extract metadata (browser)
81+
id: meta_browser
82+
uses: docker/metadata-action@v6
83+
with:
84+
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-browser
85+
tags: |
86+
type=raw,value=edge,enable=${{ github.event_name == 'push' }}
87+
type=sha,prefix=,format=short
88+
89+
- name: Build and push (browser)
90+
uses: docker/build-push-action@v7
91+
with:
92+
context: browser
93+
push: true
94+
tags: ${{ steps.meta_browser.outputs.tags }}
95+
labels: ${{ steps.meta_browser.outputs.labels }}
96+
cache-from: type=gha,scope=browser
97+
cache-to: type=gha,mode=max,scope=browser
98+
platforms: linux/amd64

browser/Dockerfile

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
FROM mcr.microsoft.com/playwright/python:v1.62.0-noble
2+
3+
RUN pip install --no-cache-dir playwright==1.62.0 starlette==1.6.0 uvicorn==0.52.4
4+
5+
WORKDIR /srv
6+
COPY probe.py /srv/probe.py
7+
8+
USER pwuser
9+
EXPOSE 8011
10+
CMD ["uvicorn", "probe:app", "--host", "0.0.0.0", "--port", "8011", "--no-access-log"]

0 commit comments

Comments
 (0)