@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.
@@ -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 normCond = (m.condition ?? "").toLowerCase().replace(/\s+/g, "");
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(m.condition)) {
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 && m.condition !== void 0) {
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
  }
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-B4ATI4OJ.js";
3
+ } from "./chunk-GR2SM3OL.js";
4
4
  import {
5
5
  verify
6
6
  } from "./chunk-CSMT4PIP.js";
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-B4ATI4OJ.js";
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-CG54EVVP.js");
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-4ST23J6W.js";
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-B4ATI4OJ.js";
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-4ST23J6W.js";
5
- import "./chunk-B4ATI4OJ.js";
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-4ST23J6W.js";
5
- import "./chunk-B4ATI4OJ.js";
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";
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-B4ATI4OJ.js";
3
+ } from "./chunk-GR2SM3OL.js";
4
4
  import "./chunk-7J3ADEBW.js";
5
5
  export {
6
6
  scan
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/firebase-audit",
3
- "version": "0.5.9",
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