Upload index.html with huggingface_hub
Browse files- index.html +269 -17
index.html
CHANGED
|
@@ -1,19 +1,271 @@
|
|
| 1 |
<!doctype html>
|
| 2 |
-
<html>
|
| 3 |
-
|
| 4 |
-
|
| 5 |
-
|
| 6 |
-
|
| 7 |
-
|
| 8 |
-
|
| 9 |
-
|
| 10 |
-
|
| 11 |
-
|
| 12 |
-
|
| 13 |
-
|
| 14 |
-
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
</html>
|
|
|
|
| 1 |
<!doctype html>
|
| 2 |
+
<html lang="en">
|
| 3 |
+
<head>
|
| 4 |
+
<meta charset="utf-8">
|
| 5 |
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
| 6 |
+
<title>Gitlab Anonymous Fetch 2026 — check fetches before the cap</title>
|
| 7 |
+
<style>
|
| 8 |
+
:root{color-scheme:light}body{margin:0;font:15px/1.55 system-ui,-apple-system,"Segoe UI",sans-serif;background:#faf9f5;color:#1a1512}
|
| 9 |
+
.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}
|
| 10 |
+
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}
|
| 11 |
+
.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}
|
| 12 |
+
input[type=date]{font:14px system-ui;padding:8px;border:1px solid #d9d0c4;border-radius:6px}
|
| 13 |
+
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}
|
| 14 |
+
.sev.error{color:#a8551f;font-weight:700}.sev.warn,.sev.warning{color:#96691f}.ok{color:#2a6446;font-weight:600}.sum{font-weight:600}
|
| 15 |
+
.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}
|
| 16 |
+
</style>
|
| 17 |
+
<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>
|
| 18 |
+
<style data-yj-mfit="1">#out{max-width:100%}#out td,#out th{overflow-wrap:break-word}#out th{white-space:nowrap}#out pre{white-space:pre-wrap;overflow-wrap:anywhere}#out code{white-space:pre-wrap;overflow-wrap:break-word}@media (max-width:640px){#out td,#out th{overflow-wrap:anywhere}#out th{white-space:normal}table{display:block;max-width:100%;overflow-x:auto}pre{max-width:100%;overflow-x:auto}code,a{overflow-wrap:anywhere}img,svg,video,canvas,iframe{max-width:100%}textarea,input,select{max-width:100%;box-sizing:border-box}.yj-lgbar{position:static!important;justify-content:flex-end;margin:0 0 10px}h1,h2,h3,h4,p,li,dd,dt,blockquote,figcaption,summary,label,.res,.lead{overflow-wrap:anywhere}label{max-width:100%;box-sizing:border-box}#out table:not(:has(tr>:nth-child(4))){display:table;table-layout:fixed;width:100%}#out table:has(tr>:nth-child(4)),#out table:has(tr>:nth-child(4)) tbody,#out table:has(tr>:nth-child(4)) tr,#out table:has(tr>:nth-child(4)) td{display:block;width:auto;overflow:visible}#out table:has(tr>:nth-child(4)) thead{display:none}#out table:has(tr>:nth-child(4)) tr{padding:10px 0;border-bottom:1px solid rgba(128,128,128,.28)}#out table:has(tr>:nth-child(4)) td{border:0;padding:1px 0}#out table:has(tr>:nth-child(4)) td:nth-child(-n+3){display:inline-block;margin-right:10px;font-size:12px;opacity:.78}}</style>
|
| 19 |
+
<!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/codeberg-gitlab-anonymous-fetch-lint"><!--yj-trust-org-s191--><script type="application/ld+json" data-yj="yj-trust-org">{"@context": "https://schema.org", "@type": "Organization", "name": "ReadyStack", "url": "https://getreadystack.com/", "logo": "https://getreadystack.com/branding/readystack_profile.png", "email": "support@getreadystack.com", "contactPoint": {"@type": "ContactPoint", "contactType": "customer support", "email": "support@getreadystack.com", "availableLanguage": ["English", "German", "Japanese", "Spanish", "Portuguese"]}}</script><!--/yj-trust-org-s191--></head>
|
| 20 |
+
<body>
|
| 21 |
+
<div class="wrap">
|
| 22 |
+
<h1>Gitlab Anonymous Fetch 2026 — check fetches before the cap</h1>
|
| 23 |
+
<p class="lede">Maps every token-less gitlab.com fetch in workflows, Dockerfiles, go.mod, requirements.txt and package.json before the 60/hour anonymous cap of 2026-10-19, with the token or Codeberg line. Runs entirely in your browser — nothing is uploaded.</p>
|
| 24 |
+
<textarea id="src" spellcheck="false">name: build
|
| 25 |
+
on: [push]
|
| 26 |
+
jobs:
|
| 27 |
+
build:
|
| 28 |
+
runs-on: ubuntu-latest
|
| 29 |
+
steps:
|
| 30 |
+
- uses: actions/checkout@v4
|
| 31 |
+
- name: Shared CI scripts
|
| 32 |
+
run: git clone --depth 1 https://gitlab.com/acme-tools/ci-scripts.git
|
| 33 |
+
- name: Python deps
|
| 34 |
+
run: pip install "git+https://gitlab.com/acme-tools/pyhelpers.git@v2.3.0"
|
| 35 |
+
- name: Go deps
|
| 36 |
+
env:
|
| 37 |
+
GOPROXY: direct
|
| 38 |
+
run: go mod download
|
| 39 |
+
- name: Latest CLI tag
|
| 40 |
+
run: curl -s https://gitlab.com/api/v4/projects/acme-tools%2Fcli/releases | jq -r '.[0].tag_name'
|
| 41 |
+
- name: Install CLI
|
| 42 |
+
run: curl -sL -o cli.tar.gz https://gitlab.com/acme-tools/cli/-/archive/v1.4.2/cli-v1.4.2.tar.gz
|
| 43 |
+
- name: UI kit
|
| 44 |
+
run: npm install gitlab:acme-tools/ui-kit#v3.1.0
|
| 45 |
+
- name: Billing client
|
| 46 |
+
run: pip install acme-billing --index-url https://gitlab.com/api/v4/projects/41/packages/pypi/simple</textarea>
|
| 47 |
+
<div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
|
| 48 |
+
<div id="out" aria-live="polite"></div>
|
| 49 |
+
<div id="paid"><strong>Free here:</strong> Lists every anonymous gitlab.com fetch in the open file (file and line) with the exact token line or Codeberg line that replaces it. <strong>In the editor:</strong> Whole-workspace migration plan (each gitlab.com fetch mapped to its token or Codeberg replacement), a dated exposure report and a CI gate that fails a build adding a new anonymous fetch — that part asks for a licence key.<br><a href="https://getreadystack.com/api/buy/cl/polar_cl_yqBgzWBrXNKCqHBOSG0cZl9F9psXlpdxPFBSQ2744ag">Get the full version — $29 once: workspace migration plan + CI gate</a> · one licence key per person or team seat.</div>
|
| 50 |
+
<p class="note">Same engine as the VS Code extension, byte for byte.</p>
|
| 51 |
+
</div>
|
| 52 |
+
<script>var GLANON_RULES = [
|
| 53 |
+
{"id": "GLA01", "sev": "error", "title": "Anonymous git clone/fetch from gitlab.com", "file": "workflows, Dockerfile, scripts", "why": "git over https with no credentials is an unauthenticated gitlab.com request.", "fix": "Put a token in the URL (oauth2:${GITLAB_TOKEN}@gitlab.com), use SSH (git@gitlab.com:), or point it at the Codeberg mirror."},
|
| 54 |
+
{"id": "GLA02", "sev": "error", "title": "pip git+https dependency on gitlab.com", "file": "requirements.txt, pyproject.toml, pip install", "why": "pip runs git clone against gitlab.com with no credentials on every cold install.", "fix": "git+https://oauth2:${GITLAB_TOKEN}@gitlab.com/... (pip expands ${VAR} in requirements files) or git+https://codeberg.org/..."},
|
| 55 |
+
{"id": "GLA03", "sev": "error", "title": "npm dependency fetched from gitlab.com", "file": "package.json, npm install", "why": "gitlab:owner/repo and git+https://gitlab.com deps are fetched from gitlab.com without credentials; package.json cannot expand a token.", "fix": "git+ssh://git@gitlab.com/owner/repo.git#ref (deploy key) or git+https://codeberg.org/owner/repo.git#ref"},
|
| 56 |
+
{"id": "GLA04", "sev": "warning", "title": "Go modules fetched direct from gitlab.com", "file": "workflows, Dockerfile, go env", "why": "GOPROXY=direct, or GOPRIVATE/GONOPROXY covering gitlab.com, makes the go command clone gitlab.com modules itself, from your IP, without credentials unless .netrc is set.", "fix": "GOPROXY=https://proxy.golang.org,direct for public modules; for private ones write .netrc: machine gitlab.com login oauth2 password ${GITLAB_TOKEN}"},
|
| 57 |
+
{"id": "GLA05", "sev": "info", "title": "Go module path on gitlab.com", "file": "go.mod, go get, go install", "why": "Map entry: with the default GOPROXY the module comes through proxy.golang.org, so your IP is not the one counted.", "fix": "If you mirror it to Codeberg: replace gitlab.com/owner/repo =\u003e codeberg.org/owner/repo vX.Y.Z"},
|
| 58 |
+
{"id": "GLA06", "sev": "error", "title": "Unauthenticated gitlab.com API call", "file": "workflows, Dockerfile, scripts", "why": "A /api/v4 call with no PRIVATE-TOKEN, JOB-TOKEN or Authorization header counts against the anonymous allowance.", "fix": "curl --header \"PRIVATE-TOKEN: ${GITLAB_TOKEN}\" ... or the Forgejo API at https://codeberg.org/api/v1/"},
|
| 59 |
+
{"id": "GLA07", "sev": "error", "title": "Anonymous download from gitlab.com", "file": "workflows, Dockerfile ADD, curl, wget", "why": "/-/archive, /-/raw and release downloads with no credentials are unauthenticated gitlab.com requests.", "fix": "Use the API route with a token (repository/archive.tar.gz?sha=REF, repository/files/PATH/raw?ref=REF) or the Codeberg archive/raw URL."},
|
| 60 |
+
{"id": "GLA08", "sev": "error", "title": "Submodule URL on gitlab.com over https", "file": ".gitmodules", "why": "git submodule update clones it with no credentials on every fresh CI workspace.", "fix": "url = git@gitlab.com:owner/repo.git, url = https://codeberg.org/owner/repo.git, or an insteadOf rule with a token before git submodule update."},
|
| 61 |
+
{"id": "GLA09", "sev": "error", "title": "gitlab.com package registry without a token", "file": ".npmrc, pip --index-url, pip.conf", "why": "The npm/PyPI/Maven package registry lives under /api/v4, so a token-less index URL is an unauthenticated API call per package.", "fix": "npm: //gitlab.com/api/v4/projects/ID/packages/npm/:_authToken=${GITLAB_TOKEN} · pip: https://${GITLAB_TOKEN_NAME}:${GITLAB_TOKEN}@gitlab.com/api/v4/... · Forgejo: https://codeberg.org/api/packages/OWNER/"},
|
| 62 |
+
{"id": "GLA10", "sev": "warning", "title": "Hosted runner shares the 60/hour per-IP allowance", "file": ".github/workflows/*.yml", "why": "GitHub-hosted runners come from shared cloud IP ranges, so the per-IP anonymous allowance is not yours alone.", "fix": "env: GITLAB_TOKEN: ${{ secrets.GITLAB_TOKEN }} on the job, used by every gitlab.com step, or a self-hosted runner."}
|
| 63 |
+
]
|
| 64 |
+
;</script>
|
| 65 |
+
<script>// Codeberg / GitLab.com Anonymous Fetch Lint — one engine for the extension and the web page.
|
| 66 |
+
// Source: GitLab blog "Rate limits on GitLab.com are changing" (2026-09-17): from 2026-10-19 a request
|
| 67 |
+
// that arrives with no credentials gets 60 requests per hour per IP address; over it you get HTTP 429.
|
| 68 |
+
(function () {
|
| 69 |
+
var RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.GLANON_RULES;
|
| 70 |
+
var BY = {};
|
| 71 |
+
RULES.forEach(function (r) { BY[r.id] = r; });
|
| 72 |
+
var CAP_DATE = '2026-10-19';
|
| 73 |
+
var TOKEN_URL = 'https://oauth2:${GITLAB_TOKEN}@gitlab.com/';
|
| 74 |
+
|
| 75 |
+
var ANON_URL = /https?:\/\/gitlab\.com\/([^\s"'#)]+)/i; // no userinfo before the host
|
| 76 |
+
var LINE_AUTH = /PRIVATE-TOKEN|JOB-TOKEN|Authorization:|private_token=|access_token=|job_token=|--netrc\b/i;
|
| 77 |
+
var HOSTED = /runs-on:\s*\[?\s*["']?(ubuntu|windows|macos)-/i;
|
| 78 |
+
|
| 79 |
+
function projectOf(path) { // "acme/cli/-/archive/..." -> {proj, rest}
|
| 80 |
+
var p = path.replace(/\.git(?=$|[@#\/])/, '');
|
| 81 |
+
var i = p.indexOf('/-/');
|
| 82 |
+
var proj = i >= 0 ? p.slice(0, i) : p.split(/[@#?]/)[0];
|
| 83 |
+
var segs = proj.split('/').filter(Boolean);
|
| 84 |
+
return { proj: proj, rest: i >= 0 ? p.slice(i + 3) : '', owner: segs[0] || 'OWNER', repo: segs[segs.length - 1] || 'REPO' };
|
| 85 |
+
}
|
| 86 |
+
|
| 87 |
+
function tokenLine(l) { return l.replace(/https?:\/\/gitlab\.com\//i, TOKEN_URL); }
|
| 88 |
+
function codebergLine(l) { return l.replace(/https?:\/\/gitlab\.com\//i, 'https://codeberg.org/'); }
|
| 89 |
+
|
| 90 |
+
function downloadFix(l, path) {
|
| 91 |
+
var pr = projectOf(path), enc = pr.proj.replace(/\//g, '%2F');
|
| 92 |
+
var m = pr.rest.match(/^archive\/([^\/]+)\/[^\s"']*?\.(tar\.gz|zip|tar\.bz2|tar)\b/);
|
| 93 |
+
if (m) return {
|
| 94 |
+
tok: 'curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" -L -o ARCHIVE https://gitlab.com/api/v4/projects/' + enc + '/repository/archive.' + m[2] + '?sha=' + m[1],
|
| 95 |
+
cb: 'https://codeberg.org/' + pr.owner + '/' + pr.repo + '/archive/' + m[1] + '.' + m[2] };
|
| 96 |
+
m = pr.rest.match(/^raw\/([^\/]+)\/([^\s"'?]+)/);
|
| 97 |
+
if (m) return {
|
| 98 |
+
tok: 'curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" https://gitlab.com/api/v4/projects/' + enc + '/repository/files/' + m[2].replace(/\//g, '%2F') + '/raw?ref=' + m[1],
|
| 99 |
+
cb: 'https://codeberg.org/' + pr.owner + '/' + pr.repo + '/raw/branch/' + m[1] + '/' + m[2] + ' (use /raw/tag/ for a tag)' };
|
| 100 |
+
return { tok: 'download it through /api/v4 with --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"', cb: codebergLine(l.trim()) };
|
| 101 |
+
}
|
| 102 |
+
|
| 103 |
+
function apiFix(l, path) {
|
| 104 |
+
var tok = /\bcurl\b/.test(l) ? l.trim().replace(/\bcurl\b/, 'curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}"')
|
| 105 |
+
: 'send the header PRIVATE-TOKEN: ${GITLAB_TOKEN} (or JOB-TOKEN: $CI_JOB_TOKEN inside GitLab CI)';
|
| 106 |
+
var m = path.match(/^api\/v4\/projects\/([^\/\s]+)%2F([^\/\s]+)\/releases/i);
|
| 107 |
+
var cb = m ? 'https://codeberg.org/api/v1/repos/' + m[1] + '/' + m[2] + '/releases' : 'the Forgejo API at https://codeberg.org/api/v1/';
|
| 108 |
+
return { tok: tok, cb: cb };
|
| 109 |
+
}
|
| 110 |
+
|
| 111 |
+
function check(text, opts) {
|
| 112 |
+
opts = opts || {};
|
| 113 |
+
var when = (opts.today && opts.today >= CAP_DATE) ? 'since ' + CAP_DATE : 'from ' + CAP_DATE;
|
| 114 |
+
var lines = String(text || '').split(/\r?\n/);
|
| 115 |
+
var findings = [];
|
| 116 |
+
var gitAuth = 0, hostedLine = 0, npmAuth = /gitlab\.com\/api\/v4\/[^\s]*:_authToken=/i.test(text);
|
| 117 |
+
lines.forEach(function (l, i) {
|
| 118 |
+
if (!gitAuth && ((/insteadOf/i.test(l) && /:\/\/[^\s\/"']+@gitlab\.com/i.test(l)) || /machine\s+gitlab\.com/i.test(l))) gitAuth = i + 1;
|
| 119 |
+
if (!hostedLine && HOSTED.test(l)) hostedLine = i + 1;
|
| 120 |
+
});
|
| 121 |
+
|
| 122 |
+
function add(id, line, extra, fixes) {
|
| 123 |
+
var r = BY[id];
|
| 124 |
+
var sev = r.sev;
|
| 125 |
+
var msg = r.title + '. ' + r.why;
|
| 126 |
+
if (sev !== 'info') msg += ' GitLab caps requests with no credentials at 60 per hour per IP ' + when + ' (HTTP 429 after that).';
|
| 127 |
+
if (extra) msg += ' ' + extra;
|
| 128 |
+
msg += ' Fix: ' + (fixes && fixes.tok ? fixes.tok : r.fix);
|
| 129 |
+
if (fixes && fixes.cb) msg += ' · Codeberg: ' + fixes.cb;
|
| 130 |
+
findings.push({ check: id, sev: sev, msg: msg, line: line });
|
| 131 |
+
}
|
| 132 |
+
|
| 133 |
+
lines.forEach(function (l, i) {
|
| 134 |
+
var n = i + 1;
|
| 135 |
+
if (/^\s*(#|\/\/)/.test(l)) return;
|
| 136 |
+
var t = l.trim();
|
| 137 |
+
|
| 138 |
+
if (/\bGOPROXY\s*[:=]\s*["']?direct\b/.test(l) || /\bGO(PRIVATE|NOPROXY)\s*[:=]\s*["']?[^\s"']*gitlab\.com/.test(l)) {
|
| 139 |
+
if (!gitAuth) add('GLA04', n, '', { tok: 'GOPROXY=https://proxy.golang.org,direct for public modules; for private ones: echo "machine gitlab.com login oauth2 password ${GITLAB_TOKEN}" >> ~/.netrc' });
|
| 140 |
+
return;
|
| 141 |
+
}
|
| 142 |
+
var gm = l.match(/^\s*(?:require\s+|replace\s+)?(gitlab\.com\/[\w.\-\/]+)\s+v\d/) || l.match(/\bgo\s+(?:get|install)\s+(?:-\S+\s+)*(gitlab\.com\/[\w.\-\/]+)/);
|
| 143 |
+
if (gm) {
|
| 144 |
+
var gp = gm[1].split('/');
|
| 145 |
+
add('GLA05', n, '', { tok: 'nothing to change while GOPROXY stays at proxy.golang.org', cb: 'replace ' + gp.slice(0, 3).join('/') + ' => codeberg.org/' + gp[1] + '/' + gp[gp.length - 1] + ' vX.Y.Z (a fork that keeps the module line)' });
|
| 146 |
+
return;
|
| 147 |
+
}
|
| 148 |
+
var sm = l.match(/(?:^|["'\s])gitlab:([\w.\-]+)\/([\w.\-]+)(#[^\s"']+)?/);
|
| 149 |
+
if (sm) {
|
| 150 |
+
var ref = sm[3] || '';
|
| 151 |
+
add('GLA03', n, '', { tok: 'git+ssh://git@gitlab.com/' + sm[1] + '/' + sm[2] + '.git' + ref, cb: 'git+https://codeberg.org/' + sm[1] + '/' + sm[2] + '.git' + ref });
|
| 152 |
+
return;
|
| 153 |
+
}
|
| 154 |
+
var m = l.match(ANON_URL);
|
| 155 |
+
if (!m || LINE_AUTH.test(l)) return;
|
| 156 |
+
var path = m[1];
|
| 157 |
+
|
| 158 |
+
if (/^api\/v4\/(projects|groups)\/[^\s"']*\/packages\//i.test(path)) {
|
| 159 |
+
if (npmAuth) return;
|
| 160 |
+
var npm = /registry\s*=/.test(l);
|
| 161 |
+
var proj = (path.match(/^api\/v4\/(?:projects|groups)\/([^\/]+)/) || [])[1];
|
| 162 |
+
add('GLA09', n, '', npm
|
| 163 |
+
? { tok: '//gitlab.com/api/v4/projects/' + proj + '/packages/npm/:_authToken=${GITLAB_TOKEN}', cb: 'https://codeberg.org/api/packages/OWNER/npm/' }
|
| 164 |
+
: { tok: t.replace(/https?:\/\/gitlab\.com\//i, 'https://${GITLAB_TOKEN_NAME}:${GITLAB_TOKEN}@gitlab.com/'), cb: 'https://codeberg.org/api/packages/OWNER/pypi/simple' });
|
| 165 |
+
return;
|
| 166 |
+
}
|
| 167 |
+
if (/^api\/v4\//i.test(path)) { add('GLA06', n, '', apiFix(l, path)); return; }
|
| 168 |
+
if (/\burl\s*=\s*https?:\/\/gitlab\.com\//i.test(l)) {
|
| 169 |
+
if (gitAuth) return;
|
| 170 |
+
var sp = projectOf(path);
|
| 171 |
+
add('GLA08', n, '', { tok: 'url = git@gitlab.com:' + sp.proj + '.git', cb: 'url = https://codeberg.org/' + sp.owner + '/' + sp.repo + '.git' });
|
| 172 |
+
return;
|
| 173 |
+
}
|
| 174 |
+
if (/\/-\/(archive|raw|releases|jobs|package_files)\//.test(path)) { add('GLA07', n, '', downloadFix(l, path)); return; }
|
| 175 |
+
if (/git\+https?:\/\/gitlab\.com\//i.test(l)) {
|
| 176 |
+
if (gitAuth) return;
|
| 177 |
+
if (/^\s*"[^"]+"\s*:\s*"git\+https/.test(l)) {
|
| 178 |
+
var np = projectOf(path), nref = (path.match(/#[^\s"']+/) || [''])[0];
|
| 179 |
+
add('GLA03', n, '', { tok: 'git+ssh://git@gitlab.com/' + np.proj + '.git' + nref, cb: 'git+https://codeberg.org/' + np.owner + '/' + np.repo + '.git' + nref });
|
| 180 |
+
} else {
|
| 181 |
+
add('GLA02', n, '', { tok: tokenLine(t), cb: codebergLine(t) });
|
| 182 |
+
}
|
| 183 |
+
return;
|
| 184 |
+
}
|
| 185 |
+
if (/\bgit\s+(clone|fetch|pull|ls-remote|submodule\s+add)\b/.test(l)) {
|
| 186 |
+
if (gitAuth) return;
|
| 187 |
+
add('GLA01', n, '', { tok: tokenLine(t), cb: codebergLine(t) });
|
| 188 |
+
return;
|
| 189 |
+
}
|
| 190 |
+
if (/\b(curl|wget|Invoke-WebRequest|iwr)\b|^\s*ADD\s/.test(l)) { add('GLA07', n, '', downloadFix(l, path)); return; }
|
| 191 |
+
});
|
| 192 |
+
|
| 193 |
+
var hot = findings.filter(function (f) { return f.sev !== 'info'; }).length;
|
| 194 |
+
if (hostedLine && hot) {
|
| 195 |
+
add('GLA10', hostedLine, 'This job makes ' + hot + ' token-less gitlab.com request' + (hot === 1 ? '' : 's') + ' per run.', null);
|
| 196 |
+
}
|
| 197 |
+
findings.sort(function (a, b) { return a.line - b.line; });
|
| 198 |
+
return { findings: findings };
|
| 199 |
+
}
|
| 200 |
+
|
| 201 |
+
var api = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length, CAP_DATE: CAP_DATE };
|
| 202 |
+
if (typeof module !== 'undefined') module.exports = api;
|
| 203 |
+
if (typeof window !== 'undefined') window.GLANON_ENGINE = api;
|
| 204 |
+
})();
|
| 205 |
+
</script>
|
| 206 |
+
<script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
|
| 207 |
+
'use strict';
|
| 208 |
+
function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"'); }
|
| 209 |
+
function summaryLine(res) {
|
| 210 |
+
const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
|
| 211 |
+
return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
|
| 212 |
+
}
|
| 213 |
+
function toText(res, label) {
|
| 214 |
+
const f = (res && res.findings) || [];
|
| 215 |
+
const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
|
| 216 |
+
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 || '')); });
|
| 217 |
+
return lines.join('\n');
|
| 218 |
+
}
|
| 219 |
+
function toHtml(res) {
|
| 220 |
+
const f = (res && res.findings) || [];
|
| 221 |
+
if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
|
| 222 |
+
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('');
|
| 223 |
+
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>';
|
| 224 |
+
}
|
| 225 |
+
var GLANON_REPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
|
| 226 |
+
if (typeof module !== 'undefined' && module.exports) module.exports = GLANON_REPORT;
|
| 227 |
+
else window.GLANON_REPORT = GLANON_REPORT;
|
| 228 |
+
</script>
|
| 229 |
+
<script>
|
| 230 |
+
(function(){
|
| 231 |
+
var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
|
| 232 |
+
today.value=new Date().toISOString().slice(0,10);
|
| 233 |
+
function run(){ try { var res=window.GLANON_ENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.GLANON_REPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
|
| 234 |
+
go.onclick=run; if (src.value.trim()) run();
|
| 235 |
+
})();
|
| 236 |
+
</script>
|
| 237 |
+
|
| 238 |
+
|
| 239 |
+
<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.codeberg-gitlab-anonymous-fetch-lint" rel="noopener" data-yj-install-door="mp" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'codeberg-gitlab-anonymous-fetch-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/codeberg-gitlab-anonymous-fetch-lint" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'codeberg-gitlab-anonymous-fetch-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><pre style="margin:.5rem 0 0;padding:.6rem .8rem;background:#f3f5f8;border-radius:8px;font-size:.9rem;overflow:auto">ext install ReadyStack.codeberg-gitlab-anonymous-fetch-lint</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://getreadystack.com/api/buy/team" rel="nofollow" style="color:#0f7b3f;text-decoration:underline">Team? One key for every ReadyStack linter, 5 seats, $149 once</a></p></section>
|
| 240 |
+
<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 one email when this rule changes</b><div style="opacity:.78;margin:.35rem 0 .8rem">We watch the regulation and vendor sources behind Codeberg / GitLab.com Anonymous Fetch Lint every day. When a rule changes, you get a single email with what changed and the updated check. 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">Notify me</button></div><div id="yjao" hidden style="margin-top:.6rem;color:#0f7b3f">You're on the list. One email when it changes.</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:"codeberg-gitlab-anonymous-fetch-lint",lang:"en",source:"rule_alert"+((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);document.addEventListener("click",function(ev){var b=ev.target&&ev.target.closest?ev.target.closest("button,input[type=submit]"):null;if(b&&!b.closest("[data-yj],[data-yj-cta],[data-yj-install],[data-yj-alert],.yj-find,.yj-related,#yj-ask,.yjp"))mv(ev);},true);})();</script></section>
|
| 241 |
+
|
| 242 |
+
<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 full version <span class="yj-price">$29</span></h2><p class="yj-cta-paid" style="margin:.25rem 0 .6rem;color:#1f2a37">$29 once, one licence key per person or team seat: whole-workspace migration plan (each gitlab.com fetch mapped to its token or Codeberg replacement), a dated exposure report and a CI gate that fails a build adding a new anonymous fetch.</p><p class="yj-cta-pay" style="margin:.25rem 0 1rem;color:#5b6b7c;font-size:.94rem">One payment, one licence key for this tool. The key is shown right after payment.</p><p class="yj-cta-anchor" style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">GitLab Premium is $348 per user per year ($29 per user/month, billed annually) and does not lift the anonymous cap.</p><a href="https://getreadystack.com/api/buy/cl/polar_cl_yqBgzWBrXNKCqHBOSG0cZl9F9psXlpdxPFBSQ2744ag" 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-trust-strip-s191--><aside data-yj="yj-trust-strip" style="max-width:46rem;margin:-1.8rem auto 2.4rem;padding:.9rem 1.2rem;border:1px solid #d8dee9;border-radius:12px;background:#f6f8fb;font:14.5px/1.55 system-ui,-apple-system,Segoe UI,sans-serif;color:#2b3640"><p style="margin:0 0 .3rem;font-weight:600">Buying here, plainly</p><ul style="margin:0;padding-left:1.2rem"><li style="margin:.25rem 0">Payment runs on Whop’s checkout: your card details go to Whop, not to us, and Whop emails you the receipt.</li><li style="margin:.25rem 0">Your licence key shows on the next page right after payment and stays in your Whop account (Your purchases).</li><li style="margin:.25rem 0">Chose a subscription? Cancel any time in your Whop account (Orders → Cancel membership); access lasts until the end of the period you paid.</li><li style="margin:.25rem 0">Your inputs are calculated in your browser and are not sent to us (unless you ask for the result by email).</li><li style="margin:.25rem 0">Questions before buying? support@getreadystack.com — we answer within 2 business days.</li></ul></aside><!--/yj-trust-strip-s191-->
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
|
| 247 |
+
|
| 248 |
+
<!--yj-embed-s178--><details id="embed" data-yj="yj-embed" style="max-width:46rem;margin:1.6rem auto;padding:.85rem 1.1rem;border:1px solid #8884;border-radius:10px"><summary style="cursor:pointer;font-weight:600">Embed this tool on your site (free)</summary><p style="margin:.6rem 0;opacity:.85">Paste this code into your page. It runs in your visitor’s browser and stays up to date. Nothing your visitors type is stored.</p><textarea readonly rows="6" style="width:100%;font:12px/1.4 ui-monospace,Menlo,Consolas,monospace;border:1px solid #8886;border-radius:7px;padding:.5rem"><iframe src="https://getreadystack.com/embed/codeberg-gitlab-anonymous-fetch-lint/" title="Codeberg / GitLab.com Anonymous Fetch Lint — free online check" loading="lazy" data-rs-embed="en-codeberg-gitlab-anonymous-fetch-lint" style="width:100%;max-width:760px;height:640px;border:1px solid #e3e3e3;border-radius:10px"></iframe>
|
| 249 |
+
<p style="font-size:13px;margin:6px 0 0"><a href="https://getreadystack.com/tools/codeberg-gitlab-anonymous-fetch-lint">Codeberg / GitLab.com Anonymous Fetch Lint — free online check</a> by <a href="https://getreadystack.com/">ReadyStack</a></p>
|
| 250 |
+
<script>window.addEventListener("message",function(e){if(e.origin!=="https://getreadystack.com"||!e.data||e.data.type!=="readystack-embed")return;var f=document.querySelectorAll("iframe[data-rs-embed]");for(var i=0;i<f.length;i++){if(f[i].contentWindow===e.source)f[i].style.height=Math.min(e.data.h+6,8000)+"px";}});</script></textarea><p style="margin:.5rem 0 0"><button type="button" data-yj="yj-embed-copy" style="font:inherit;padding:8px 14px;border:1px solid #8884;border-radius:9px;background:transparent;cursor:pointer">Copy code</button> <a href="/embed/">All embeddable tools</a></p><p style="margin:.7rem 0 0;font-size:.92em;opacity:.9">For businesses: your name and logo instead of ours, and each visitor’s result + e-mail in your inbox (only when they tick consent) — Pro from $19/month. <a href="/embed/pro/?ref=embed_box">See Pro →</a></p></details><script data-yj="yj-embed">(function(){var b=document.querySelector("[data-yj=yj-embed-copy]");if(!b)return;var t=b.textContent;b.addEventListener("click",function(){var a=b.closest("details").querySelector("textarea");var v=a.value;function d(){b.textContent="Copied";setTimeout(function(){b.textContent=t;},1800);}if(navigator.clipboard&&navigator.clipboard.writeText){navigator.clipboard.writeText(v).then(d,function(){a.select();});}else{a.select();}});})();</script><!--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>
|
| 251 |
+
<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>
|
| 252 |
+
|
| 253 |
+
<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>
|
| 254 |
+
|
| 255 |
+
<script data-yj="yj-prefill-s181 v1">(function(){try{var q=new URLSearchParams(location.search);if(!q.toString())return;var P={"R": {"employee": ["employee|w-?2\\b|arbeitnehmer|angestellt|会社員|従業員|給与所得|trabajador|asalariad|empregad|clt\\b|salari", "Employee"], "self": ["self-?employ|freelanc|sole (trader|prop)|selbst(ä|ae)ndig|freiberuf|個人事業|フリーランス|aut(ó|o)nom|\\bmei\\b", "Freelancer / self-employed"], "landlord": ["landlord|rental|vermiet|大家|不動産|arrendador|alquiler|locador|aluguel", "Landlord"], "investor": ["invest|trader|anleger|投資|inversor|investidor", "Investor / trader"], "employer": ["employer|arbeitgeber|雇用主|事業主|empleador|empregador", "Employer"], "seller": ["seller|online shop|h(ä|ae)ndler|販売|vendedor|e-?commerce", "Online seller"]}, "C": {"US": "^(us|usa)$|united states|estados unidos|vereinigte staaten|アメリカ|米国", "UK": "^(uk|gb)$|united kingdom|great britain|reino unido|vereinigtes k|イギリス|英国", "DE": "^de$|germany|deutschland|alemania|alemanha|ドイツ", "JP": "^jp$|japan|jap(ó|ã|o)n?|日本", "ES": "^es$|spain|españa|espana|espanha|spanien|スペイン", "BR": "^br$|brazil|brasil|ブラジル"}, "B": "your 2027 dates", "L": "en"};function ours(el){return !!(el&&el.closest&&el.closest("[data-yj],[data-yj-cta],[data-yj-cal],.yj-email-gate,.yj-find,.yj-related,#yj-ask,#yjgo,#yjr,header,footer,nav"));}function fields(){var r=document.querySelector("main")||document.body;return [].filter.call(r.querySelectorAll("input,select,textarea"),function(el){var t=(el.type||"").toLowerCase();return !ours(el)&&!el.disabled&&["email","hidden","search","password","file","submit","button","reset","image"].indexOf(t)<0;});}function key(el){return el.getAttribute("data-k")||el.id||el.name||"";}function fire(el){["input","change"].forEach(function(n){try{el.dispatchEvent(new Event(n,{bubbles:true}))}catch(e){}});}function setv(el,v){v=String(v).slice(0,80);var t=(el.type||"").toLowerCase(),tg=el.tagName;if(tg=="SELECT"){var o=[].filter.call(el.options,function(o){return o.value==v||o.text.trim()==v})[0];if(!o)return false;el.value=o.value;}else if(t=="checkbox"||t=="radio"){if(!/^(1|0|true|false|on|off|yes|no)$/i.test(v))return false;el.checked=/^(1|true|on|yes)$/i.test(v);}else if(t=="number"||t=="range"){var n=parseFloat(v.replace(/\s/g,"").replace(",","."));if(!isFinite(n))return false;el.value=String(n);}else if(t=="date"){if(!/^\d{4}-\d{2}-\d{2}$/.test(v))return false;el.value=v;}else{if(/[<>]/.test(v))return false;el.value=v;}fire(el);return true;}function pick(el,rx){if(el.tagName!="SELECT")return false;var re=new RegExp(rx,"i");var o=[].filter.call(el.options,function(o){return re.test(o.value)||re.test(o.text)})[0];if(!o||el.value==o.value)return false;el.value=o.value;fire(el);return true;}function run(){var F=fields(),done={},n=0;q.forEach(function(v,k){if(n>=20||/^(ref|role|cc|utm_.*|fbclid|gclid)$/.test(k)||!/^[A-Za-z0-9_-]{1,40}$/.test(k))return;F.forEach(function(el){if(key(el)===k&&!done[k]&&setv(el,v)){done[k]=1;n++;}});});var cc=(q.get("cc")||"").toUpperCase().replace(/[^A-Z]/g,"");if(P.C[cc])F.forEach(function(el){var k=key(el);if(!done[k]&&/country|land|pa(i|í)s|pays|国/i.test(k+" "+(el.id||""))&&pick(el,P.C[cc])){done[k]=1;n++;}});var ro=(q.get("role")||"").replace(/[^a-z]/g,""),R=P.R[ro];if(R)F.forEach(function(el){var k=key(el);if(!done[k]&&el.tagName=="SELECT"&&[].some.call(el.options,function(o){return [].some.call(Object.keys(P.R),function(x){return new RegExp(P.R[x][0],"i").test(o.text)})})&&pick(el,R[0])){done[k]=1;n++;}});if(q.get("ref")=="calendar"){var h=document.querySelector("main h1")||document.querySelector("h1");var d=document.createElement("div");d.setAttribute("data-yj","yj-prefill-back");d.style.cssText="margin:.4rem 0 .8rem;font:15px/1.5 system-ui,sans-serif;opacity:.9";var a=document.createElement("a");a.href="/"+P.L+"/calendar/?ref=tool"+(R?"&role="+ro:"")+(P.C[cc]?"&cc="+cc:"");a.textContent=(R?R[1]+" \u00b7 ":"")+P.B+" \u2192";d.appendChild(a);if(h&&h.parentNode)h.parentNode.insertBefore(d,h.nextSibling);}document.documentElement.setAttribute("data-yj-prefilled",String(n));}if(document.readyState=="loading")document.addEventListener("DOMContentLoaded",run);else run();}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 {"&":"&","<":"<",">":">",'"':"""}[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-10-11">Updated 2026-10-11</time> · ReadyStack</p><script type="application/ld+json">{"@context":"https://schema.org","@type":"WebPage","dateModified":"2026-10-11","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/sku605371_result_card.jpg" alt="Migrate from anonymous gitlab.com fetches to Codeberg: find every line before the 60/hour cap" loading="lazy" style="max-width:100%;border-radius:6px"></p>
|
| 256 |
+
<p>From 2026-10-19 gitlab.com answers HTTP 429 after 60 anonymous fetches per hour per IP, and for DevOps engineers running GitHub Actions in the US, the UK or the EU the sample workflow below makes 7 of them on every run.</p>
|
| 257 |
+
<table><thead><tr><th>Line</th><th>Broken (no token)</th><th>Replacement</th></tr></thead><tbody><tr><td>L9</td><td>git clone https://gitlab.com/acme-tools/ci-scripts.git</td><td>https://oauth2:${GITLAB_TOKEN}@gitlab.com/... or codeberg.org</td></tr><tr><td>L11</td><td>pip install git+https://gitlab.com/acme-tools/pyhelpers.git@v2.3.0</td><td>git+https://codeberg.org/acme-tools/pyhelpers.git@v2.3.0</td></tr><tr><td>L14</td><td>GOPROXY: direct</td><td>GOPROXY=https://proxy.golang.org,direct</td></tr><tr><td>L17</td><td>curl https://gitlab.com/api/v4/projects/acme-tools%2Fcli/releases</td><td>curl --header "PRIVATE-TOKEN: ${GITLAB_TOKEN}" ...</td></tr><tr><td>L19</td><td>curl .../cli/-/archive/v1.4.2/cli-v1.4.2.tar.gz</td><td>https://codeberg.org/acme-tools/cli/archive/v1.4.2.tar.gz</td></tr><tr><td>L21</td><td>npm install gitlab:acme-tools/ui-kit#v3.1.0</td><td>git+https://codeberg.org/acme-tools/ui-kit.git#v3.1.0</td></tr><tr><td>L23</td><td>pip --index-url https://gitlab.com/api/v4/projects/41/packages/pypi/simple</td><td>a token in the index URL</td></tr></tbody></table>
|
| 258 |
+
<p>That is the output of Codeberg / GitLab.com Anonymous Fetch Lint on its bundled sample: 8 findings, 7 token-less fetches plus a warning on runs-on: ubuntu-latest.</p>
|
| 259 |
+
<h3>What GitLab changed</h3>
|
| 260 |
+
<p>From 2026-10-19, GitLab caps requests to gitlab.com that arrive with no credentials at 60 requests per hour per IP address; over that you get HTTP 429 with a Retry-After header (GitLab blog, 2026-09-17). Unauthenticated requests are capped no matter where they come from, including automation running against a paid account without credentials. Premium and Ultimate move in January 2027. GitLab Self-Managed and GitLab Dedicated limits stay with your operator; this is only a gitlab.com change.</p>
|
| 261 |
+
<p>The runner warning matters most. GitHub-hosted runners come from shared cloud IP ranges, so that per-IP allowance is not yours alone.</p>
|
| 262 |
+
<h3>Why grep is not enough</h3>
|
| 263 |
+
<p>Grep for gitlab.com also hits doc links and lines that already carry a token. The linter separates three kinds:</p>
|
| 264 |
+
<ul><li>anonymous fetches: git clone over https, pip git+https, npm gitlab: shorthand, curl to /api/v4, /-/archive and /-/raw downloads, .gitmodules URLs and package registry index URLs with no credentials;</li><li>authenticated fetches it leaves alone: a token in the URL, PRIVATE-TOKEN or JOB-TOKEN headers, an insteadOf rule with a token, a .netrc entry, SSH git@gitlab.com:;</li><li>map entries: a go.mod path on gitlab.com is fetched through proxy.golang.org by default, so it only turns into a problem when GOPROXY=direct or GOPRIVATE covers gitlab.com.</li></ul>
|
| 265 |
+
<p>A chatbot cannot see your files.</p>
|
| 266 |
+
<h3>Two ways out per line</h3>
|
| 267 |
+
<p>Every finding prints two replacement lines. The token line keeps you on gitlab.com: oauth2:${GITLAB_TOKEN}@ in the URL, a PRIVATE-TOKEN header for the API, a .netrc entry for Go. The Codeberg line is the same path on codeberg.org, with Forgejo's URL shapes: /archive/REF.tar.gz, /raw/branch/REF/PATH, /api/v1/repos/OWNER/REPO/releases and /api/packages/OWNER/ for registries.</p>
|
| 268 |
+
<p>Paying GitLab does not fix this on its own. GitLab Premium is $348 per user per year ($29 per user/month, billed annually) and does not lift the anonymous cap.</p>
|
| 269 |
+
<h3>Free and key</h3>
|
| 270 |
+
<p>The free part lists every anonymous fetch in the open file, with file, line and both replacement lines, from 10 rules. The licence key adds the whole-workspace migration plan, a dated exposure report and a CI gate that fails a build adding a new anonymous fetch.</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/sku605371_result_card.jpg" style="width:100%;max-width:360px;border-radius:10px;display:block;margin:0 auto"><source src="/media/sku605371_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 Codeberg / GitLab.com Anonymous Fetch Lint do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads workflows, Dockerfiles, go.mod, requirements.txt, package.json and .gitmodules and marks every gitlab.com request sent without a token, by file and line. From 2026-10-19, GitLab caps requests to gitlab.com that arrive with no credentials at 60 requests per hour per IP address; over that you get HTTP 429 with a Retry-After header (GitLab blog, 2026-09-17). Each finding gives the replacement line: token, SSH or codeberg.org.</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">DevOps and platform engineers whose GitHub Actions workflows, Dockerfiles or dependency manifests pull code, releases or packages from gitlab.com without signing in, and teams moving those repositories to Codeberg or Forgejo. Unauthenticated requests are capped no matter where they come from, including automation running against a paid account without credentials. GitHub-hosted runners share cloud IP ranges, so the per-IP allowance is not yours alone.</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 or grep for gitlab.com?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A chatbot cannot see your repository, and grep for gitlab.com also hits doc links and token URLs that are fine. This linter separates the anonymous fetches from the authenticated ones, knows that go.mod paths go through proxy.golang.org unless GOPROXY=direct, and writes the replacement line for each kind: git, pip, npm, Go, API, archive, submodule, registry.</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">Free, no key: every anonymous gitlab.com fetch in the open file, with file, line and the exact token or Codeberg line, from 10 rules. On the bundled sample workflow (_fixtures/dirty.yml) it reports 8 findings: 7 token-less gitlab.com fetches plus the hosted-runner warning, each with its token line and Codeberg line. A $29 licence key, once, adds the whole-workspace migration plan, a dated exposure report and a CI gate.</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 does the alternative cost?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">GitLab Premium is $348 per user per year ($29 per user/month, billed annually) and does not lift the anonymous cap. Unauthenticated requests are capped no matter where they come from, including automation running against a paid account without credentials. This linter finds every fetch that needs a token, SSH or a Codeberg mirror. The key is $29 once, one licence key per person or 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">When does the GitLab.com limit change next?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free accounts and unauthenticated requests move to the new limits on 2026-10-19, after preview windows on October 7 and October 14 from 15:00 to 19:00 UTC. Premium and Ultimate move in January 2027. GitLab Self-Managed and GitLab Dedicated limits stay with your operator; this is only a gitlab.com change (GitLab blog, 2026-09-17).</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Codeberg / GitLab.com Anonymous Fetch Lint do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads workflows, Dockerfiles, go.mod, requirements.txt, package.json and .gitmodules and marks every gitlab.com request sent without a token, by file and line. From 2026-10-19, GitLab caps requests to gitlab.com that arrive with no credentials at 60 requests per hour per IP address; over that you get HTTP 429 with a Retry-After header (GitLab blog, 2026-09-17). Each finding gives the replacement line: token, SSH or codeberg.org."}}, {"@type": "Question", "name": "Who is this for?", "acceptedAnswer": {"@type": "Answer", "text": "DevOps and platform engineers whose GitHub Actions workflows, Dockerfiles or dependency manifests pull code, releases or packages from gitlab.com without signing in, and teams moving those repositories to Codeberg or Forgejo. Unauthenticated requests are capped no matter where they come from, including automation running against a paid account without credentials. GitHub-hosted runners share cloud IP ranges, so the per-IP allowance is not yours alone."}}, {"@type": "Question", "name": "Why not just ask a chatbot or grep for gitlab.com?", "acceptedAnswer": {"@type": "Answer", "text": "A chatbot cannot see your repository, and grep for gitlab.com also hits doc links and token URLs that are fine. This linter separates the anonymous fetches from the authenticated ones, knows that go.mod paths go through proxy.golang.org unless GOPROXY=direct, and writes the replacement line for each kind: git, pip, npm, Go, API, archive, submodule, registry."}}, {"@type": "Question", "name": "What is free and what needs a licence key?", "acceptedAnswer": {"@type": "Answer", "text": "Free, no key: every anonymous gitlab.com fetch in the open file, with file, line and the exact token or Codeberg line, from 10 rules. On the bundled sample workflow (_fixtures/dirty.yml) it reports 8 findings: 7 token-less gitlab.com fetches plus the hosted-runner warning, each with its token line and Codeberg line. A $29 licence key, once, adds the whole-workspace migration plan, a dated exposure report and a CI gate."}}, {"@type": "Question", "name": "What does the alternative cost?", "acceptedAnswer": {"@type": "Answer", "text": "GitLab Premium is $348 per user per year ($29 per user/month, billed annually) and does not lift the anonymous cap. Unauthenticated requests are capped no matter where they come from, including automation running against a paid account without credentials. This linter finds every fetch that needs a token, SSH or a Codeberg mirror. The key is $29 once, one licence key per person or team seat."}}, {"@type": "Question", "name": "When does the GitLab.com limit change next?", "acceptedAnswer": {"@type": "Answer", "text": "Free accounts and unauthenticated requests move to the new limits on 2026-10-19, after preview windows on October 7 and October 14 from 15:00 to 19:00 UTC. Premium and Ultimate move in January 2027. GitLab Self-Managed and GitLab Dedicated limits stay with your operator; this is only a gitlab.com change (GitLab blog, 2026-09-17)."}}]}</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:605371,slug:"codeberg-gitlab-anonymous-fetch-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/gitlab-ci-migration-check?ref=related"><span class="yj-rel-t">GitLab CI/CD Migration Check for GitHub Actions</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/cron-schedule-lint?ref=related"><span class="yj-rel-t">Cron Schedule Lint for crontab, Kubernetes CronJob, GitHub Actions, Spring and</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/actions-deprecation-lint-2026?ref=related"><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?ref=related"><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/ssdf-attestation-workflow-audit?ref=related"><span class="yj-rel-t">SSDF Attestation Audit for GitHub Actions</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/xcode-26-sdk-gate?ref=related"><span class="yj-rel-t">Xcode 26 SDK Gate for GitHub Actions</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><!--yj-trust-foot-s191--><footer data-yj="yj-trust-foot" style="margin:3rem auto 0;padding:1.2rem 1rem 2rem;border-top:1px solid rgba(128,128,128,.28);max-width:60rem;font:14px/1.6 system-ui,-apple-system,Segoe UI,sans-serif;color:#5b6570;text-align:center"><nav aria-label="ReadyStack" style="display:flex;gap:.4rem 1.1rem;justify-content:center;flex-wrap:wrap"><a href="/about" style="color:inherit">About</a><a href="/verify/" style="color:inherit">How we verify</a><a href="/privacy" style="color:inherit">Privacy</a><a href="/terms" style="color:inherit">Terms</a><a href="/contact.html" style="color:inherit">Contact</a><a href="mailto:support@getreadystack.com" style="color:inherit">support@getreadystack.com</a></nav><p style="margin:.6rem 0 0">🔒 Secure checkout by Whop · © 2026 ReadyStack</p></footer><!--/yj-trust-foot-s191--></body>
|
| 271 |
</html>
|