@justmpm/firebase-audit 0.5.8 → 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-NZT4NIVA.js → chunk-GR2SM3OL.js} +17 -8
- package/dist/{chunk-D75SRJ7P.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-RTIMO3I2.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,
|
|
@@ -912,10 +913,9 @@ function validateClaimSamples(samples) {
|
|
|
912
913
|
|
|
913
914
|
// src/scan.ts
|
|
914
915
|
async function scan(rootDir, opts = {}) {
|
|
915
|
-
const { statSync: statRoot } = await import("fs");
|
|
916
916
|
let rootOk = false;
|
|
917
917
|
try {
|
|
918
|
-
rootOk =
|
|
918
|
+
rootOk = statSync2(rootDir).isDirectory();
|
|
919
919
|
} catch {
|
|
920
920
|
rootOk = false;
|
|
921
921
|
}
|
|
@@ -1221,6 +1221,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1221
1221
|
const raw = readFileSync3(d.storageRulesFile, "utf-8");
|
|
1222
1222
|
const relStorage = model.firebase.storage?.rulesFile ?? "storage.rules";
|
|
1223
1223
|
const clean = stripRuleComments(raw);
|
|
1224
|
+
const storageHelpers = parseRuleFunctionsDetailed(clean);
|
|
1224
1225
|
let evCounter = 0;
|
|
1225
1226
|
const evId = (prefix) => {
|
|
1226
1227
|
evCounter += 1;
|
|
@@ -1232,12 +1233,15 @@ async function scan(rootDir, opts = {}) {
|
|
|
1232
1233
|
}
|
|
1233
1234
|
const ops = expandStorageOps(m.target);
|
|
1234
1235
|
const key = conditionKeyForFingerprint(m.condition);
|
|
1235
|
-
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, "");
|
|
1236
1240
|
const hasAuth = normCond.includes("request.auth");
|
|
1237
1241
|
const bareAuth = (/request\.auth(!==|!=)null/.test(normCond) || /request\.auth\.uid(!==|!=)null/.test(normCond)) && !normCond.includes("request.auth.token") && !normCond.includes("sign_in_provider");
|
|
1238
1242
|
const wildcard = /\{\s*\w+\s*=\s*\*\*\s*\}/.test(m.matchPath);
|
|
1239
1243
|
const where = `${relStorage}:${m.line} (${m.matchPath}, allow ${m.target})`;
|
|
1240
|
-
if (isOpenStorageCondition(
|
|
1244
|
+
if (isOpenStorageCondition(effectiveCond)) {
|
|
1241
1245
|
if (ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete")) {
|
|
1242
1246
|
findings.push({
|
|
1243
1247
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
@@ -1296,7 +1300,9 @@ async function scan(rootDir, opts = {}) {
|
|
|
1296
1300
|
});
|
|
1297
1301
|
continue;
|
|
1298
1302
|
}
|
|
1299
|
-
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.` : "";
|
|
1300
1306
|
const canWrite = ops.has("write") || ops.has("create") || ops.has("update") || ops.has("delete");
|
|
1301
1307
|
const canRead = ops.has("read") || ops.has("get") || ops.has("list");
|
|
1302
1308
|
if (canWrite) {
|
|
@@ -1304,7 +1310,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1304
1310
|
rule: "STORAGE_PUBLIC_WRITE",
|
|
1305
1311
|
severity: "ERROR",
|
|
1306
1312
|
confidence: "PROBABLE",
|
|
1307
|
-
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}`,
|
|
1308
1314
|
fingerprint: `STORAGE_PUBLIC_WRITE:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1309
1315
|
file: relStorage,
|
|
1310
1316
|
line: m.line,
|
|
@@ -1316,7 +1322,7 @@ async function scan(rootDir, opts = {}) {
|
|
|
1316
1322
|
rule: "STORAGE_PUBLIC_READ",
|
|
1317
1323
|
severity: "INFO",
|
|
1318
1324
|
confidence: "PROBABLE",
|
|
1319
|
-
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}`,
|
|
1320
1326
|
fingerprint: `STORAGE_PUBLIC_READ:${m.matchPath}:${normalizeTarget(m.target)}:${key}`,
|
|
1321
1327
|
file: relStorage,
|
|
1322
1328
|
line: m.line,
|
|
@@ -1507,9 +1513,12 @@ function isOpenStorageCondition(condition) {
|
|
|
1507
1513
|
if (condition === void 0) return true;
|
|
1508
1514
|
const norm = stripOuterParens(condition).toLowerCase().replace(/\s+/g, "");
|
|
1509
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
|
+
}
|
|
1510
1520
|
if (/^!\(.*request\.auth(===|==)null/.test(norm)) return false;
|
|
1511
1521
|
if (/^!\(.*request\.auth(!==|!=)null/.test(norm)) return true;
|
|
1512
|
-
if (/(^|\|\|)true($|\|\|)/.test(norm)) return true;
|
|
1513
1522
|
if (norm.includes("request.auth==null") || norm.includes("request.auth===null")) return true;
|
|
1514
1523
|
return false;
|
|
1515
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
|