getreadystack commited on
Commit
cb2f177
·
verified ·
1 Parent(s): 5a6eccb

Upload index.html with huggingface_hub

Browse files
Files changed (1) hide show
  1. index.html +239 -17
index.html CHANGED
@@ -1,19 +1,241 @@
1
  <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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>Firebase Deploy Leak Audit — free online check</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
+ <!--yj-canon-s101--><link rel="canonical" href="https://getreadystack.com/tools/firebase-deploy-leak-audit"></head>
19
+ <body>
20
+ <div class="wrap">
21
+ <h1>Firebase Deploy Leak Audit</h1>
22
+ <p class="lede">Audits firebase.json before you run firebase deploy: ignore lists that let .env, source maps and admin keys reach the public bundle, catch-all public roots, and missing response headers. Runs entirely in your browser — nothing is uploaded.</p>
23
+ <textarea id="src" spellcheck="false">{
24
+ "hosting": {
25
+ "public": ".",
26
+ "ignore": ["node_modules"],
27
+ "rewrites": [{ "source": "**", "destination": "/index.html" }],
28
+ "headers": [
29
+ {
30
+ "source": "**/*.@(js|css)",
31
+ "headers": [{ "key": "Cache-Control", "value": "max-age=31536000, immutable" }]
32
+ },
33
+ {
34
+ "source": "**/*.html",
35
+ "headers": [{ "key": "Cache-Control", "value": "max-age=31536000" }]
36
+ },
37
+ {
38
+ "source": "**",
39
+ "headers": [{ "key": "Access-Control-Allow-Origin", "value": "*" }]
40
+ }
41
+ ]
42
+ },
43
+ "firestore": { "rules": "firestore.rules", "indexes": "firestore.indexes.json" },
44
+ "emulators": { "hosting": { "port": 5000 } }
45
+ }</textarea>
46
+ <div class="bar"><button id="go">Check</button><label>as of <input type="date" id="today"></label></div>
47
+ <div id="out" aria-live="polite"></div>
48
+ <div id="paid"><strong>Free here:</strong> Audit the firebase.json you have open and name every file your next firebase deploy would publish that should never be public.. <strong>In the editor:</strong> Audit every firebase.json in a monorepo in one run and export the findings as a Markdown or JSON report you can commit, attach to a release, or hand to a client. — that part asks for a licence key.</div>
49
+ <p class="note">Same engine as the VS Code extension, byte for byte.</p>
50
+ </div>
51
+ <script>var FDL_RULES = [
52
+ {"id":"json_parse","sev":"high","title":"firebase.json is not valid JSON","msg":"firebase.json does not parse as JSON, so the CLI cannot read it and nothing below could be checked. Fix the syntax first.","fix":"Run `firebase deploy --only hosting --dry-run` and fix the reported position."},
53
+ {"id":"public_is_repo_root","sev":"high","title":"public root is the whole repository","msg":"hosting.public points at the repository root, so every file the ignore list misses ships to the public CDN — including .git history, README drafts and local scripts.","fix":"Point hosting.public at your build output directory (dist, build, public)."},
54
+ {"id":"public_missing","sev":"med","title":"no public directory declared","msg":"This hosting block declares neither `public` nor `source`, so `firebase deploy` will refuse the target or fall back to a directory you did not choose.","fix":"Add \"public\": \"\u003cyour build output\u003e\" to the hosting block."},
55
+ {"id":"ignore_absent","sev":"med","title":"no ignore list — defaults only","msg":"With no ignore list Firebase applies its three defaults (firebase.json, **/.*, **/node_modules/**). Source maps, service-account JSON and backup files inside the public directory are still uploaded.","fix":"Add an explicit ignore array that repeats the three defaults and adds **/*.map and key files."},
56
+ {"id":"missing_dotfiles","sev":"high","title":"ignore list drops the dotfile default","msg":"Supplying hosting.ignore REPLACES Firebase's defaults, and this list has no **/.* entry. Every dotfile under the public root — .env, .env.production, .npmrc, .git — is uploaded and served.","fix":"Add \"**/.*\" back to the ignore array."},
57
+ {"id":"env_not_ignored","sev":"high","title":".env files are not excluded","msg":"Nothing in the ignore list excludes .env files, so a .env or .env.production sitting in the build output is fetchable at https://\u003csite\u003e/.env by anyone who guesses the path.","fix":"Add \"**/.env*\" (and keep \"**/.*\") to hosting.ignore."},
58
+ {"id":"sourcemap_not_ignored","sev":"high","title":"source maps ship to production","msg":"No ignore entry matches *.map, so the bundler's .map files publish next to the minified code and hand any visitor your unminified source, comments and internal paths.","fix":"Add \"**/*.map\" to hosting.ignore, or turn sourcemaps off in the production build."},
59
+ {"id":"service_account_not_ignored","sev":"high","title":"admin key files are not excluded","msg":"No ignore entry matches a service-account key (serviceAccount*.json, *adminsdk*.json, *.pem). One of those left in the build folder is a full Firebase Admin credential served over HTTPS.","fix":"Add \"**/*serviceAccount*.json\", \"**/*adminsdk*.json\" and \"**/*.pem\" to hosting.ignore."},
60
+ {"id":"missing_node_modules","sev":"med","title":"node_modules is not fully excluded","msg":"The ignore list has no \"**/node_modules/**\" entry, so nested dependency folders under the public root are uploaded file by file — a slow deploy that also publishes package sources.","fix":"Add \"**/node_modules/**\" to hosting.ignore."},
61
+ {"id":"missing_firebase_json","sev":"low","title":"firebase.json itself is not ignored","msg":"\"firebase.json\" is a Firebase default that your custom ignore list dropped, so this config — target names, rewrite map, header policy — is served from your own domain.","fix":"Add \"firebase.json\" to the ignore array."},
62
+ {"id":"headers_absent","sev":"high","title":"no response headers configured","msg":"This hosting block sets no headers at all, so the site answers with Firebase defaults: no Content-Security-Policy, no HSTS, no nosniff and no framing rule.","fix":"Add a headers entry with source \"**\" and the four baseline security headers."},
63
+ {"id":"csp_missing","sev":"high","title":"no Content-Security-Policy header","msg":"No headers rule sets Content-Security-Policy, so any injected or third-party script on the page runs with full privileges and can read the signed-in user's Firebase ID token.","fix":"Add a Content-Security-Policy header on the \"**\" source, starting from default-src 'self'."},
64
+ {"id":"hsts_missing","sev":"med","title":"no Strict-Transport-Security header","msg":"Without Strict-Transport-Security a first visit over http:// can be intercepted before the redirect to https:// completes.","fix":"Add \"Strict-Transport-Security\": \"max-age=31536000; includeSubDomains\"."},
65
+ {"id":"nosniff_missing","sev":"med","title":"no X-Content-Type-Options header","msg":"Without X-Content-Type-Options: nosniff, a file a user uploaded and you serve back can be sniffed into an executable content type by the browser.","fix":"Add \"X-Content-Type-Options\": \"nosniff\"."},
66
+ {"id":"frame_missing","sev":"med","title":"page can be framed by any site","msg":"Neither X-Frame-Options nor a CSP frame-ancestors directive is set, so your signed-in app can be loaded in an invisible iframe on someone else's domain.","fix":"Add \"X-Frame-Options\": \"DENY\" or frame-ancestors 'none' in the CSP."},
67
+ {"id":"cors_wildcard","sev":"high","title":"Access-Control-Allow-Origin is *","msg":"A headers rule returns Access-Control-Allow-Origin: * on a broad source, so every other origin can read the responses this site serves, including any JSON the app fetches from its own path.","fix":"Scope the header to the exact asset paths that need it, or name the allowed origin."},
68
+ {"id":"html_immutable_cache","sev":"med","title":"HTML cached long-term","msg":"An HTML source is served with a long max-age, so browsers and the CDN keep serving the previous index.html after a deploy and users load old asset hashes that no longer exist.","fix":"Serve HTML with \"Cache-Control\": \"max-age=0, must-revalidate\" and keep long caching for hashed assets only."}
69
+ ]
70
+ ;</script>
71
+ <script>// Firebase Deploy Leak Audit — one brain, used by the VS Code extension and by the free web page.
72
+ const RULES = (typeof module !== 'undefined') ? require('./rules.json') : window.FDL_RULES;
73
+ const BY_ID = {};
74
+ RULES.forEach(function (r) { BY_ID[r.id] = r; });
75
+
76
+ function lineOf(text, needle) {
77
+ const lines = String(text).split(/\r?\n/);
78
+ for (let i = 0; i < lines.length; i++) if (lines[i].indexOf(needle) !== -1) return i + 1;
79
+ return 1;
80
+ }
81
+
82
+ function hit(out, text, id, where, extra) {
83
+ const r = BY_ID[id];
84
+ if (!r) return;
85
+ out.push({
86
+ check: id,
87
+ sev: r.sev,
88
+ msg: (extra ? extra + ' ' : '') + r.msg + ' Fix: ' + r.fix,
89
+ line: lineOf(text, where || '"hosting"')
90
+ });
91
+ }
92
+
93
+ function asArray(v) { return Array.isArray(v) ? v : (v ? [v] : []); }
94
+
95
+ // every header key/value pair declared anywhere in this hosting block
96
+ function headerPairs(h) {
97
+ const pairs = [];
98
+ asArray(h.headers).forEach(function (entry) {
99
+ const src = entry && entry.source ? String(entry.source) : '';
100
+ asArray(entry && entry.headers).forEach(function (p) {
101
+ if (!p || !p.key) return;
102
+ pairs.push({ source: src, key: String(p.key).toLowerCase(), value: String(p.value == null ? '' : p.value) });
103
+ });
104
+ });
105
+ return pairs;
106
+ }
107
+
108
+ function hasHeader(pairs, key) {
109
+ return pairs.some(function (p) { return p.key === key; });
110
+ }
111
+
112
+ function check(text, opts) {
113
+ opts = opts || {};
114
+ const findings = [];
115
+ let doc;
116
+ try {
117
+ doc = JSON.parse(String(text));
118
+ } catch (e) {
119
+ hit(findings, text, 'json_parse', '{', '(' + e.message + ')');
120
+ return { findings: findings };
121
+ }
122
+ const blocks = asArray(doc && doc.hosting);
123
+ if (!blocks.length) return { findings: findings };
124
+
125
+ blocks.forEach(function (h, i) {
126
+ if (!h || typeof h !== 'object') return;
127
+ const label = blocks.length > 1 ? '[hosting #' + (i + 1) + (h.target ? ' target ' + h.target : '') + ']' : '';
128
+ const anchor = h.public !== undefined ? '"public"' : '"hosting"';
129
+
130
+ // --- where the bundle comes from -------------------------------------
131
+ const pub = h.public === undefined ? null : String(h.public).trim();
132
+ if (pub !== null && (pub === '.' || pub === './' || pub === '' || pub === '/')) {
133
+ hit(findings, text, 'public_is_repo_root', '"public"', label);
134
+ }
135
+ if (pub === null && h.source === undefined) {
136
+ hit(findings, text, 'public_missing', anchor, label);
137
+ }
138
+
139
+ // --- what the ignore list lets through -------------------------------
140
+ const ignore = Array.isArray(h.ignore) ? h.ignore.map(function (s) { return String(s).toLowerCase(); }) : null;
141
+ const covers = function (frag) { return ignore !== null && ignore.some(function (e) { return e.indexOf(frag) !== -1; }); };
142
+ const dotfiles = covers('**/.*');
143
+
144
+ if (ignore === null) {
145
+ hit(findings, text, 'ignore_absent', anchor, label);
146
+ } else {
147
+ if (!dotfiles) hit(findings, text, 'missing_dotfiles', '"ignore"', label);
148
+ if (!covers('node_modules/**')) hit(findings, text, 'missing_node_modules', '"ignore"', label);
149
+ if (!ignore.some(function (e) { return e === 'firebase.json' || e.indexOf('firebase*.json') !== -1; })) {
150
+ hit(findings, text, 'missing_firebase_json', '"ignore"', label);
151
+ }
152
+ }
153
+ if (!covers('.env') && !dotfiles) hit(findings, text, 'env_not_ignored', ignore === null ? anchor : '"ignore"', label);
154
+ if (!covers('.map')) hit(findings, text, 'sourcemap_not_ignored', ignore === null ? anchor : '"ignore"', label);
155
+ if (!covers('serviceaccount') && !covers('adminsdk') && !covers('.pem') && !covers('-key.json')) {
156
+ hit(findings, text, 'service_account_not_ignored', ignore === null ? anchor : '"ignore"', label);
157
+ }
158
+
159
+ // --- what the CDN answers with ---------------------------------------
160
+ const pairs = headerPairs(h);
161
+ if (!pairs.length) {
162
+ hit(findings, text, 'headers_absent', anchor, label);
163
+ } else {
164
+ const csp = pairs.filter(function (p) { return p.key === 'content-security-policy'; });
165
+ if (!csp.length) hit(findings, text, 'csp_missing', '"headers"', label);
166
+ if (!hasHeader(pairs, 'strict-transport-security')) hit(findings, text, 'hsts_missing', '"headers"', label);
167
+ if (!hasHeader(pairs, 'x-content-type-options')) hit(findings, text, 'nosniff_missing', '"headers"', label);
168
+ const framed = hasHeader(pairs, 'x-frame-options') ||
169
+ csp.some(function (p) { return p.value.toLowerCase().indexOf('frame-ancestors') !== -1; });
170
+ if (!framed) hit(findings, text, 'frame_missing', '"headers"', label);
171
+ }
172
+ pairs.forEach(function (p) {
173
+ if (p.key === 'access-control-allow-origin' && p.value.trim() === '*') {
174
+ hit(findings, text, 'cors_wildcard', 'Access-Control-Allow-Origin', label);
175
+ }
176
+ if (p.key === 'cache-control' && /html/i.test(p.source)) {
177
+ const m = /max-age\s*=\s*(\d+)/i.exec(p.value);
178
+ if (m && Number(m[1]) > 3600) {
179
+ hit(findings, text, 'html_immutable_cache', p.source, label + ' (max-age=' + m[1] + ' on ' + p.source + ')');
180
+ }
181
+ }
182
+ });
183
+ });
184
+
185
+ findings.sort(function (a, b) {
186
+ const rank = { high: 0, med: 1, low: 2 };
187
+ return (rank[a.sev] - rank[b.sev]) || (a.line - b.line);
188
+ });
189
+ return { findings: findings };
190
+ }
191
+
192
+ const OUT = { engine: { check: check }, RULES: RULES, RULE_COUNT: RULES.length };
193
+ if (typeof module !== 'undefined') module.exports = OUT;
194
+ if (typeof window !== 'undefined') window.FDLENGINE = OUT;
195
+ </script>
196
+ <script>// ⛔뼈대 — kit/scaffold.py 가 찍는다 (s143). findings [{check,sev,msg,line?}] → 글/HTML. 같은 파일이 Node 와 브라우저에서 산다.
197
+ 'use strict';
198
+ function escapeHtml(s) { return String(s == null ? '' : s).replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;'); }
199
+ function summaryLine(res) {
200
+ const f = (res && res.findings) || []; const e = f.filter(function (x) { return String(x.sev || 'error').toLowerCase().startsWith('err'); }).length;
201
+ return f.length ? (f.length + ' finding' + (f.length === 1 ? '' : 's') + ' (' + e + ' error' + (e === 1 ? '' : 's') + ')') : 'Clean — no findings.';
202
+ }
203
+ function toText(res, label) {
204
+ const f = (res && res.findings) || [];
205
+ const lines = ['== ' + (label || '') + ' — ' + summaryLine(res)];
206
+ 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 || '')); });
207
+ return lines.join('\n');
208
+ }
209
+ function toHtml(res) {
210
+ const f = (res && res.findings) || [];
211
+ if (!f.length) return '<p class="ok">' + escapeHtml(summaryLine(res)) + '</p>';
212
+ 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('');
213
+ 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>';
214
+ }
215
+ var FDLREPORT = { escapeHtml: escapeHtml, summaryLine: summaryLine, toText: toText, toHtml: toHtml };
216
+ if (typeof module !== 'undefined' && module.exports) module.exports = FDLREPORT;
217
+ else window.FDLREPORT = FDLREPORT;
218
+ </script>
219
+ <script>
220
+ (function(){
221
+ var src=document.getElementById('src'), out=document.getElementById('out'), go=document.getElementById('go'), today=document.getElementById('today');
222
+ today.value=new Date().toISOString().slice(0,10);
223
+ function run(){ try { var res=window.FDLENGINE.engine.check(src.value,{today:today.value}); out.innerHTML=window.FDLREPORT.toHtml(res); } catch(e){ out.innerHTML='<p class="sev error">Could not run: '+String(e.message||e)+'</p>'; } }
224
+ go.onclick=run; if (src.value.trim()) run();
225
+ })();
226
+ </script>
227
+
228
+ <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 Firebase Deploy Leak Audit 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:"firebase-deploy-leak-audit",lang:"en",source:"rule_alert"})}).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></section>
229
+
230
+
231
+ <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://open-vsx.org/extension/readystack/firebase-deploy-leak-audit" rel="noopener" data-yj-install-door="ovsx" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'firebase-deploy-leak-audit',src:'ovsx',path:location.pathname})],{type:'application/json'}))}catch(e){}" style="display:inline-block;margin:0 .5rem .5rem 0;padding:.6rem 1rem;border-radius:8px;background:#0f7b3f;color:#fff;text-decoration:none;font-weight:600">Open VSX (Cursor, VSCodium)</a><a href="https://www.npmjs.com/package/@readystack/firebase-deploy-leak-audit" rel="noopener" data-yj-install-door="npm" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'firebase-deploy-leak-audit',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://hub.docker.com/r/getreadystack/firebase-deploy-leak-audit" rel="noopener" data-yj-install-door="docker" onclick="try{navigator.sendBeacon('/api/ev',new Blob([JSON.stringify({t:'install',slug:'firebase-deploy-leak-audit',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/firebase-deploy-leak-audit &lt;file&gt;
232
+ docker run --rm -v &quot;$PWD:/w&quot; getreadystack/firebase-deploy-leak-audit /w</pre><p style="margin:.6rem 0 0;font-size:.92rem;color:#41505f"><a href="https://buy.polar.sh/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>
233
+ <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">Reads firebase.json and names the files your next firebase deploy would publish to the open web</p><p style="margin:0 0 1rem;color:#5b6b7c;font-size:.94rem">A freelance web-security reviewer bills around $150 an hour.</p><a href="https://buy.polar.sh/polar_cl_s056vzYWEEtw0Muvdls6QOXpqf47Huzm8zNrC3JL2Ya" 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>
234
+ <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><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 Firebase Deploy Leak Audit actually do?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">It reads firebase.json and applies 17 rules to it before you deploy. Seven rules cover what the ignore list lets through — .env files, source maps, service-account keys, dotfiles, node_modules. The rest cover the public root and the response headers Firebase Hosting will send. Each finding names the line, the exposure, and the entry to add.</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 it for?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Solo developers and two-to-five person teams shipping a React, Vue, Svelte or Next.js build to Firebase Hosting, especially when firebase.json was generated by an AI assistant or copied from a starter. It is for the person who runs firebase deploy themselves and has no reviewer between them and production.</p></details><details style="border-bottom:1px solid #e6e9ed;padding:10px 0"><summary style="cursor:pointer;font:600 15px/1.4 system-ui,sans-serif;color:#1b1f24">Why is a chatbot or a generic linter not enough?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Firebase replaces its three default ignore patterns the moment you supply your own array. A generic JSON linter sees valid JSON. A chatbot reasons about the lines present in the file you paste, not about the default that silently vanished when you added one entry. This tool checks for the absence.</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 does the $29 licence add?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">Free covers one file completely: open a firebase.json, get all 17 checks with every finding shown, nothing truncated or watermarked, in VS Code or on the web page. The licence adds a different axis — every firebase.json in a monorepo scanned in one run, and an exported Markdown or JSON report you own.</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 me otherwise?</summary><p style="margin:8px 0 2px;font:400 14.5px/1.6 system-ui,sans-serif;color:#33404b">A freelance web-security reviewer bills around $150 an hour, and a configuration review is rarely a single hour. The cheaper comparison is the incident: a leaked .env in a public bundle means rotating every key it held and, in the EU, a GDPR Article 33 notification within 72 hours of becoming aware.</p></details><script type="application/ld+json" data-yj="yj-faq-ld">{"@context": "https://schema.org", "@type": "FAQPage", "mainEntity": [{"@type": "Question", "name": "What does Firebase Deploy Leak Audit actually do?", "acceptedAnswer": {"@type": "Answer", "text": "It reads firebase.json and applies 17 rules to it before you deploy. Seven rules cover what the ignore list lets through — .env files, source maps, service-account keys, dotfiles, node_modules. The rest cover the public root and the response headers Firebase Hosting will send. Each finding names the line, the exposure, and the entry to add."}}, {"@type": "Question", "name": "Who is it for?", "acceptedAnswer": {"@type": "Answer", "text": "Solo developers and two-to-five person teams shipping a React, Vue, Svelte or Next.js build to Firebase Hosting, especially when firebase.json was generated by an AI assistant or copied from a starter. It is for the person who runs firebase deploy themselves and has no reviewer between them and production."}}, {"@type": "Question", "name": "Why is a chatbot or a generic linter not enough?", "acceptedAnswer": {"@type": "Answer", "text": "Firebase replaces its three default ignore patterns the moment you supply your own array. A generic JSON linter sees valid JSON. A chatbot reasons about the lines present in the file you paste, not about the default that silently vanished when you added one entry. This tool checks for the absence."}}, {"@type": "Question", "name": "What is free and what does the $29 licence add?", "acceptedAnswer": {"@type": "Answer", "text": "Free covers one file completely: open a firebase.json, get all 17 checks with every finding shown, nothing truncated or watermarked, in VS Code or on the web page. The licence adds a different axis — every firebase.json in a monorepo scanned in one run, and an exported Markdown or JSON report you own."}}, {"@type": "Question", "name": "What would this cost me otherwise?", "acceptedAnswer": {"@type": "Answer", "text": "A freelance web-security reviewer bills around $150 an hour, and a configuration review is rarely a single hour. The cheaper comparison is the incident: a leaked .env in a public bundle means rotating every key it held and, in the EU, a GDPR Article 33 notification within 72 hours of becoming aware."}}]}</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:0,slug:"firebase-deploy-leak-audit",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, 7-day refund.</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/linker-map-auditor"><span class="yj-rel-t">Linker Map Auditor - FLASH &amp; RAM budget for GNU ld map files</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/mv3-manifest-preflight"><span class="yj-rel-t">MV3 Manifest Preflight - Chrome Web Store</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/runtime-eol-deploy-block-audit"><span class="yj-rel-t">Runtime EOL and Deploy-Block Audit for Docker, CI and AWS Lambda</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/spdx-license-field-lint"><span class="yj-rel-t">SPDX License Field Lint for package.json, pyproject.toml and Cargo.toml</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/tfstate-secret-leak-lint"><span class="yj-rel-t">Terraform State Secret Leak Lint</span><span class="yj-rel-p">$29</span></a><a class="yj-rel-c" href="https://getreadystack.com/tools/vsix-publish-lint"><span class="yj-rel-t">VSIX Publish Lint</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-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>
235
+
236
+
237
+ <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>
238
+
239
+ <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>
240
+ </body>
241
  </html>