@justmpm/firebase-audit 0.5.10 → 0.5.12
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-GR2SM3OL.js → chunk-CTQPAMOE.js} +39 -10
- package/dist/{chunk-JPHERTX5.js → chunk-OYWTDV45.js} +1 -1
- 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-CU54MUN7.js} +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
|
@@ -1228,15 +1228,31 @@ 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);
|
|
1233
|
+
const { unknown: unknownStorageOps } = opsFrom(m.target);
|
|
1234
|
+
if (unknownStorageOps.length > 0) {
|
|
1235
|
+
findings.push({
|
|
1236
|
+
rule: "RULES_UNKNOWN_OP",
|
|
1237
|
+
severity: "WARNING",
|
|
1238
|
+
confidence: "CONFIRMED",
|
|
1239
|
+
message: `Opera\xE7\xE3o desconhecida no Storage em ${m.matchPath} (trecho desconhecido: ${unknownStorageOps.join(", ")}) (${relStorage}:${m.line}). Typo em allow? Deploy rejeita.`,
|
|
1240
|
+
fingerprint: `RULES_UNKNOWN_OP:${m.matchPath}:${m.line}:${normalizeTarget(m.target)}`,
|
|
1241
|
+
file: relStorage,
|
|
1242
|
+
line: m.line,
|
|
1243
|
+
evidence: [{ id: evId("unknown-op"), kind: "unknown-op", summary: `${m.matchPath} allow ${m.target}`, confidence: "CONFIRMED", location: { file: relStorage, start: { line: m.line, column: 0 } } }],
|
|
1244
|
+
fix: "Corrija para read/get/list/write/create/update/delete."
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1236
1247
|
const singleStorage = m.condition === void 0 ? void 0 : resolveHelperConditionDetailed(m.condition, storageHelpers);
|
|
1237
1248
|
const inlinedStorage = m.condition === void 0 ? void 0 : inlineHelpersInCondition(m.condition, storageHelpers);
|
|
1238
|
-
const
|
|
1239
|
-
const
|
|
1249
|
+
const singleResolved = singleStorage !== void 0 && singleStorage !== m.condition ? singleStorage : void 0;
|
|
1250
|
+
const effectiveCond = m.condition === void 0 ? void 0 : singleResolved !== void 0 ? helperBodyToCondition(singleResolved) : inlinedStorage !== void 0 && inlinedStorage !== m.condition ? inlinedStorage : m.condition;
|
|
1251
|
+
const normEffFalse = effectiveCond === void 0 ? "" : stripOuterParens(effectiveCond).toLowerCase().replace(/\s+/g, "");
|
|
1252
|
+
if (normEffFalse === "false") {
|
|
1253
|
+
continue;
|
|
1254
|
+
}
|
|
1255
|
+
const normCond = `${singleResolved ?? ""} ${effectiveCond ?? ""}`.toLowerCase().replace(/\s+/g, "");
|
|
1240
1256
|
const hasAuth = normCond.includes("request.auth");
|
|
1241
1257
|
const bareAuth = (/request\.auth(!==|!=)null/.test(normCond) || /request\.auth\.uid(!==|!=)null/.test(normCond)) && !normCond.includes("request.auth.token") && !normCond.includes("sign_in_provider");
|
|
1242
1258
|
const wildcard = /\{\s*\w+\s*=\s*\*\*\s*\}/.test(m.matchPath);
|
|
@@ -1286,7 +1302,8 @@ async function scan(rootDir, opts = {}) {
|
|
|
1286
1302
|
continue;
|
|
1287
1303
|
}
|
|
1288
1304
|
}
|
|
1289
|
-
|
|
1305
|
+
const noAuthBranch = findNoAuthBranch(effectiveCond);
|
|
1306
|
+
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
1307
|
findings.push({
|
|
1291
1308
|
rule: "STORAGE_ANON_ALLOWED",
|
|
1292
1309
|
severity: "INFO",
|
|
@@ -1300,8 +1317,10 @@ async function scan(rootDir, opts = {}) {
|
|
|
1300
1317
|
});
|
|
1301
1318
|
continue;
|
|
1302
1319
|
}
|
|
1303
|
-
if (!hasAuth && effectiveCond !== void 0) {
|
|
1304
|
-
const
|
|
1320
|
+
if ((!hasAuth || noAuthBranch !== void 0) && effectiveCond !== void 0) {
|
|
1321
|
+
const branchScope = noAuthBranch ?? (effectiveCond === m.condition ? m.condition ?? "" : "");
|
|
1322
|
+
const unresolvedHelper = /^[\s(]*([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(branchScope)?.[1];
|
|
1323
|
+
const branchNote = noAuthBranch !== void 0 ? ` Ramo sem request.auth ("${noAuthBranch}"): an\xF4nimo passa por ele.` : "";
|
|
1305
1324
|
const noAuthNote = unresolvedHelper ? ` Helper "${unresolvedHelper}" n\xE3o resolvido no storage.rules \u2014 confirme o corpo no Emulator.` : "";
|
|
1306
1325
|
const canWrite = ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete");
|
|
1307
1326
|
const canRead = ops.has("read") || ops.has("get") || ops.has("list");
|
|
@@ -1310,7 +1329,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1310
1329
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
1311
1330
|
severity: "ERROR",
|
|
1312
1331
|
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}`,
|
|
1332
|
+
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
1333
|
fingerprint: `STORAGE_PUBLIC_WRITE:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1315
1334
|
file: relStorage,
|
|
1316
1335
|
line: m.line,
|
|
@@ -1322,7 +1341,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1322
1341
|
rule: "STORAGE_PUBLIC_READ",
|
|
1323
1342
|
severity: "INFO",
|
|
1324
1343
|
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}`,
|
|
1344
|
+
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
1345
|
fingerprint: `STORAGE_PUBLIC_READ:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1327
1346
|
file: relStorage,
|
|
1328
1347
|
line: m.line,
|
|
@@ -1522,6 +1541,16 @@ function isOpenStorageCondition(condition) {
|
|
|
1522
1541
|
if (norm.includes("request.auth==null") || norm.includes("request.auth===null")) return true;
|
|
1523
1542
|
return false;
|
|
1524
1543
|
}
|
|
1544
|
+
function findNoAuthBranch(effective) {
|
|
1545
|
+
if (effective === void 0 || !effective.includes("||")) return void 0;
|
|
1546
|
+
for (const part of effective.split("||")) {
|
|
1547
|
+
const branch = stripOuterParens(part.replace(/return/g, "").replace(/;/g, "").trim());
|
|
1548
|
+
const norm = branch.toLowerCase().replace(/\s+/g, "");
|
|
1549
|
+
if (norm === "" || norm === "false") continue;
|
|
1550
|
+
if (!norm.includes("request.auth")) return part.trim().slice(0, 80);
|
|
1551
|
+
}
|
|
1552
|
+
return void 0;
|
|
1553
|
+
}
|
|
1525
1554
|
function parseStorageAllows(clean) {
|
|
1526
1555
|
const out = [];
|
|
1527
1556
|
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-CTQPAMOE.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-CU54MUN7.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-OYWTDV45.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-CTQPAMOE.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-OYWTDV45.js";
|
|
5
|
+
import "./chunk-CTQPAMOE.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-OYWTDV45.js";
|
|
5
|
+
import "./chunk-CTQPAMOE.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.12",
|
|
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
|