File size: 47,757 Bytes
149d2bd
8762842
 
 
 
7d1e37c
 
8762842
 
 
 
 
 
 
 
 
 
 
 
 
 
7d1e37c
8762842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d1e37c
8762842
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
7d1e37c
 
 
 
8762842
7d1e37c
8762842
 
 
 
 
 
 
 
 
 
 
 
 
7d1e37c
149d2bd
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>EU hosted images check — check image hosts in browser</title>
<meta name="description" content="This free tool finds images, avatars, fonts, and embeds in documentation that are hotlinked from non-EU hosts.">
<style>
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
.wrap{max-width:960px;margin:0 auto;padding:28px 18px 60px}h1{font-size:26px;margin:0 0 6px}.lede{color:#4a403a;margin:0 0 16px}
textarea{width:100%;min-height:260px;font:13px/1.45 ui-monospace,Menlo,Consolas,monospace;padding:12px;border:1px solid #d9d0c4;border-radius:8px;box-sizing:border-box}
.bar{display:flex;gap:10px;align-items:center;margin:12px 0}button{font:600 15px system-ui;padding:10px 18px;border:0;border-radius:8px;background:#1a1512;color:#fff;cursor:pointer}
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
table{border-collapse:collapse;width:100%;font-size:14px;margin-top:10px}th,td{text-align:left;padding:8px 10px;border-bottom:1px solid #eee;vertical-align:top}th{font-size:12px;letter-spacing:.06em;text-transform:uppercase;color:#7a6e65}
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
.note{font-size:13px;color:#7a6e65;margin-top:18px}#paid{margin-top:22px;padding:14px 16px;border:1px solid #e2d8cb;border-radius:10px;background:#fffdfa}
</style>
<script data-grow-initializer="">!(function(){window.growMe||((window.growMe=function(e){window.growMe._.push(e);}),(window.growMe._=[]));var e=document.createElement("script");(e.type="text/javascript"),(e.src="https://faves.grow.me/main.js"),(e.defer=!0),e.setAttribute("data-grow-faves-site-id","U2l0ZTphZjljMzgyMC0yMmU5LTRhYjgtYTIxNS03OTRhMGY1N2ExYzM=");var t=document.getElementsByTagName("script")[0];t.parentNode.insertBefore(e,t);})();</script>
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/image-host-transfer-lint"></head>
<body>
<div class="wrap">
<h1>EU hosted images check — check image hosts in browser</h1>
<p class="lede">Finds images, avatars, fonts and embeds in your docs that are hotlinked from non-EU hosts, so EU visitor IPs leave the EEA before anyone consents. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false"># Deploying the Reporting Service

Internal handbook page, published at docs.example.eu for customers in Germany,
Austria and the Netherlands.

&lt;link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Inter:wght@400;700&amp;display=swap"&gt;

## Who maintains this page

&lt;img src="https://secure.gravatar.com/avatar/9c1f3b2a7d4e5f60?s=96" width="48" alt="Maintainer avatar"&gt;

Questions go to the platform team.

## Step 1 - open the deploy dashboard

![Deploy dashboard](https://i.imgur.com/Qk3vT9m.png)

The dashboard shows the last ten releases and the rollback button.

## Step 2 - check the queue depth

![Queue depth panel](https://i.loli.net/2026/03/11/qdepth-panel.png)

If the depth stays above 500 for more than five minutes, stop the rollout.

## Build status

![build](https://img.shields.io/github/actions/workflow/status/example/reporting/ci.yml)

## Walkthrough

&lt;iframe width="560" height="315" src="https://www.youtube.com/embed/4rTcPq2s1kA" title="Deploy walkthrough"&gt;&lt;/iframe&gt;

## Copy-to-clipboard helper used by this page

&lt;script src="https://cdn.jsdelivr.net/npm/clipboard@2.0.11/dist/clipboard.min.js"&gt;&lt;/script&gt;

## Rollback

Run `make rollback` and confirm in the dashboard. See [the runbook](./runbook.md)
and [the escalation ladder](./escalation.md) for the on-call path.</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
<div id="out" aria-live="polite"></div>
<div id="paid"><strong>Free here:</strong> Scan the docs you have open and name every asset URL that sends an EU visitor's IP to a host outside the EEA, with the host, the country and the rule behind it.. <strong>In the editor:</strong> Workspace-wide export: an Art. 30 transfer register (CSV + Markdown) plus a self-host rewrite map for every file, per team seat. — that part asks for a licence key.</div>
<div id="buy" style="margin:14px 0 0;padding:14px 16px;border:1px solid #d9dde3;border-radius:10px;line-height:1.55">
  <a href="https://getreadystack.com/api/buy/cl/polar_cl_CizB7BOiQnrCD9uAsBxpds5fDtpbi0BD1xPKa3GqTUD" id="buylink"
     style="display:inline-block;font-weight:700;padding:10px 16px;border-radius:8px;background:#1b62d6;color:#fff;text-decoration:none">Get the full version — $29 once</a>
  <div style="margin-top:8px;font-size:14px;color:#444">$29 once · one licence key per person or team seat · LG M&uuml;nchen I, 20 O 14144/19 (2022-01-20) awarded EUR 100 in damages to one visitor for one Google Fonts call on one page.</div>
  <div style="margin-top:6px;font-size:13px;color:#666">This page loads no third-party asset until you click: the checkout script is fetched only when you press the button — the same click-to-load fix this linter asks for.</div>
</div>
<script>
(function () {
  var a = document.getElementById('buylink'), loaded = false;
  if (!a) return;
  a.addEventListener('click', function (ev) {
    if (loaded) return;                      // second click goes to the loaded overlay
    ev.preventDefault();
    loaded = true;
    var s = document.createElement('script');
    s.src = 'https://cdn.jsdelivr.net/npm/@polar-sh/checkout@latest/dist/embed.global.js';
    s.setAttribute('data-auto-init', '');
    s.onload = function () { a.click(); };
    s.onerror = function () { window.location.href = a.href; };
    document.body.appendChild(s);
  });
})();
</script>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var IHT_RULES = [
  {
    "check": "google-fonts-us",
    "kind": "host",
    "sev": "error",
    "host": "fonts.googleapis.com / fonts.gstatic.com",
    "country": "United States",
    "pattern": "fonts\\.(?:googleapis|gstatic)\\.com",
    "msg": "Google Fonts is pulled from a US host, so the visitor's IP is transferred before the page paints. LG Munchen I, 20 O 14144/19 (2022-01-20) awarded EUR 100 damages to one visitor for exactly this. Fix: download the .woff2 next to your docs and serve it yourself.",
    "constants": {
      "law": "GDPR Art. 44-49 (transfers to a third country) + Art. 6(1) (no legal basis for the IP disclosure)",
      "ruling": "LG Munchen I, 20 O 14144/19, 2022-01-20, EUR 100 per visitor",
      "eea_note": "EEA = EU 27 + Iceland, Liechtenstein, Norway. The US has an adequacy decision (EU-US Data Privacy Framework, 2023-07-10) but only for certified recipients; a CDN edge is not certified for you by default.",
      "today_default": "2026-09-21",
      "scope": "asset URLs inside .md, .mdx and .html docs; fenced code blocks are treated as examples and skipped"
    }
  },
  {
    "check": "gravatar-us",
    "kind": "host",
    "sev": "error",
    "host": "gravatar.com",
    "country": "United States",
    "pattern": "(?:secure\\.|www\\.)?gravatar\\.com",
    "msg": "A Gravatar avatar is loaded from Automattic in the US. The request carries the reader's IP plus the MD5 of an author e-mail. Fix: commit the avatar image into the repo and point at the local path."
  },
  {
    "check": "imgur-us",
    "kind": "host",
    "sev": "error",
    "host": "imgur.com",
    "country": "United States",
    "pattern": "(?:i\\.|www\\.)?imgur\\.com",
    "msg": "This screenshot is hotlinked from Imgur (US). Every reader of the page is announced to Imgur, and Imgur may delete or replace the file without telling you. Fix: move the image into docs/img/ and reference it relatively."
  },
  {
    "check": "smms-cn",
    "kind": "host",
    "sev": "error",
    "host": "sm.ms / i.loli.net",
    "country": "China",
    "pattern": "(?:\\bsm\\.ms\\b|i\\.loli\\.net)",
    "msg": "This image sits on SM.MS (China). China has no EU adequacy decision, so this is an Art. 44 transfer with no safeguard on file. Fix: re-upload to a host inside the EEA or into the repo itself.",
    "constants": {
      "no_adequacy": "China, Russia and India have no EU adequacy decision as of 2026-09-21; an SCC set plus a transfer impact assessment would be required."
    }
  },
  {
    "check": "imgbb-postimg-us",
    "kind": "host",
    "sev": "error",
    "host": "ibb.co / postimg.cc",
    "country": "United States",
    "pattern": "(?:\\bibb\\.co\\b|postimg\\.cc)",
    "msg": "Free image hosts ImgBB / PostImage are US-run and give you no data processing agreement at all. Fix: host the file yourself; these links also rot once the free bucket is recycled."
  },
  {
    "check": "github-raw-us",
    "kind": "host",
    "sev": "warn",
    "host": "raw.githubusercontent.com / user-images.githubusercontent.com",
    "country": "United States",
    "pattern": "(?:raw|user-images)\\.githubusercontent\\.com",
    "msg": "The asset is served by GitHub in the US. Fine inside GitHub, but on your own published docs page it is an uncontrolled third-country transfer. Fix: copy the file into the site build instead of hotlinking it."
  },
  {
    "check": "jsdelivr-cdn",
    "kind": "host",
    "sev": "error",
    "host": "cdn.jsdelivr.net",
    "country": "United States / China edges",
    "pattern": "cdn\\.jsdelivr\\.net",
    "msg": "jsDelivr resolves to US and Chinese edge nodes depending on the reader, so you cannot name the receiving country in your Art. 30 record. Fix: install the package and bundle it, or self-host the single file."
  },
  {
    "check": "unpkg-cdn",
    "kind": "host",
    "sev": "error",
    "host": "unpkg.com",
    "country": "United States",
    "pattern": "unpkg\\.com",
    "msg": "unpkg.com is a US CDN in front of npm. Loading a script from it hands the reader's IP and referrer to a party you have no contract with. Fix: vendor the file into your assets folder."
  },
  {
    "check": "cdnjs-cloudflare",
    "kind": "host",
    "sev": "error",
    "host": "cdnjs.cloudflare.com",
    "country": "United States",
    "pattern": "cdnjs\\.cloudflare\\.com",
    "msg": "cdnjs is operated by Cloudflare (US). Subresource integrity does not help here: the transfer happens with the request, not with the payload. Fix: serve the library from your own origin."
  },
  {
    "check": "china-object-storage",
    "kind": "host",
    "sev": "error",
    "host": "Aliyun OSS / Tencent COS / Qiniu",
    "country": "China",
    "pattern": "(?:oss-cn-[a-z0-9-]+\\.aliyuncs\\.com|cos\\.ap-[a-z0-9-]+\\.myqcloud\\.com|[a-z0-9-]+\\.clouddn\\.com)",
    "msg": "A Chinese object-storage bucket is serving this asset. Typical when an editor upload plugin was left on its default backend. Fix: repoint the upload backend at an EEA bucket and rewrite the published links."
  },
  {
    "check": "youtube-embed",
    "kind": "host",
    "sev": "error",
    "host": "youtube.com",
    "country": "United States",
    "pattern": "(?:youtube\\.com/embed|youtu\\.be/)",
    "msg": "A plain YouTube embed sets cookies and profiles the reader on load. Fix: use youtube-nocookie.com behind a click-to-load placeholder, so nothing leaves until the reader asks for the video."
  },
  {
    "check": "google-tag-analytics",
    "kind": "host",
    "sev": "error",
    "host": "googletagmanager.com / google-analytics.com",
    "country": "United States",
    "pattern": "(?:googletagmanager\\.com|google-analytics\\.com)",
    "msg": "Analytics fires on page load, before any consent choice exists on a docs page. Fix: gate it behind consent, or use a self-hosted counter that stores no identifier."
  },
  {
    "check": "badge-host",
    "kind": "host",
    "sev": "warn",
    "host": "img.shields.io / badgen.net",
    "country": "United States",
    "pattern": "(?:img\\.shields\\.io|badgen\\.net)",
    "msg": "Build badges are live images: each reader pings the badge host. Harmless in a repo, a transfer on a published page. Fix: commit the rendered .svg and refresh it in CI."
  },
  {
    "check": "typekit-fonts",
    "kind": "host",
    "sev": "error",
    "host": "use.typekit.net / p.typekit.net",
    "country": "United States",
    "pattern": "(?:use|p)\\.typekit\\.net",
    "msg": "Adobe Fonts is loaded from a US host for every reader, the same pattern the Munich Google Fonts ruling punished. Fix: switch to a licensed self-hosted webfont file."
  },
  {
    "check": "comment-widget",
    "kind": "host",
    "sev": "error",
    "host": "disqus.com / utteranc.es / giscus.app",
    "country": "United States",
    "pattern": "(?:disqus\\.com|utteranc\\.es|giscus\\.app)",
    "msg": "A comment widget loads third-party script and, for Disqus, tracking cookies before a word is typed. Fix: load it only after an explicit click, and name the processor in your privacy notice.",
    "constants": {
      "art30": "Art. 30(1)(e) asks you to record transfers to third countries and the safeguards used; a widget you never listed cannot be recorded."
    }
  },
  {
    "check": "diagram-render-service",
    "kind": "host",
    "sev": "warn",
    "host": "mermaid.ink / kroki.io / quickchart.io",
    "country": "United States",
    "pattern": "(?:mermaid\\.ink|kroki\\.io|quickchart\\.io)",
    "msg": "The diagram source is encoded into the URL, so your internal architecture is sent to a render service along with the reader's IP. Fix: render the diagram at build time and commit the .svg."
  },
  {
    "check": "insecure-asset-scheme",
    "kind": "text",
    "sev": "warn",
    "host": "plain http",
    "country": "any",
    "pattern": "(?:\\]\\(http://|src\\s*=\\s*[\"']http://|href\\s*=\\s*[\"']http://)",
    "msg": "This asset is fetched over plain http, so the transfer is also readable in transit. Fix: use https, or a relative path if the file lives with the docs."
  }
]
;</script>
<script>/* Image Host Transfer Lint - engine
 * One brain, two homes: Node (VS Code extension) and the browser (free web page).
 * It reads docs text line by line and names every asset URL that hands an EU
 * reader's IP address to a host outside the EEA.
 */
'use strict';

var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.IHT_RULES;

var URL_RE = /https?:\/\/|\/\//;
var FENCE_RE = /^\s*(?:```|~~~)/;

function compiled(rule) {
  if (!rule._re) rule._re = new RegExp(rule.pattern, 'i');
  return rule._re;
}

function check(text, opts) {
  opts = opts || {};
  var today = opts.today || '2026-09-21';
  var lines = String(text == null ? '' : text).split(/\r?\n/);
  var findings = [];
  var inFence = false;

  for (var i = 0; i < lines.length; i++) {
    var line = lines[i];
    if (FENCE_RE.test(line)) { inFence = !inFence; continue; }
    if (inFence) continue;                    // fenced blocks are examples, not live assets
    if (!URL_RE.test(line)) continue;         // a host named in prose is not a request

    for (var r = 0; r < RULES.length; r++) {
      var rule = RULES[r];
      if (!compiled(rule).test(line)) continue;
      findings.push({
        check: rule.check,
        sev: rule.sev,
        line: i + 1,
        msg: rule.msg,
        host: rule.host,
        country: rule.country,
        snippet: line.trim().slice(0, 120)
      });
      break;                                   // one line, one verdict
    }
  }

  return { findings: findings };
}

var engine = { check: check };

var API = { engine: engine, check: check, RULES: RULES, RULE_COUNT: RULES.length };

if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.IHTENGINE = API;
</script>
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
'use strict';
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
function summaryLine(res) {
  const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
  return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
}
function toText(res, label) {
  const f = (res && res.findings) || [];
  const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
  f.forEach(function (x) { lines.push('  ' + String(x.sev || 'error').toUpperCase().slice(0, 4) + '  ' + (x.line ? 'L' + x.line + '  ' : '') + (x.check || x.id || '') + '  ' + (x.msg || x.message || '')); });
  return lines.join('\n');
}
function toHtml(res) {
  const f = (res && res.findings) || [];
  if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
  const rows = f.map(function (x) { return '<tr><td class="sev ' + escapeHtml(String(x.sev || 'error')) + '">' + escapeHtml(String(x.sev || 'error')) + '</td><td>' + (x.line ? 'L' + escapeHtml(x.line) : '') + '</td><td><code>' + escapeHtml(x.check || x.id || '') + '</code></td><td>' + escapeHtml(x.msg || x.message || '') + '</td></tr>'; }).join('');
  return '<p class="sum">' + escapeHtml(summaryLine(res)) + '</p><table><thead><tr><th>Level</th><th>Line</th><th>Check</th><th>What</th></tr></thead><tbody>' + rows + '</tbody></table>';
}
var IHTREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = IHTREPORT;
else window.IHTREPORT = IHTREPORT;
</script>
<script>
(function(){
  var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
  today.value=new Date().toISOString().slice(0,10);
  function run(){ try { var res=window.IHTENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.IHTREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
  go.onclick=run; if (src.value.trim()) run();
})();
</script>


<section data-yj-install="free" style="margin:2rem auto 1rem;max-width:46rem;padding:1.25rem 1.25rem;border:1px solid #cfe3d6;border-radius:12px;background:#f6fbf8;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .4rem;font-size:1.2rem">Install free</h2><p style="margin:0 0 .8rem;color:#41505f">Free for the file open in your editor - no key, no limit. The workspace sweep and the report ask for a key.</p><a href="https://marketplace.visualstudio.com/items?itemName=ReadyStack.image-host-transfer-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'image-host-transfer-lint',src:'mp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">VS Code Marketplace</a><a href="https://open-vsx.org/extension/readystack/image-host-transfer-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'image-host-transfer-lint',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/image-host-transfer-lint" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'image-host-transfer-lint',src:'npm',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">npm</a><a href="https://www.npmjs.com/package/@readystack/image-host-transfer-lint" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'image-host-transfer-lint',src:'mcp',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">MCP server (npx)</a><a href="https://hub.docker.com/r/getreadystack/image-host-transfer-lint" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'image-host-transfer-lint',src:'docker',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Docker Hub</a><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.image-host-transfer-lint
npx @readystack/image-host-transfer-lint &lt;file&gt;
npx @readystack/image-host-transfer-lint --mcp</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/cl/polar_cl_l6iN1uWt0FwWu7tBsczD0jWpP2vxFM54Wdwqb3KPi1G" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
<section data-yj-alert="rule" style="margin:1rem auto 1.5rem;max-width:46rem;padding:1.1rem 1.25rem;border:1px solid #d9dee6;border-radius:12px;background:#fbfcfd;font:15px/1.55 system-ui,-apple-system,Segoe UI,sans-serif"><b style="font-size:1.05rem">Get the full rule table as a PDF</b><div style="opacity:.78;margin:.35rem 0 .8rem">All 17 rules Image Host Transfer Lint checks, each with its source and date, on printable pages you can hand to your team or auditor. We email it now, and once more only if a rule changes. No newsletter.</div><div style="display:flex;gap:8px;flex-wrap:wrap"><input id="yjae" type="email" placeholder="you@example.com" style="flex:1;min-width:210px;padding:11px 13px;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:#fff;color:inherit"><button id="yjab" style="padding:11px 18px;border-radius:8px;border:0;cursor:pointer;background:#0f7b3f;color:#fff;font:600 15px system-ui,sans-serif">Email me the PDF</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">Sent. It arrives within the hour - check spam too.</div><script>(function(){var b=document.getElementById("yjab"),e=document.getElementById("yjae");if(!b||!e)return;b.addEventListener("click",function(){var v=e.value.trim();if(!/.+@.+\..+/.test(v))return;try{localStorage.setItem("yj_em","1");}catch(_){}fetch(location.origin+"/api/subscribe",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({email:v,slug:"image-host-transfer-lint",lang:"en",source:"rule_table"+((b.closest&&b.closest("section")&&b.closest("section").getAttribute("data-yj-moved"))?"_at_result":"")})}).then(function(r){var o=document.getElementById("yjao");o.hidden=false;if(!r.ok){o.textContent="Could not save - please try again.";}}).catch(function(){var o=document.getElementById("yjao");o.hidden=false;o.textContent="Could not save - please try again.";});});})();</script><script>(function(){var e=document.getElementById("yjae"),s=e&&e.closest?e.closest("section"):null,o=document.getElementById("out");if(!s||!o)return;var d=false;function mv(ev){if(d)return;if(ev&&ev.target&&s.contains(ev.target))return;d=true;var b=o;while(b.parentNode&&b.parentNode!==document.body&&b.parentNode.tagName!=="MAIN"&&b.parentNode.tagName!=="ARTICLE")b=b.parentNode;if(!b.parentNode||b===s||b.contains(s))return;try{b.parentNode.insertBefore(s,b.nextSibling);s.setAttribute("data-yj-moved","1");}catch(_){}}document.addEventListener("input",mv,true);document.addEventListener("change",mv,true);})();</script></section>

<section data-yj-cta="paid" style="margin:3rem auto;max-width:46rem;padding:1.5rem 1.25rem;border:1px solid #d8dee9;border-radius:12px;background:#fbfcfe;font:16px/1.6 system-ui,-apple-system,Segoe UI,sans-serif"><h2 style="margin:0 0 .5rem;font-size:1.25rem">Get the complete version <span class="yj-price">$29</span></h2><p style="margin:.25rem 0 1rem;color:#41505f">This page is the working piece. The full pack has everything below.</p><p style="margin:0 0 1rem;color:#41505f">Names every image, font, badge and embed in your docs that sends an EU reader's IP outside the EEA - and the fix for each one.</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">LG Munchen I, 20 O 14144/19 (2022-01-20) awarded EUR 100 in damages to one visitor for one Google Fonts call on one page.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_CizB7BOiQnrCD9uAsBxpds5fDtpbi0BD1xPKa3GqTUD" rel="nofollow" style="display:inline-block;padding:.7rem 1.3rem;border-radius:8px;background:#1b64f2;color:#fff;text-decoration:none;font-weight:600">Buy the full version — $29</a></section>
<!--yj-share-s101--><div style="margin:28px 0 8px;text-align:center"><button id="yjcp" type="button" style="font:inherit;padding:10px 18px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Copy link</button><button id="yjimg" type="button" style="font:inherit;padding:10px 18px;margin-left:8px;border:1px solid #8884;border-radius:10px;background:transparent;cursor:pointer">Save result as image</button></div><script>(function(){var b=document.getElementById("yjimg");if(!b)return;b.addEventListener("click",function(){var o=document.getElementById("out");var txt=(o&&(o.innerText||o.textContent)||"").trim();if(!txt){b.textContent="(no result yet)";setTimeout(function(){b.textContent="Save result as image";},1500);return;}var lines=txt.split(String.fromCharCode(10)).filter(function(x){return x.trim();}).slice(0,10);var W=1200,P=56,c=document.createElement("canvas");c.width=W;var lh=38,H=P*2+70+lines.length*lh+60;c.height=H;var x=c.getContext("2d");x.fillStyle="#1a1512";x.fillRect(0,0,W,H);x.fillStyle="#f3e9d2";x.font="600 34px system-ui,Segoe UI,Arial";var t=(document.title||"").split("|")[0].trim();x.fillText(t.slice(0,54),P,P+34);x.font="22px ui-monospace,Menlo,Consolas,monospace";x.fillStyle="#e8dcc4";for(var i=0;i<lines.length;i++){x.fillText(lines[i].slice(0,88),P,P+70+i*lh+22);}x.font="600 22px system-ui,Segoe UI,Arial";x.fillStyle="#c9a227";var d=location.hostname.replace("www.","");x.fillText(d,W-P-x.measureText(d).width,H-P+6);c.toBlob(function(bl){var u=URL.createObjectURL(bl),a=document.createElement("a");a.href=u;a.download=(t||"result").replace(/[^a-z0-9]+/gi,"-").toLowerCase()+"-"+d+".png";document.body.appendChild(a);a.click();setTimeout(function(){URL.revokeObjectURL(u);a.remove();},2000);},"image/png");});})();</script><script>(function(){var b=document.getElementById("yjcp");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var u=location.href.split("#")[0];function d(){b.textContent="Link copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(u).then(d,function(){prompt("",u);});}else{prompt("",u);}});})();</script>
<script data-yj="yj-view-beacon">(function(){try{var p=location.pathname;var k='yjv:'+p;if(sessionStorage.getItem(k))return;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",ref:(document.referrer||''),src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}catch(e){}})();</script>

<script data-yj="yj-use-beacon">(function(){try{var o=document.getElementById('out');if(!o)return;var p=location.pathname,k='yju:'+p,sent=false;function fire(){if(sent||sessionStorage.getItem(k))return;if(!(o.textContent||'').replace(/\s+/g,'').length)return;sent=true;sessionStorage.setItem(k,'1');fetch('/api/ev',{method:'POST',keepalive:true,headers:{'Content-Type':'application/json'},body:JSON.stringify({path:p,lang:"en",t:'use',src:(function(){try{return (new URLSearchParams(location.search).get('ref')||'').replace(/[^a-z_]/g,'').slice(0,12)}catch(e){return ''}})()})}).catch(function(){});}var mo=new MutationObserver(function(){fire();if(sent)mo.disconnect();});mo.observe(o,{childList:true,characterData:true,subtree:true});}catch(e){}})();</script><div class="yj-home-find"><div class="yj-lgbar"><a href="/" class="yj-lg on">EN</a><a href="/de/" class="yj-lg">DE</a><a href="/ja/" class="yj-lg">JA</a><a href="/es/" class="yj-lg">ES</a><a href="/pt/" class="yj-lg">PT</a></div><section class="yj-find"><h2>Find a tool</h2><input id="yjq" type="search" autocomplete="off" placeholder="Type a product name, format or task"><div id="yjr"></div></section><style>.yj-lgbar{position:absolute;top:14px;right:18px;display:flex;gap:6px;z-index:60}.yj-lg{font:600 12px/1 system-ui,sans-serif;padding:6px 9px;border-radius:4px;text-decoration:none;color:#8b96a3;border:1px solid rgba(140,150,165,.28)}.yj-lg:hover{color:#e8eaed;border-color:rgba(160,175,195,.6)}.yj-lg.on{color:#0e1116;background:#cfd6de;border-color:#cfd6de}.yj-find{max-width:760px;margin:34px auto 8px;padding:0 20px}.yj-find h2{font:600 20px/1.3 system-ui,sans-serif;margin:0 0 10px}#yjq{width:100%;box-sizing:border-box;padding:13px 15px;font:400 15.5px system-ui,sans-serif;border:1px solid rgba(140,150,165,.34);border-radius:8px;background:rgba(255,255,255,.04);color:inherit}#yjq:focus{outline:none;border-color:#7FD1C4}#yjr{margin-top:10px;display:flex;flex-direction:column;gap:6px}#yjr a{display:flex;justify-content:space-between;gap:12px;padding:11px 14px;border:1px solid rgba(140,150,165,.22);border-radius:6px;text-decoration:none;font:400 14.5px/1.4 system-ui,sans-serif;color:inherit}#yjr a:hover{border-color:#7FD1C4}#yjr .pr{font:600 13px system-ui,sans-serif;color:#5fbf9a;white-space:nowrap}</style><script>(function(){var q=document.getElementById("yjq"),r=document.getElementById("yjr"),D=null;function load(){if(D)return Promise.resolve(D);return fetch("/search-index.json").then(function(x){return x.json();}).then(function(j){D=j;return j;}).catch(function(){D=[];return D;});}function esc(t){return String(t).replace(/[&<>"]/g,function(c){return {"&":"&amp;","<":"&lt;",">":"&gt;",'"':"&quot;"}[c];});}function run(){var v=q.value.trim().toLowerCase();if(v.length<2){r.innerHTML="";return;}load().then(function(j){var w=v.split(/\s+/),o=[];for(var i=0;i<j.length&&o.length<12;i++){var t=j[i].t.toLowerCase();var ok=true;for(var k=0;k<w.length;k++){if(t.indexOf(w[k])<0){ok=false;break;}}if(ok)o.push(j[i]);}r.innerHTML=o.length?o.map(function(x){return "<a href=\""+esc(x.u)+"\">"+"<span>"+esc(x.t)+"</span>"+(x.p?"<span class=\"pr\">$"+x.p+"</span>":"")+"</a>";}).join(""):"";});}q.addEventListener("input",run);q.addEventListener("focus",load);})();</script></div><section data-yj="yj-facts-v1" style="max-width:920px;margin:48px auto 0;padding:0 20px;font-family:system-ui,sans-serif"><style>.yj-article p{font:400 15.5px/1.7 system-ui,sans-serif;color:#26313b;margin:0 0 12px;max-width:72ch}.yj-article h2,.yj-article h3,.yj-article h4{font:600 17px/1.35 system-ui,sans-serif;color:#1b1f24;margin:22px 0 8px}.yj-article table{border-collapse:collapse;margin:10px 0 16px;font:14px/1.45 system-ui,sans-serif}.yj-article th,.yj-article td{border:1px solid #e2e6ea;padding:6px 10px;text-align:left}.yj-article th{background:#f4f6f8}.yj-article pre{background:#0f1720;color:#e6edf3;padding:12px 14px;border-radius:8px;overflow:auto;font:13px/1.5 ui-monospace,Menlo,monospace}.yj-article code{font:13px ui-monospace,Menlo,monospace;background:#f1f3f5;padding:1px 4px;border-radius:3px}.yj-article pre code{background:none;padding:0}.yj-article ul,.yj-article ol{font:15px/1.6 system-ui,sans-serif;color:#26313b}</style><p style="font:400 13px/1.5 system-ui,sans-serif;color:#6b7680;margin:0 0 14px"><time datetime="2026-09-21">Updated 2026-09-21</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-21","author":{"@type":"Organization","name":"ReadyStack"}}</script><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Worked example</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 16px;color:#5b6570">Real numbers from this tool, line by line.</p><div class="yj-article"><p><img src="https://getreadystack.com/img/promo/sku151192_result_card.jpg" alt="Image Host Transfer Lint" loading="lazy" style="max-width:100%;border-radius:6px"></p>
<p>Seven assets on one 41-line deploy handbook page send EU visitor IPs outside the EEA before the page has finished loading, and every one of the seven is fixable inside the repository - that page is the bundled test fixture of Image Host Transfer Lint, written the way a German platform team writes an internal handbook that later goes public.</p>
<p>A docs page reads like text. It is not. Every screenshot, every avatar, every build badge and every webfont link is an instruction to the reader&#x27;s browser: go and fetch this from somebody else&#x27;s server. The fetch carries the reader&#x27;s IP address, the page they are reading, and the time. When the server is outside the EEA, that is a transfer to a third country under GDPR Art. 44, and it happens while the page renders - before a consent banner exists, before the reader has clicked anything, on every visit.</p>
<p>The German courts have already put a number on it. LG Munchen I, case 20 O 14144/19, decided on 20 January 2022, awarded one visitor EUR 100 in damages for a single Google Fonts call on a single page.</p>
<p>Run the engine over the dirty fixture and it returns 7 findings on 7 lines: 6 errors and 1 warning. The stylesheet comes from fonts.googleapis.com. The maintainer photo comes from secure.gravatar.com, which also carries the MD5 hash of an e-mail address. The dashboard screenshot is hotlinked from i.imgur.com. The queue-depth panel sits on i.loli.net, the SM.MS image host in China. The build badge is a live image from img.shields.io. The walkthrough is a plain youtube.com/embed iframe. The copy helper is a script tag pointed at cdn.jsdelivr.net.</p>
<p>Run it over the clean fixture - the same page after the fixes - and it returns 0.</p>
<p>The interesting part is the third column: the country. This is where people, and chatbots, get it wrong. raw.githubusercontent.com feels like &quot;my own repo&quot; and is a host in the United States. cdn.jsdelivr.net is not one country at all; it resolves to US and Chinese edge nodes depending on who is reading, so there is no single receiving country for an Art. 30(1)(e) record. China has no EU adequacy decision, so an image on a Chinese bucket needs standard contractual clauses and a transfer impact assessment. Images land there constantly: an upload plugin ships with a default backend, somebody presses paste, and the link outlives the memory of it.</p>
<p>Seventeen rules cover the families that actually show up in docs repositories: webfonts, avatars, image hosts, repo hotlinks, script CDNs, Chinese object storage, embeds and comment widgets, live badges and diagram render services, analytics that fire on render, and assets still fetched over plain http.</p>
<p>Two things it deliberately does not count: a hostname written in prose, because prose makes no request, and anything inside a fenced code block, because an example is documentation. The clean fixture keeps the old leaking markup inside a fence on purpose, and still scores zero.</p>
<p>That is also the answer to &quot;why not a free online scanner&quot;. A scanner loads a URL that is already published. It cannot see the Markdown on your branch or the pull request adding the next Imgur link. The lint runs where the text lives, so the leak is caught by review instead of by a visitor.</p>
<p>The free scan finishes one job completely: open a file or a folder, and every leaking asset is named with its host, its country and its fix. The next job is a different one - owning the evidence: an Art. 30 transfer register exported as CSV and Markdown for the whole workspace, plus a self-host rewrite map listing every file, every URL and the local path that replaces it.</p></div><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:30px 0 10px;color:#1b1f24">15 seconds — what it actually does</h2><video controls muted playsinline preload="none" poster="/img/promo/sku151192_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku151192_short.mp4" type="video/mp4"></video><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:34px 0 6px;color:#1b1f24">Questions people ask</h2><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What does Image Host Transfer Lint actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads your Markdown, MDX and HTML docs line by line and flags every asset URL a reader&#x27;s browser would fetch from a third party: webfonts, avatars, image hosts, script CDNs, embeds, live badges and analytics. Each finding names the host, the country the request lands in, and the concrete fix that keeps the asset on your own origin.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Who is this for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Developers and technical writers in Germany, Austria and the Netherlands who publish a docs site out of a Markdown or HTML repository. If your handbook, changelog or product docs are public and your readers are in the EU, every hotlinked asset on those pages is a transfer you are answerable for.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a free online checker not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Online privacy scanners load a rendered URL. They cannot see the Markdown in your repository, the pages you have not deployed yet, or the branch under review. This runs where the text lives, so a leaking image is caught in the pull request instead of after publication, and code blocks holding examples are not counted as leaks.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What is free and what needs a licence key?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">The free scan is complete on its own: open a file or folder and every leaking asset is named with host, country and fix. The paid layer is a different job - owning the evidence. It exports a workspace-wide Art. 30 transfer register as CSV and Markdown plus a self-host rewrite map, and covers a team seat.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">What would this cost without the tool?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A court has already priced the failure: LG Munchen I, case 20 O 14144/19, awarded one visitor EUR 100 in damages on 20 January 2022 for a single Google Fonts call on a single page. A manual review of a docs repository is billed as privacy counsel time, page by page, and has to be repeated after every merge.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why not just ask ChatGPT or another AI chat?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Image Host Transfer Lint checks the file you open against 17 rules from a rule set dated 2026-09-21, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Image Host Transfer Lint actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads your Markdown, MDX and HTML docs line by line and flags every asset URL a reader's browser would fetch from a third party: webfonts, avatars, image hosts, script CDNs, embeds, live badges and analytics. Each finding names the host, the country the request lands in, and the concrete fix that keeps the asset on your own origin."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Developers and technical writers in Germany, Austria and the Netherlands who publish a docs site out of a Markdown or HTML repository. If your handbook, changelog or product docs are public and your readers are in the EU, every hotlinked asset on those pages is a transfer you are answerable for."}}, {"@type": "Question", "name": "Why is a free online checker not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Online privacy scanners load a rendered URL. They cannot see the Markdown in your repository, the pages you have not deployed yet, or the branch under review. This runs where the text lives, so a leaking image is caught in the pull request instead of after publication, and code blocks holding examples are not counted as leaks."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "The free scan is complete on its own: open a file or folder and every leaking asset is named with host, country and fix. The paid layer is a different job - owning the evidence. It exports a workspace-wide Art. 30 transfer register as CSV and Markdown plus a self-host rewrite map, and covers a team seat."}}, {"@type": "Question", "name": "What would this cost without the tool?", "acceptedAnswer": {"@type": "Answer", "text": "A court has already priced the failure: LG Munchen I, case 20 O 14144/19, awarded one visitor EUR 100 in damages on 20 January 2022 for a single Google Fonts call on a single page. A manual review of a docs repository is billed as privacy counsel time, page by page, and has to be repeated after every merge."}}, {"@type": "Question", "name": "Why not just ask ChatGPT or another AI chat?", "acceptedAnswer": {"@type": "Answer", "text": "A general AI chat answers from training data with a cutoff date, cannot read your repository and names no rule version. Image Host Transfer Lint checks the file you open against 17 rules from a rule set dated 2026-09-21, and points at the exact line. For a filing, an audit or a client you need that dated result on your own files."}}]}</script><div id="yj-ask" style="margin:36px 0 0;padding:18px 18px 16px;border:1px solid #e2e6ea;border-radius:10px;background:#fbfbfc"><h2 style="font:600 19px/1.3 system-ui,sans-serif;margin:0 0 4px;color:#1b1f24">Ask about this tool</h2><p style="font:400 13.5px/1.5 system-ui,sans-serif;margin:0 0 12px;color:#5b6570">One question, answered by the person who built it. Your email only if you want the answer sent.</p><form onsubmit="return yjAsk(event)" style="display:grid;gap:8px"><textarea name="question" required minlength="10" maxlength="2000" rows="3" placeholder="Your question" style="font:14px/1.5 system-ui,sans-serif;padding:10px;border:1px solid #cfd6dd;border-radius:6px"></textarea><div style="display:flex;gap:8px;flex-wrap:wrap"><input type="email" name="email" placeholder="Email (optional)" style="flex:1;min-width:200px;font:14px system-ui,sans-serif;padding:9px 10px;border:1px solid #cfd6dd;border-radius:6px"><button type="submit" style="font:600 14px system-ui,sans-serif;padding:9px 18px;border:0;border-radius:6px;background:#1b1f24;color:#fff;cursor:pointer">Send</button></div><p id="yj-ask-msg" style="font:13px system-ui,sans-serif;margin:2px 0 0;color:#0a6238;min-height:1em"></p></form><script>function yjAsk(e){e.preventDefault();var f=e.target,m=document.getElementById("yj-ask-msg");m.style.color="#0a6238";fetch(location.origin+"/api/ask",{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({sku:151192,slug:"image-host-transfer-lint",lang:"en",question:f.question.value,email:f.email.value,page:location.pathname})}).then(function(r){if(!r.ok)throw 0;m.textContent="Thanks \u2014 noted. Answers land on this page and, if you left an email, in your inbox.";f.question.value="";}).catch(function(){m.style.color="#a8551f";m.textContent="Could not send. Please try again in a minute.";});return false;}</script></div></section><section class="yj-related" style="max-width:920px;margin:56px auto 40px;padding:0 20px"><h2 style="font:600 21px/1.3 system-ui,sans-serif;margin:0 0 6px;color:#1b1f24">Same job, next tool</h2><p style="font:400 14.5px/1.5 system-ui,sans-serif;margin:0 0 20px;color:#5b6570">Free in the browser. The full version is a one-time licence.</p><div style="display:grid;grid-template-columns:repeat(auto-fill,minmax(250px,1fr));gap:14px"><a class="yj-rel-c" href="https://getreadystack.com/tools/real-estate-webflow-template"><span class="yj-rel-t">Real Estate Webflow Template Alternative — 7-Page Self-Hosted HTML Broker Site</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/view-in-browser-leak-audit"><span class="yj-rel-t">View in Browser — Publish Leak Audit (HTML)</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/agency-website-template"><span class="yj-rel-t">Agency Website Template — 7-Page Digital Marketing Agency Site (Plain HTML, No</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cpa-website-template"><span class="yj-rel-t">CPA Website Template — 9-Page Accounting Firm Site in Plain HTML, No Subscript</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/construction-website-template"><span class="yj-rel-t">Construction Website Template — 7-Page Contractor Site in Plain HTML, No Subsc</span><span class="yj-rel-p">$49</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/law-firm-website-template"><span class="yj-rel-t">Law Firm Website Template — 8-Page Practice Site in Plain HTML, No Subscriptio</span><span class="yj-rel-p">$49</span></a></div><style>.yj-rel-c{display:flex;flex-direction:column;gap:8px;padding:15px 16px;border:1px solid #e2e6ea;border-radius:6px;text-decoration:none;background:#fff}.yj-rel-c:hover{border-color:#b9c2cb;background:#fafbfc}.yj-rel-t{font:500 14.5px/1.42 system-ui,sans-serif;color:#1b1f24}.yj-rel-p{font:600 13px system-ui,sans-serif;color:#0a6238}</style></section></body>
</html>