@justmpm/firebase-audit 0.5.10 → 0.5.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-JPHERTX5.js → chunk-ZE5K5HRR.js} +1 -1
- package/dist/{chunk-GR2SM3OL.js → chunk-ZNVQGZMJ.js} +25 -10
- package/dist/cli.js +2 -2
- package/dist/index.js +2 -2
- package/dist/mcp-cli.js +2 -2
- package/dist/mcp.js +2 -2
- package/dist/{scan-YY5UY35J.js → scan-M6OYRIKL.js} +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
|
@@ -1228,15 +1228,17 @@ async function scan(rootDir, opts = {}) {
|
|
|
1228
1228
|
return `ev-storage-${prefix}-${evCounter}`;
|
|
1229
1229
|
};
|
|
1230
1230
|
for (const m of parseStorageAllows(clean)) {
|
|
1231
|
-
if (m.condition !== void 0 && stripOuterParens(m.condition).toLowerCase().replace(/\s+/g, "") === "false") {
|
|
1232
|
-
continue;
|
|
1233
|
-
}
|
|
1234
1231
|
const ops = expandStorageOps(m.target);
|
|
1235
1232
|
const key = conditionKeyForFingerprint(m.condition);
|
|
1236
1233
|
const singleStorage = m.condition === void 0 ? void 0 : resolveHelperConditionDetailed(m.condition, storageHelpers);
|
|
1237
1234
|
const inlinedStorage = m.condition === void 0 ? void 0 : inlineHelpersInCondition(m.condition, storageHelpers);
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1235
|
+
const singleResolved = singleStorage !== void 0 && singleStorage !== m.condition ? singleStorage : void 0;
|
|
1236
|
+
const effectiveCond = m.condition === void 0 ? void 0 : singleResolved !== void 0 ? helperBodyToCondition(singleResolved) : inlinedStorage !== void 0 && inlinedStorage !== m.condition ? inlinedStorage : m.condition;
|
|
1237
|
+
const normEffFalse = effectiveCond === void 0 ? "" : stripOuterParens(effectiveCond).toLowerCase().replace(/\s+/g, "");
|
|
1238
|
+
if (normEffFalse === "false") {
|
|
1239
|
+
continue;
|
|
1240
|
+
}
|
|
1241
|
+
const normCond = `${singleResolved ?? ""} ${effectiveCond ?? ""}`.toLowerCase().replace(/\s+/g, "");
|
|
1240
1242
|
const hasAuth = normCond.includes("request.auth");
|
|
1241
1243
|
const bareAuth = (/request\.auth(!==|!=)null/.test(normCond) || /request\.auth\.uid(!==|!=)null/.test(normCond)) && !normCond.includes("request.auth.token") && !normCond.includes("sign_in_provider");
|
|
1242
1244
|
const wildcard = /\{\s*\w+\s*=\s*\*\*\s*\}/.test(m.matchPath);
|
|
@@ -1286,7 +1288,8 @@ async function scan(rootDir, opts = {}) {
|
|
|
1286
1288
|
continue;
|
|
1287
1289
|
}
|
|
1288
1290
|
}
|
|
1289
|
-
|
|
1291
|
+
const noAuthBranch = findNoAuthBranch(effectiveCond);
|
|
1292
|
+
if (noAuthBranch === void 0 && bareAuth && (ops.has("read") || ops.has("get") || ops.has("list") || ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete"))) {
|
|
1290
1293
|
findings.push({
|
|
1291
1294
|
rule: "STORAGE_ANON_ALLOWED",
|
|
1292
1295
|
severity: "INFO",
|
|
@@ -1300,8 +1303,10 @@ async function scan(rootDir, opts = {}) {
|
|
|
1300
1303
|
});
|
|
1301
1304
|
continue;
|
|
1302
1305
|
}
|
|
1303
|
-
if (!hasAuth && effectiveCond !== void 0) {
|
|
1304
|
-
const
|
|
1306
|
+
if ((!hasAuth || noAuthBranch !== void 0) && effectiveCond !== void 0) {
|
|
1307
|
+
const branchScope = noAuthBranch ?? (effectiveCond === m.condition ? m.condition ?? "" : "");
|
|
1308
|
+
const unresolvedHelper = /^[\s(]*([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(branchScope)?.[1];
|
|
1309
|
+
const branchNote = noAuthBranch !== void 0 ? ` Ramo sem request.auth ("${noAuthBranch}"): an\xF4nimo passa por ele.` : "";
|
|
1305
1310
|
const noAuthNote = unresolvedHelper ? ` Helper "${unresolvedHelper}" n\xE3o resolvido no storage.rules \u2014 confirme o corpo no Emulator.` : "";
|
|
1306
1311
|
const canWrite = ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete");
|
|
1307
1312
|
const canRead = ops.has("read") || ops.has("get") || ops.has("list");
|
|
@@ -1310,7 +1315,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1310
1315
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
1311
1316
|
severity: "ERROR",
|
|
1312
1317
|
confidence: "PROBABLE",
|
|
1313
|
-
message: `Storage com escrita condicionada sem request.auth (${where}). Se a condi\xE7\xE3o for verdadeira para an\xF4nimo, ele escreve \u2014 cofre prov\xE1vel.${noAuthNote}`,
|
|
1318
|
+
message: `Storage com escrita condicionada sem request.auth (${where}). Se a condi\xE7\xE3o for verdadeira para an\xF4nimo, ele escreve \u2014 cofre prov\xE1vel.${branchNote}${noAuthNote}`,
|
|
1314
1319
|
fingerprint: `STORAGE_PUBLIC_WRITE:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1315
1320
|
file: relStorage,
|
|
1316
1321
|
line: m.line,
|
|
@@ -1322,7 +1327,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1322
1327
|
rule: "STORAGE_PUBLIC_READ",
|
|
1323
1328
|
severity: "INFO",
|
|
1324
1329
|
confidence: "PROBABLE",
|
|
1325
|
-
message: `Storage com leitura condicionada sem request.auth (${where}). Se a condi\xE7\xE3o for verdadeira para an\xF4nimo, ele l\xEA \u2014 confirme que \xE9 vitrine.${noAuthNote}`,
|
|
1330
|
+
message: `Storage com leitura condicionada sem request.auth (${where}). Se a condi\xE7\xE3o for verdadeira para an\xF4nimo, ele l\xEA \u2014 confirme que \xE9 vitrine.${branchNote}${noAuthNote}`,
|
|
1326
1331
|
fingerprint: `STORAGE_PUBLIC_READ:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1327
1332
|
file: relStorage,
|
|
1328
1333
|
line: m.line,
|
|
@@ -1522,6 +1527,16 @@ function isOpenStorageCondition(condition) {
|
|
|
1522
1527
|
if (norm.includes("request.auth==null") || norm.includes("request.auth===null")) return true;
|
|
1523
1528
|
return false;
|
|
1524
1529
|
}
|
|
1530
|
+
function findNoAuthBranch(effective) {
|
|
1531
|
+
if (effective === void 0 || !effective.includes("||")) return void 0;
|
|
1532
|
+
for (const part of effective.split("||")) {
|
|
1533
|
+
const branch = stripOuterParens(part.replace(/return/g, "").replace(/;/g, "").trim());
|
|
1534
|
+
const norm = branch.toLowerCase().replace(/\s+/g, "");
|
|
1535
|
+
if (norm === "" || norm === "false") continue;
|
|
1536
|
+
if (!norm.includes("request.auth")) return part.trim().slice(0, 80);
|
|
1537
|
+
}
|
|
1538
|
+
return void 0;
|
|
1539
|
+
}
|
|
1525
1540
|
function parseStorageAllows(clean) {
|
|
1526
1541
|
const out = [];
|
|
1527
1542
|
const matches = [];
|
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
scan
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZNVQGZMJ.js";
|
|
5
5
|
import "./chunk-7J3ADEBW.js";
|
|
6
6
|
|
|
7
7
|
// src/cli.ts
|
|
@@ -84,7 +84,7 @@ Uso: firebase-audit scan|check|verify|drift [--json] ...`);
|
|
|
84
84
|
const { resolve: resolveRoot } = await import("path");
|
|
85
85
|
const rootDir = cwdArg ? resolveRoot(process.cwd(), cwdArg) : process.cwd();
|
|
86
86
|
if (cmd === "verify") {
|
|
87
|
-
const { scan: scanForVerify } = await import("./scan-
|
|
87
|
+
const { scan: scanForVerify } = await import("./scan-M6OYRIKL.js");
|
|
88
88
|
const { verify } = await import("./verify-GQSR4FQL.js");
|
|
89
89
|
const { resolve: resolveVerify } = await import("path");
|
|
90
90
|
const coverageArg = getArg("coverage");
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpServer,
|
|
3
3
|
startMcpServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-ZE5K5HRR.js";
|
|
5
5
|
import {
|
|
6
6
|
AccessOriginSchema,
|
|
7
7
|
AuditYamlSchema,
|
|
@@ -34,7 +34,7 @@ import {
|
|
|
34
34
|
runChecks,
|
|
35
35
|
scan,
|
|
36
36
|
validateClaimSamples
|
|
37
|
-
} from "./chunk-
|
|
37
|
+
} from "./chunk-ZNVQGZMJ.js";
|
|
38
38
|
import {
|
|
39
39
|
buildFullPath,
|
|
40
40
|
conditionKeyForFingerprint,
|
package/dist/mcp-cli.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startMcpServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-ZE5K5HRR.js";
|
|
5
|
+
import "./chunk-ZNVQGZMJ.js";
|
|
6
6
|
import "./chunk-7J3ADEBW.js";
|
|
7
7
|
import "./chunk-CSMT4PIP.js";
|
|
8
8
|
import "./chunk-LD3F73XI.js";
|
package/dist/mcp.js
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpServer,
|
|
3
3
|
startMcpServer
|
|
4
|
-
} from "./chunk-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-ZE5K5HRR.js";
|
|
5
|
+
import "./chunk-ZNVQGZMJ.js";
|
|
6
6
|
import "./chunk-7J3ADEBW.js";
|
|
7
7
|
import "./chunk-CSMT4PIP.js";
|
|
8
8
|
import "./chunk-LD3F73XI.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/firebase-audit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.11",
|
|
4
4
|
"description": "Auditor de consistência e segurança para projetos Firebase: código + Rules + índices + contrato vs comportamento. Static-first, nunca inventa certeza.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"firebase",
|
package/skill/SKILL.md
CHANGED
|
@@ -20,7 +20,8 @@ Skill para agentes usarem o `@justmpm/firebase-audit` do jeito certo.
|
|
|
20
20
|
- Claims: 1000 bytes max, sem chaves OIDC reservadas, só controle de acesso
|
|
21
21
|
- AppCheck é carimbo do app oficial — não prova usuário nem tenant
|
|
22
22
|
- Logo pública (`get: if true` em `/logos/`) pode ser vitrine proposital — cofre é `write: if true` ou curinga total aberto
|
|
23
|
-
- Storage resolve funções auxiliares do `.rules` antes de acusar (portaria com auth dentro não é "sem auth"); helper não declarado vira PROBABLE com o nome na mensagem, nunca silêncio
|
|
23
|
+
- Storage resolve funções auxiliares do `.rules` antes de acusar (portaria com auth dentro não é "sem auth", inclui `let` do rules v2); helper não declarado vira PROBABLE com o nome na mensagem, nunca silêncio
|
|
24
|
+
- Storage avalia disjunção por ramo (`auth || tamanho` acusa o ramo sem auth) e `return false` via helper é deny silencioso, igual ao `if false`
|
|
24
25
|
- Seeds/fixtures com Admin SDK moram em `scripts/` ou `server/`, nunca em `src/` (senão o scan acusa FBA003 de propósito)
|
|
25
26
|
|
|
26
27
|
## Emulador
|