Skip to the content.

Web & Email Evidence — Findings, Methods & Reproduction

The rest of this repository proves what the app does (static analysis of the decompiled APK, network captures). This document covers the surrounding evidence layer gathered to show two things the code alone cannot:

  1. How the public is funneled to the app — a Dutch municipality presents it as “the official TT Festival App” and hands out the link, while the app and its data processing are actually run by a third party (the organising stichting / Appmiral / Crowd Connected).
  2. That the responsible parties were formally notified — and, in writing, admitted the very US data transfer the app’s own privacy policy denies.

All of it is captured as timestamped, reproducible, checksum-verified snapshots under ../evidence/. This page is the analyst’s index to those snapshots: the findings, the techniques used to capture them, the prerequisites, and how to reproduce each one. For the human-browsable gallery open ../evidence/index.html; to add evidence see ../evidence/CONTRIBUTING.md.


The evidence chain

flowchart TD
    A["Gemeente Assen — official municipal website<br/>www.assen.nl/heeft-u-nog-vragen<br/>'Download de officiële TT Festival App via app.ttfestival.nl'"]
    A -->|301 redirect| B["app.ttfestival.nl<br/>(Apache)"]
    B -->|301 redirect| C["ttfestival2026.deeplink.appmiral.com<br/>Appmiral · AWS API Gateway / CloudFront — a third party"]
    C --> D["Google Play<br/>com.appmiral.ttfestival"]
    C --> E["Apple App Store<br/>id 6744364695"]
    C --> F["app.appmiral.com/ttfestival2026/privacy-policy<br/>'All data stored within the EU (AWS eu-west-1)'<br/>English-only · no Dutch version"]
    F -. contradicted by .-> G["This repo's finding:<br/>location streamed to AWS us-east-1 (USA)<br/>see docs/crowd-connected.md"]
    H["ttfestival.nl — event website<br/>app-announcement post → app.ttfestival.nl"] --> B
    style G fill:#fde2e2,stroke:#c0392b

The municipality presents the app under its own banner; the operator and the data processing are a separate party. Each snapshot’s README.md holds the full reasoning and verbatim quotes.


Findings

IDs: WE-# = web evidence, EM-# = email/correspondence evidence. Each finding links to the captured artifact that proves it.

ID Finding Proven by Status
WE-1 Gemeente Assen calls it “de officiële TT Festival App” and distributes the download link on an official www.assen.nl page. assen.nl_.../ — screenshots/03_heeft-u-nog-vragen_fullpage.png, rendered-dom/, raw/ Captured
WE-2 The “official app” link is operated by a third party: app.ttfestival.nl 301-redirects to ttfestival2026.deeplink.appmiral.com (Appmiral / AWS), not Gemeente Assen. appmiral.com_.../logs/redirect-chain_app.ttfestival.nl.txt Captured
WE-3 The privacy policy’s “all data stored in the EU (eu-west-1)” claim is captured verbatim — directly testable against this repo’s us-east-1 finding. appmiral.com_.../raw/privacy-policy.html + docs/crowd-connected.md Captured + contradicted
WE-4 The privacy policy is English-only — no Dutch version (no hreflang, no language negotiation). GDPR Art. 12(1) transparency concern for a Dutch audience. appmiral.com_.../logs/language-check_privacy-policy.txt + privacy-policy-analysis.md §8 Captured + verified
WE-5 TT Festival house rules: entering the terrain = automatic consent to image/audio recording, used both for “openbare orde en veiligheid” and “promotionele doeleinden”. ttfestival.nl_.../raw/info-huisregels.html + screenshot 03 Captured
WE-6 The event site ttfestival.nl is the on-ramp to the tracking chain — its app-announcement post sends the public to app.ttfestival.nl. ttfestival.nl_.../raw/app-announcement.html (174/174 sitemap pages mirrored) Captured
EM-1 Written admission of the US transfer. The organisation, relaying Crowd Connected, confirms location data is processed via AWS us-east-1 (Virginia, USA) — the exact point the policy denies. email-correspondence/03_ttfestival_privacyonderzoek/04_*.txt Admitted in writing
EM-2 The municipality is on notice and acknowledged it. A municipal safety adviser replies “…dan moeten wij hier zeker iets mee” and undertakes to escalate. email-correspondence/02_gemeente-assen_privacy-zorgen/03_*.txt Acknowledged
EM-3 Controllership + access request. A formal GDPR Art. 15 access request and the Art. 4(7)/24 controllership question are put to Stichting TT Festival. email-correspondence/03_ttfestival_privacyonderzoek/05_*.txt On record

WE-1 · Municipal endorsement (assen.nl)

The decisive line is on /heeft-u-nog-vragen, captured verbatim:

“Meer weten over het TT Festival? Download de officiële TT Festival App via app.ttfestival.nl (externe link) of neem contact op met de organisatie via info@ttfestival.nl.”

On an official www.assen.nl government page this reads as a municipal endorsement of the app, while it is hard for a reader to tell that the app is not run by the municipality. Three municipal pages were frozen (/, /tt, /heeft-u-nog-vragen) so the endorsement can be cited and reproduced even if the page is later edited. Full reasoning: assen.nl_2026-06-26_231242/README.md.

WE-2 · The redirect chain proves a third-party operator

https://app.ttfestival.nl/
  └─ 301 Moved Permanently  (Server: Apache)
     → https://ttfestival2026.deeplink.appmiral.com/download   (AWS API Gateway / CloudFront)
        → Google Play com.appmiral.ttfestival / Apple App Store id 6744364695

The link the municipality calls “the official app” lands on Appmiral / AWS infrastructure. (Note: the deeplink page returns 403 to a bare HEAD — AWS API Gateway MissingAuthenticationTokenException — and serves real HTML only to a browser-like GET; see the technique below.)

WE-3 / WE-4 · The captured privacy policy

The policy is the primary source for two comparisons made elsewhere in the repo: its EU-only storage claim (§4/§7) tested against the us-east-1 streaming finding (crowd-connected.md), and its English-only delivery as a GDPR Art. 12 issue (privacy-policy-analysis.md §8). Both are frozen in raw/privacy-policy.html + rendered-dom/ + a screenshot, and the language check is a re-runnable log.

WE-5 / WE-6 · The event website (ttfestival.nl)

info/huisregels/ states verbatim that recordings “kunnen worden gebruikt in het kader van handhaving van de openbare orde en veiligheid, maar ook voor promotionele doeleinden” and that “door het terrein te betreden geef je toestemming”. The whole site (174/174 sitemap pages) was mirrored so quotes can’t be dismissed as cherry-picked; coverage is proven in meta/coverage-report.txt.

EM-1 / EM-2 / EM-3 · The correspondence

The single most important item: the operator concedes in writing the US (us-east-1) processing the privacy policy denies, and a municipal adviser acknowledges the concern. The full timeline, the three threads, and the “why this is evidence” framing are in email-correspondence/README.md. Every message is preserved byte-exact as the original .eml.


Prerequisites

The captures were produced on Microsoft Windows 11 Pro with the following tools. Exact versions are recorded in each snapshot’s logs/tool-versions.txt.

Tool Version used Purpose Install
curl 8.20.0 (Schannel) Exact raw HTTP responses + headers; redirect chains; emoji-slug page recovery Bundled with Windows 10/11 (curl.exe)
GNU Wget 1.21.4 (mingw32) Browsable offline full-site mirror with assets winget install JernejSimoncic.Wget
Google Chrome 149.x (driven via Chrome DevTools) JavaScript-rendered DOM + full-page/viewport screenshots https://www.google.com/chrome/
PowerShell 5.1 / 7.x Orchestration, Get-FileHash integrity manifests Built into Windows
Python 3.11.9 Parse/extract .eml email threads (email + BytesParser) https://www.python.org/
Node.js v24.12.0 Deduped download of Next.js-proxied images (assen.nl assets-extra/) https://nodejs.org/
git 2.53.0 Version control; byte preservation via .gitattributes https://git-scm.com/
gh (GitHub CLI) current Create the private companion repo, push https://cli.github.com/

A single consistent desktop User-Agent is used across curl, wget and Chrome so every artifact reflects the same rendering path:

Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36

Techniques used

T1 · Exact raw capture (curl)

The verbatim, unmodified server response and its response headers — the forensic ground truth:

$UA = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/126.0.0.0 Safari/537.36"
curl.exe -s -A $UA -D raw/home.headers.txt -o raw/home.html "https://www.assen.nl/"

The saved bytes match the Content-Length in the headers, so the capture is self-verifying.

T2 · Rendered DOM + screenshots (Chrome DevTools)

Modern sites hydrate with JavaScript, so the raw HTML isn’t what a visitor sees. Chrome was driven at a 1440×900 desktop viewport to (a) dump the DOM after hydration and (b) save a full-page PNG and a viewport PNG. The DOM dump is '<!DOCTYPE html>\n' + document.documentElement.outerHTML written to rendered-dom/<page>.html. Every screenshot was opened and confirmed non-blank before being trusted.

T3 · Full-site mirror (wget) seeded from the sitemap

# whole site, seeded from sitemap_index.xml -> 174 page URLs
wget -r -l inf -p -k -E -e robots=off --domains=ttfestival.nl --no-check-certificate `
     -U $UA -i meta/sitemap-urls.txt "https://ttfestival.nl/" `
     -P mirror -o wget-mirror.log

Completeness is then cross-checked against the sitemap (URL → local file, URL-decoding %xx) and written to meta/coverage-report.txt. Wget gotchas worth knowing: the server sends no Last-Modified (so -N can’t resume; delete mirror/ before a fresh run to avoid .1/.2 duplicates), and link-conversion (-k) only runs after all downloads finish.

T4 · Emoji / multi-byte slug recovery (curl)

GNU Wget on Windows cannot write filenames containing emoji or other multi-byte characters (e.g. the slug “Nieuwe TT Festival app 📱”). Eight live (HTTP 200) sitemap pages were skipped by wget for this reason and recovered directly with curl into mirror/.../_recovered/ under short, stable filenames, with a filename → original-URL map. This is how coverage reached 174/174.

T5 · Email .eml parsing & extraction (Python)

The 10 messages were parsed with Python’s email library (BytesParser(policy=policy.default)), sorted by the Date header, then for each message: the raw .eml is copied byte-exact to raw/, a readable NN_*.txt rendering is written (From/To/Cc/Date/Subject/Message-ID/In-Reply-To/Attachments + plain-text body, falling back to stripped HTML), and attachments are saved to NN_attachments/. The imported .eml files were verified byte-identical to the originals via SHA-256.

T6 · Integrity manifests (SHA-256)

Every evidence folder ships a SHA256SUMS.txt (SHA-256 of every file except the folder’s own README.md, SHA256SUMS.txt, mirror-manifest.sha256). Generate with the repo helper, which writes LF/UTF-8/forward-slash output so verification is identical on every OS:

pwsh tools/gen-sha256sums.ps1 -Path evidence/<host>_<timestamp>

T7 · Two-repo split + mirror cross-attestation

The heavy browsable wget mirrors (the ttfestival.nl clone alone is ~1.16 GB) would permanently bloat the public history and re-host large third-party sites, so they live in the private companion repo GraafG/tt-festival-app-evidence. Each public snapshot keeps a mirror-manifest.sha256 listing the SHA-256 of every moved file; the private snapshot ships an identical SHA256SUMS.txt. The two lists are byte-for-byte equal, so the public repo independently attests to exactly what the private mirror contains.

T8 · Byte preservation in git

evidence/.gitattributes sets * -text -diff, so git stores and checks out the exact bytes (no line-ending/encoding normalization) on every platform — which is what keeps the SHA-256 manifests valid after cloning.


Reproduce — quickest paths


Chain of custody / caveats


Artifact index

Snapshot What it captures README
evidence/assen.nl_2026-06-26_231242/ 3 municipal pages (WE-1) README
evidence/appmiral.com_2026-06-26_232241/ deeplink + privacy policy (WE-2/3/4) README
evidence/ttfestival.nl_2026-06-26_234242/ full event site, 174/174 pages (WE-5/6) README
evidence/email-correspondence/ 10 emails, 3 threads (EM-1/2/3) README

See also: ../evidence/README.md (top-level index), ../evidence/index.html (human gallery), ../evidence/CONTRIBUTING.md (how to add evidence).