@justmpm/firebase-audit 0.5.9 → 0.5.10
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-B4ATI4OJ.js → chunk-GR2SM3OL.js} +16 -6
- package/dist/{chunk-4ST23J6W.js → chunk-JPHERTX5.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-CG54EVVP.js → scan-YY5UY35J.js} +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +1 -0
|
@@ -2,6 +2,7 @@ import {
|
|
|
2
2
|
conditionKeyForFingerprint,
|
|
3
3
|
extractRules,
|
|
4
4
|
findPublicAllowsContent,
|
|
5
|
+
helperBodyToCondition,
|
|
5
6
|
inlineHelpersInCondition,
|
|
6
7
|
normalizeTarget,
|
|
7
8
|
opsFrom,
|
|
@@ -1220,6 +1221,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1220
1221
|
const raw = readFileSync3(d.storageRulesFile, "utf-8");
|
|
1221
1222
|
const relStorage = model.firebase.storage?.rulesFile ?? "storage.rules";
|
|
1222
1223
|
const clean = stripRuleComments(raw);
|
|
1224
|
+
const storageHelpers = parseRuleFunctionsDetailed(clean);
|
|
1223
1225
|
let evCounter = 0;
|
|
1224
1226
|
const evId = (prefix) => {
|
|
1225
1227
|
evCounter += 1;
|
|
@@ -1231,12 +1233,15 @@ async function scan(rootDir, opts = {}) {
|
|
|
1231
1233
|
}
|
|
1232
1234
|
const ops = expandStorageOps(m.target);
|
|
1233
1235
|
const key = conditionKeyForFingerprint(m.condition);
|
|
1234
|
-
const
|
|
1236
|
+
const singleStorage = m.condition === void 0 ? void 0 : resolveHelperConditionDetailed(m.condition, storageHelpers);
|
|
1237
|
+
const inlinedStorage = m.condition === void 0 ? void 0 : inlineHelpersInCondition(m.condition, storageHelpers);
|
|
1238
|
+
const effectiveCond = m.condition === void 0 ? void 0 : singleStorage !== void 0 && singleStorage !== m.condition ? helperBodyToCondition(singleStorage) : inlinedStorage !== void 0 && inlinedStorage !== m.condition ? inlinedStorage : m.condition;
|
|
1239
|
+
const normCond = (effectiveCond ?? "").toLowerCase().replace(/\s+/g, "");
|
|
1235
1240
|
const hasAuth = normCond.includes("request.auth");
|
|
1236
1241
|
const bareAuth = (/request\.auth(!==|!=)null/.test(normCond) || /request\.auth\.uid(!==|!=)null/.test(normCond)) && !normCond.includes("request.auth.token") && !normCond.includes("sign_in_provider");
|
|
1237
1242
|
const wildcard = /\{\s*\w+\s*=\s*\*\*\s*\}/.test(m.matchPath);
|
|
1238
1243
|
const where = `${relStorage}:${m.line} (${m.matchPath}, allow ${m.target})`;
|
|
1239
|
-
if (isOpenStorageCondition(
|
|
1244
|
+
if (isOpenStorageCondition(effectiveCond)) {
|
|
1240
1245
|
if (ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete")) {
|
|
1241
1246
|
findings.push({
|
|
1242
1247
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
@@ -1295,7 +1300,9 @@ async function scan(rootDir, opts = {}) {
|
|
|
1295
1300
|
});
|
|
1296
1301
|
continue;
|
|
1297
1302
|
}
|
|
1298
|
-
if (!hasAuth &&
|
|
1303
|
+
if (!hasAuth && effectiveCond !== void 0) {
|
|
1304
|
+
const unresolvedHelper = effectiveCond === m.condition ? /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*\(/.exec(m.condition ?? "")?.[1] : void 0;
|
|
1305
|
+
const noAuthNote = unresolvedHelper ? ` Helper "${unresolvedHelper}" n\xE3o resolvido no storage.rules \u2014 confirme o corpo no Emulator.` : "";
|
|
1299
1306
|
const canWrite = ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete");
|
|
1300
1307
|
const canRead = ops.has("read") || ops.has("get") || ops.has("list");
|
|
1301
1308
|
if (canWrite) {
|
|
@@ -1303,7 +1310,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1303
1310
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
1304
1311
|
severity: "ERROR",
|
|
1305
1312
|
confidence: "PROBABLE",
|
|
1306
|
-
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
|
|
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}`,
|
|
1307
1314
|
fingerprint: `STORAGE_PUBLIC_WRITE:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1308
1315
|
file: relStorage,
|
|
1309
1316
|
line: m.line,
|
|
@@ -1315,7 +1322,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1315
1322
|
rule: "STORAGE_PUBLIC_READ",
|
|
1316
1323
|
severity: "INFO",
|
|
1317
1324
|
confidence: "PROBABLE",
|
|
1318
|
-
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
|
|
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}`,
|
|
1319
1326
|
fingerprint: `STORAGE_PUBLIC_READ:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1320
1327
|
file: relStorage,
|
|
1321
1328
|
line: m.line,
|
|
@@ -1506,9 +1513,12 @@ function isOpenStorageCondition(condition) {
|
|
|
1506
1513
|
if (condition === void 0) return true;
|
|
1507
1514
|
const norm = stripOuterParens(condition).toLowerCase().replace(/\s+/g, "");
|
|
1508
1515
|
if (norm === "" || norm === "true") return true;
|
|
1516
|
+
for (const part of norm.split("||")) {
|
|
1517
|
+
const branch = stripOuterParens(part.replace(/return/g, "").replace(/;/g, "").trim());
|
|
1518
|
+
if (branch === "true") return true;
|
|
1519
|
+
}
|
|
1509
1520
|
if (/^!\(.*request\.auth(===|==)null/.test(norm)) return false;
|
|
1510
1521
|
if (/^!\(.*request\.auth(!==|!=)null/.test(norm)) return true;
|
|
1511
|
-
if (/(^|\|\|)true($|\|\|)/.test(norm)) return true;
|
|
1512
1522
|
if (norm.includes("request.auth==null") || norm.includes("request.auth===null")) return true;
|
|
1513
1523
|
return false;
|
|
1514
1524
|
}
|
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-GR2SM3OL.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-YY5UY35J.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-JPHERTX5.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-GR2SM3OL.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-JPHERTX5.js";
|
|
5
|
+
import "./chunk-GR2SM3OL.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-JPHERTX5.js";
|
|
5
|
+
import "./chunk-GR2SM3OL.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.10",
|
|
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,6 +20,7 @@ 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
24
|
- Seeds/fixtures com Admin SDK moram em `scripts/` ou `server/`, nunca em `src/` (senão o scan acusa FBA003 de propósito)
|
|
24
25
|
|
|
25
26
|
## Emulador
|