File size: 45,436 Bytes
3c6a9c3
1610627
 
 
 
71b181f
 
1610627
 
 
 
 
 
 
 
 
 
 
 
 
 
71b181f
1610627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71b181f
1610627
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
71b181f
1610627
71b181f
 
1610627
71b181f
 
1610627
71b181f
 
 
 
 
 
 
 
 
 
 
 
 
3c6a9c3
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
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>NET framework end of Support — check project support dates</title>
<meta name="description" content="This browser tool reads a project file and dates each target framework against Microsoft&#x27;s end-of-support date and the support period promised to buyers.">
<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/dotnet-eol-support-gate"></head>
<body>
<div class="wrap">
<h1>NET framework end of Support — check project support dates</h1>
<p class="lede">Reads a .csproj or global.json and dates every target framework against Microsoft's published end-of-support day, then against the support period you promise buyers. Runs entirely in your browser — nothing is uploaded.</p>
<textarea id="src" spellcheck="false">&lt;Project Sdk="Microsoft.NET.Sdk"&gt;

  &lt;PropertyGroup&gt;
    &lt;TargetFrameworks&gt;net8.0;net9.0;net6.0&lt;/TargetFrameworks&gt;
    &lt;Nullable&gt;enable&lt;/Nullable&gt;
    &lt;NuGetAudit&gt;false&lt;/NuGetAudit&gt;
  &lt;/PropertyGroup&gt;

  &lt;ItemGroup&gt;
    &lt;PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.36" /&gt;
    &lt;PackageReference Include="Microsoft.EntityFrameworkCore" Version="9.0.4" /&gt;
    &lt;PackageReference Include="Xamarin.Essentials" Version="1.8.1" /&gt;
    &lt;PackageReference Include="Serilog" Version="4.2.0" /&gt;
  &lt;/ItemGroup&gt;

&lt;/Project&gt;</textarea>
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label><label>support period <select id="sy"><option>3</option><option selected>5</option><option>10</option></select> years</label></div>
<div id="out" aria-live="polite"></div>
<p id="next"><a href="https://getreadystack.com/api/buy/cl/polar_cl_Z3kgDSemeuFusqV4CgHenwXtVCDgUEjo92a1x2d70T6">Sweep every project file in the workspace and write the dated support-window evidence file</a><br><small>$29 once &middot; one licence key per person or team seat &middot; Having a contractor inventory runtime support dates across a solution and write the evidence page is commonly quoted as a multi-day engagement.</small></p>
<div id="paid"><strong>Free here:</strong> Dates one .csproj or global.json you have open: every target framework, SDK pin and runtime-bound package gets its real end-of-support date, the days left, and the months it falls short of the support period you promise.. <strong>In the editor:</strong> Runs the same dating over every project file in the workspace at once and writes the dated support-window evidence file (Markdown + CSV) you keep and hand to an auditor. — that part asks for a licence key.</div>
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
</div>
<script>var DOTNET_EOL_RULES = [
  {"id": "tfm_out_of_support", "sev": "error", "title": "Target framework is past its end-of-support date", "why": "Microsoft ships no security fixes for it, so every later CVE in the runtime stays open in your build."},
  {"id": "tfm_ends_soon", "sev": "error", "title": "Target framework loses support inside 180 days", "why": "A runtime bump is a release-planning item, not a patch; 180 days is the last comfortable window to schedule it."},
  {"id": "support_window_gap", "sev": "error", "title": "Runtime dies before the support period you promise ends", "why": "CRA Article 13(8) makes the manufacturer support the product for the declared period, at least 5 years, so the runtime has to outlive the promise."},
  {"id": "support_period_undeclared", "sev": "warn", "title": "No support period declared in the project file", "why": "CRA Article 13(8) requires a determined support period; if it is nowhere in the repo, the gate has to assume the 5-year floor."},
  {"id": "framework_retired", "sev": "error", "title": ".NET Framework 4.6.1 or older", "why": ".NET Framework 4.5.2, 4.6 and 4.6.1 all left support on 2022-04-26 and never come back."},
  {"id": "xamarin_retired", "sev": "error", "title": "Xamarin target or package reference", "why": "Xamarin support ended on 2024-05-01; the .NET MAUI move is the only supported path."},
  {"id": "sdk_pin_out_of_support", "sev": "error", "title": "global.json pins an SDK band that is out of support", "why": "A pinned dead SDK silently overrides an up-to-date machine and rebuilds the dead runtime on every CI run."},
  {"id": "preview_runtime", "sev": "error", "title": "Preview or release-candidate runtime in a shipping project", "why": "Preview builds carry no support period at all, so they cannot back any declared support promise."},
  {"id": "runtime_bound_package", "sev": "error", "title": "Package pinned to a runtime version that is out of support", "why": "ASP.NET Core, EF Core and Microsoft.Extensions.* follow the .NET release lifecycle exactly, so a 6.x or 9.x pin is already unpatched."},
  {"id": "nuget_audit_off", "sev": "error", "title": "NuGet vulnerability audit disabled or not covering transitive packages", "why": "CRA Annex I Part II asks manufacturers to handle vulnerabilities in the components they ship, including the ones they did not pick directly."}
]
;</script>
<script>// .NET EOL & CRA Support Window Gate — one brain, used by the extension and by the free web page.
'use strict';
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.DOTNET_EOL_RULES;

// Microsoft .NET release lifecycle — published end-of-support dates.
var TFM_EOL = {
  'netcoreapp3.1': '2022-12-13', 'net5.0': '2022-05-10', 'net6.0': '2024-11-12',
  'net7.0': '2024-05-14', 'net8.0': '2026-11-10', 'net9.0': '2026-05-12', 'net10.0': '2028-11-14'
};
var MAJOR_EOL = { '3': '2022-12-13', '5': '2022-05-10', '6': '2024-11-12', '7': '2024-05-14', '8': '2026-11-10', '9': '2026-05-12', '10': '2028-11-14' };
var FX_RETIRED = { 'net45': '2016-01-12', 'net451': '2016-01-12', 'net452': '2022-04-26', 'net46': '2022-04-26', 'net461': '2022-04-26' };
var XAMARIN_END = '2024-05-01';
var RUNTIME_PKG = /^(Microsoft\.AspNetCore\.|Microsoft\.EntityFrameworkCore|Microsoft\.Extensions\.)/;
var SOON_DAYS = 180;

function day(s) { var p = String(s || '').slice(0, 10).split('-'); return Date.UTC(+p[0], (+p[1] || 1) - 1, +p[2] || 1); }
function daysBetween(a, b) { return Math.round((day(b) - day(a)) / 86400000); }
function iso(ms) { return new Date(ms).toISOString().slice(0, 10); }
function addYears(s, n) { var d = new Date(day(s)); d.setUTCFullYear(d.getUTCFullYear() + Math.round(n)); return iso(d.getTime()); }
function months(a, b) { return Math.round(daysBetween(a, b) / 30.44); }

function check(text, opts) {
  opts = opts || {};
  var today = String(opts.today || new Date().toISOString().slice(0, 10)).slice(0, 10);
  var years = Number(opts.support_years || 5) || 5;
  var placed = String(opts.placed_on_market || today).slice(0, 10);
  var lines = String(text == null ? '' : text).split(/\r?\n/);
  var out = [];
  var declared = null, declaredLine = 0, sawProject = false, sawTfm = false;
  var auditOff = false, auditMode = false, sawAudit = false;

  function add(id, line, msg) { out.push({ check: id, sev: (RULES.filter(function (r) { return r.id === id; })[0] || {}).sev || 'error', msg: msg, line: line }); }

  // pass 1 — declared support period and audit settings
  lines.forEach(function (raw, i) {
    var n = i + 1;
    var d = raw.match(/(?:SupportedUntil|SupportPeriodEnd|SupportEndDate|CRA-support-until)\s*[>:=]\s*"?(\d{4}-\d{2}-\d{2})/i);
    if (d && !declared) { declared = d[1]; declaredLine = n; }
    if (/<Project\b|"sdk"\s*:/i.test(raw)) sawProject = true;
    if (/<NuGetAudit>\s*false\s*</i.test(raw)) { auditOff = true; add('nuget_audit_off', n, 'NuGetAudit is switched off, so no package in this project is checked against the advisory database at build time. CRA Annex I Part II expects shipped components to be screened.'); }
    if (/<NuGetAudit>\s*true\s*</i.test(raw)) sawAudit = true;
    if (/<NuGetAuditMode>\s*all\s*</i.test(raw)) { auditMode = true; sawAudit = true; }
  });
  if (sawProject && !auditOff && sawAudit && !auditMode) add('nuget_audit_off', 1, 'NuGetAuditMode is not set to "all", so only your direct PackageReferences are screened and transitive dependencies — where most shipped CVEs live — are skipped.');

  var windowEnd = declared || addYears(placed, years);
  if (sawProject && !declared) {
    add('support_period_undeclared', 1, 'No support period is declared in this file, so the gate uses the CRA Article 13(8) floor: ' + years + ' years from ' + placed + ', ending ' + windowEnd + '. Add <SupportedUntil>' + windowEnd + '</SupportedUntil> to make the promise explicit.');
  }

  function dateTfm(tfm, n) {
    var t = tfm.toLowerCase().trim();
    if (/-(preview|rc)/.test(t) || /^net(1[1-9])\.0/.test(t)) {
      add('preview_runtime', n, tfm + ' is a preview or unreleased runtime. Preview builds carry no support period at all, so nothing you promise a buyer is backed by a patch stream.');
      return;
    }
    if (/^(monoandroid|xamarin|xamarinios|xamarinmac)/.test(t)) {
      add('xamarin_retired', n, tfm + ' is a Xamarin target framework. Xamarin support ended on ' + XAMARIN_END + ', ' + daysBetween(XAMARIN_END, today) + ' days ago; move the head project to a net10.0-android / net10.0-ios .NET MAUI target.');
      return;
    }
    if (FX_RETIRED[t]) {
      add('framework_retired', n, tfm + ' (.NET Framework) left support on ' + FX_RETIRED[t] + ', ' + daysBetween(FX_RETIRED[t], today) + ' days ago. Retarget to net472 or later, or to net10.0.');
      return;
    }
    var base = t.split('-')[0];
    var eol = TFM_EOL[base];
    if (!eol) return;
    var left = daysBetween(today, eol);
    if (left < 0) {
      add('tfm_out_of_support', n, tfm + ' lost security servicing on ' + eol + ' — ' + (-left) + ' days ago. Every runtime CVE published since that day is still open in this build.');
    } else if (left <= SOON_DAYS) {
      add('tfm_ends_soon', n, tfm + ' stops receiving security fixes on ' + eol + ', in ' + left + ' days. A runtime bump is a release, not a patch — schedule it now.');
    }
    if (left >= 0 && day(eol) < day(windowEnd)) {
      var tail = (day(TFM_EOL['net10.0']) >= day(windowEnd)) ? ' net10.0 covers that promise to ' + TFM_EOL['net10.0'] + '.' : ' Even net10.0 only reaches ' + TFM_EOL['net10.0'] + ', so this promise needs a planned retarget on the roadmap, not a one-off bump.';
      add('support_window_gap', n, tfm + ' dies on ' + eol + ', but the support period running to ' + windowEnd + ' leaves ' + months(eol, windowEnd) + ' months you have promised to patch on a runtime nobody patches.' + tail);
    }
  }

  // pass 2 — target frameworks, SDK pins, packages
  lines.forEach(function (raw, i) {
    var n = i + 1, m;
    if ((m = raw.match(/<TargetFramework>\s*([^<\s]+)\s*</i))) { sawTfm = true; dateTfm(m[1], n); }
    if ((m = raw.match(/<TargetFrameworks>\s*([^<]+)</i))) {
      sawTfm = true;
      m[1].split(';').forEach(function (t) { if (t.trim()) dateTfm(t, n); });
    }
    if ((m = raw.match(/"version"\s*:\s*"(\d+)\.(\d+)\.(\d+)/))) {
      var eol = MAJOR_EOL[m[1]];
      if (eol && daysBetween(today, eol) < 0) {
        add('sdk_pin_out_of_support', n, 'global.json pins SDK ' + m[1] + '.' + m[2] + '.' + m[3] + '. That band went out of support on ' + eol + ', ' + daysBetween(eol, today) + ' days ago, and the pin overrides every up-to-date SDK on the build machine.');
      } else if (eol && daysBetween(today, eol) <= SOON_DAYS) {
        add('sdk_pin_out_of_support', n, 'global.json pins SDK ' + m[1] + '.' + m[2] + '.' + m[3] + ', whose band goes out of support on ' + eol + ', in ' + daysBetween(today, eol) + ' days. Every CI run will keep building on it until the pin moves.');
      }
    }
    if ((m = raw.match(/<PackageReference\s+Include="([^"]+)"[^>]*Version="([^"]+)"/i))) {
      var id = m[1], ver = m[2];
      if (/^Xamarin\./i.test(id)) {
        add('xamarin_retired', n, id + ' ' + ver + ' is a Xamarin package. Xamarin support ended on ' + XAMARIN_END + ', ' + daysBetween(XAMARIN_END, today) + ' days ago, and no fix is coming for anything found in it.');
      } else if (RUNTIME_PKG.test(id)) {
        var maj = (ver.match(/^(\d+)/) || [])[1];
        var pe = MAJOR_EOL[maj];
        if (pe && daysBetween(today, pe) < 0) {
          add('runtime_bound_package', n, id + ' ' + ver + ' follows the .NET ' + maj + ' lifecycle, which ended on ' + pe + ' — ' + daysBetween(pe, today) + ' days ago. This package line is unpatched.');
        }
      }
    }
  });

  return { findings: out, today: today, window_end: windowEnd, support_years: years, declared: !!declared, declared_line: declaredLine, rules: RULES.length, targets_seen: sawTfm };
}

var API = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, TFM_EOL: TFM_EOL };
if (typeof module !== 'undefined') module.exports = API;
if (typeof window !== 'undefined') window.DOTNET_EOL_ENGINE = 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 DOTNET_EOL_REPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
if (typeof module !== 'undefined' && module.exports) module.exports = DOTNET_EOL_REPORT;
else window.DOTNET_EOL_REPORT = DOTNET_EOL_REPORT;
</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.DOTNET_EOL_ENGINE.engine.check(src.value,{today:today.value,support_years:Number(document.getElementById('sy').value)}); out.innerHTML=window.DOTNET_EOL_REPORT.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://www.npmjs.com/package/@readystack/dotnet-eol-support-gate" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dotnet-eol-support-gate',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/dotnet-eol-support-gate" rel="noopener" data-yj-install-door="mcp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dotnet-eol-support-gate',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/dotnet-eol-support-gate" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'dotnet-eol-support-gate',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">npx @readystack/dotnet-eol-support-gate &lt;file&gt;
npx @readystack/dotnet-eol-support-gate --mcp
docker run --rm -v &quot;$PWD:/w&quot; getreadystack/dotnet-eol-support-gate /w</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 10 rules .NET EOL &amp; CRA Support Window Gate 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:"dotnet-eol-support-gate",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">Dates every target framework in a .csproj against Microsoft's end-of-support day and against the support period you promise</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">Having a contractor inventory runtime support dates across a solution and write the evidence page is commonly quoted as a multi-day engagement</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_Z3kgDSemeuFusqV4CgHenwXtVCDgUEjo92a1x2d70T6" 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><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>


<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>
<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-17">Updated 2026-09-17</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-09-17","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>net8.0 takes its last security patch in 58 days — for EU .NET and .NET MAUI maintainers that day is 10 November 2026, and nothing in the .csproj says so.</p>
<p>== _fixtures/dirty.csproj — 9 findings ERRO  L6   nuget_audit_off            NuGetAudit is switched off, so no package in this project is checked against the advisory database at build time. WARN  L1   support_period_undeclared  No support period is declared in this file, so the gate uses the CRA Article 13(8) floor: 5 years from 2026-12-01, ending 2031-12-01. ERRO  L4   tfm_ends_soon              net8.0 stops receiving security fixes on 2026-11-10, in 58 days. ERRO  L4   support_window_gap         net8.0 dies on 2026-11-10, but the support period running to 2031-12-01 leaves 61 months you have promised to patch on a runtime nobody patches. ERRO  L4   tfm_out_of_support         net9.0 lost security servicing on 2026-05-12 — 124 days ago. ERRO  L4   tfm_out_of_support         net6.0 lost security servicing on 2024-11-12 — 670 days ago. ERRO  L10  runtime_bound_package      Microsoft.AspNetCore.Authentication.JwtBearer 6.0.36 follows the .NET 6 lifecycle, which ended on 2024-11-12. ERRO  L11  runtime_bound_package      Microsoft.EntityFrameworkCore 9.0.4 follows the .NET 9 lifecycle, which ended on 2026-05-12. ERRO  L12  xamarin_retired            Xamarin.Essentials 1.8.1 is a Xamarin package; Xamarin support ended on 2024-05-01, 865 days ago.</p>
<p>One ordinary project file, dated as of 2026-09-13: 9 findings from 10 rules, each carrying its line number and the arithmetic behind it. The clean net10.0 fixture returns none.</p>
<p>The dates are public and fixed, and they live nowhere near the file where the decision is made. The project file says net8.0. It does not say that the last patch for it lands on 2026-11-10. Ask a model instead and it will name a month with total confidence, drawn from whenever its training stopped.</p>
<p>The second number is the one nobody computes by hand. The EU Cyber Resilience Act, Regulation (EU) 2024/2847, entered into force on 2024-12-10. Its vulnerability-reporting duties apply from 2026-09-11, and the rest applies from 2027-12-11. Article 13(8) requires the manufacturer to determine a support period for the product and to hold it for at least 5 years unless the expected product lifetime is shorter. A support period is a patching promise, and a patching promise is only as good as the runtime underneath it.</p>
<p>So the gate does the subtraction. Give it the day you place the product on the market and the number of years you are promising, and it reports the overhang in months. With a 5-year period starting 2026-12-01, a net8.0 project leaves 61 months of promised patching sitting on a runtime whose last security fix lands on 2026-11-10. Even net10.0, the current LTS, only reaches 2028-11-14 — which means this is not a version bump you slot into a sprint, it is a retarget you put on a roadmap.</p>
<p>Change the inputs and the answer changes, because this is arithmetic and not advice: a library declaring a three-year period on net10.0 comes back clean, its declaration read straight out of a SupportedUntil element instead of the five-year floor.</p>
<p>Ten checks run on every pass: tfm_out_of_support, tfm_ends_soon, support_window_gap, support_period_undeclared, framework_retired, xamarin_retired, sdk_pin_out_of_support, preview_runtime, runtime_bound_package and nuget_audit_off. The last one matters: CRA Annex I Part II covers the components you ship, and transitive dependencies are components you ship.</p>
<p>Dating the file you have open is free and needs no key, in the editor or on the free web page that runs the same engine. Paste a project file, set the date, set the support period. The licensed job is a different one: sweeping every project file in the workspace in one pass and writing the dated support-window evidence file you keep — the artifact an auditor asks for, rather than the answer one developer needed today.</p></div><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 the .NET EOL &amp; CRA Support Window Gate actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads a .csproj, Directory.Build.props or global.json and dates it twice. First against the .NET release lifecycle: net9.0 ended on 2026-05-12, net8.0 ends on 2026-11-10, net10.0 runs to 2028-11-14. Then against the support period you promise buyers, reporting the months of promised patching that land after your runtime stops being patched.</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">Maintainers of .NET and .NET MAUI products sold into the EU, who under the Cyber Resilience Act must determine a support period of at least 5 years and hold it. Also anyone inheriting a solution and needing to know, by date, which of its projects are already unpatched before quoting the upgrade.</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 a chatbot which .NET versions are supported?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Lifecycle dates move and models answer from a training snapshot, confidently naming the wrong month. This gate carries the dates as data and does the subtraction against your file and today&#x27;s date, so the answer is a day count you can put in a ticket rather than a sentence you have to verify.</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">Dating the file you have open is free forever and needs no key: all 10 checks, every date, every day count. The licensed part is a different job - sweeping every project file in the workspace at once and writing the dated support-window evidence file you keep for an audit.</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 done by a person?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Inventorying target frameworks, SDK pins and runtime-bound packages across a real solution, dating each against the lifecycle, and writing the evidence page is commonly quoted as a multi-day consulting engagement. The gate is $29 once,, and the free tier already answers the single-file question.</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. .NET EOL &amp; CRA Support Window Gate checks the file you open against 10 rules from a rule set dated 2026-09-20, 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 the .NET EOL & CRA Support Window Gate actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads a .csproj, Directory.Build.props or global.json and dates it twice. First against the .NET release lifecycle: net9.0 ended on 2026-05-12, net8.0 ends on 2026-11-10, net10.0 runs to 2028-11-14. Then against the support period you promise buyers, reporting the months of promised patching that land after your runtime stops being patched."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "Maintainers of .NET and .NET MAUI products sold into the EU, who under the Cyber Resilience Act must determine a support period of at least 5 years and hold it. Also anyone inheriting a solution and needing to know, by date, which of its projects are already unpatched before quoting the upgrade."}}, {"@type": "Question", "name": "Why not just ask a chatbot which .NET versions are supported?", "acceptedAnswer": {"@type": "Answer", "text": "Lifecycle dates move and models answer from a training snapshot, confidently naming the wrong month. This gate carries the dates as data and does the subtraction against your file and today's date, so the answer is a day count you can put in a ticket rather than a sentence you have to verify."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Dating the file you have open is free forever and needs no key: all 10 checks, every date, every day count. The licensed part is a different job - sweeping every project file in the workspace at once and writing the dated support-window evidence file you keep for an audit."}}, {"@type": "Question", "name": "What would this cost done by a person?", "acceptedAnswer": {"@type": "Answer", "text": "Inventorying target frameworks, SDK pins and runtime-bound packages across a real solution, dating each against the lifecycle, and writing the evidence page is commonly quoted as a multi-day consulting engagement. The gate is $29 once,, and the free tier already answers the single-file question."}}, {"@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. .NET EOL & CRA Support Window Gate checks the file you open against 10 rules from a rule set dated 2026-09-20, 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:56311,slug:"dotnet-eol-support-gate",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/maui-ios-submission-gate"><span class="yj-rel-t">MAUI iOS Submission Gate</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/base-image-eol-lint"><span class="yj-rel-t">Base Image EOL Lint - Dockerfile and CI end-of-life check</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/actions-deprecation-lint-2026"><span class="yj-rel-t">GitHub Actions Deprecation Lint: the 2026 runner and action EOL dates</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/personal-data-map-dsar-audit"><span class="yj-rel-t">Personal Data Map: find every column a GDPR subject access request has to reac</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/wedding-videography-luts"><span class="yj-rel-t">Wedding Videography LUTs for Every Light on the Wedding Day</span><span class="yj-rel-p">$100</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/age-gate-lint"><span class="yj-rel-t">Age Gate Lint — UK Online Safety Act</span><span class="yj-rel-p">$29</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>