@justmpm/firebase-audit 0.5.5 → 0.5.7

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.
@@ -1,12 +1,12 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-ME6WO45S.js";
3
+ } from "./chunk-LJW7QXIJ.js";
4
4
  import {
5
5
  verify
6
- } from "./chunk-DIO36A3I.js";
6
+ } from "./chunk-CSMT4PIP.js";
7
7
  import {
8
8
  drift
9
- } from "./chunk-XZ32YQQC.js";
9
+ } from "./chunk-LD3F73XI.js";
10
10
 
11
11
  // src/mcp.ts
12
12
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -1,9 +1,9 @@
1
1
  // src/verify.ts
2
- import { readFileSync, existsSync } from "fs";
3
- function collectVisitCounts(node, out) {
4
- if (typeof node !== "object" || node === null) return;
2
+ import { readFileSync, existsSync, statSync } from "fs";
3
+ function collectVisitCounts(node, out, depth = 0) {
4
+ if (typeof node !== "object" || node === null || depth > 100) return;
5
5
  if (Array.isArray(node)) {
6
- for (const v of node) collectVisitCounts(v, out);
6
+ for (const v of node) collectVisitCounts(v, out, depth + 1);
7
7
  return;
8
8
  }
9
9
  const rec = node;
@@ -14,7 +14,7 @@ function collectVisitCounts(node, out) {
14
14
  break;
15
15
  }
16
16
  }
17
- for (const v of Object.values(rec)) collectVisitCounts(v, out);
17
+ for (const v of Object.values(rec)) collectVisitCounts(v, out, depth + 1);
18
18
  }
19
19
  function planVerify(model) {
20
20
  const roles = model.authorization.roles.length > 0 ? model.authorization.roles.map((r) => r.name) : ["anonymous", "user", "admin"];
@@ -63,6 +63,13 @@ function verify(model, opts = {}) {
63
63
  });
64
64
  } else {
65
65
  try {
66
+ let tooBig = false;
67
+ try {
68
+ tooBig = statSync(opts.coverageFile).size > 5 * 1024 * 1024;
69
+ } catch {
70
+ tooBig = false;
71
+ }
72
+ if (tooBig) throw new Error("coverage acima de 5 MB");
66
73
  const raw = JSON.parse(readFileSync(opts.coverageFile, "utf-8"));
67
74
  const counts = [];
68
75
  collectVisitCounts(raw, counts);
@@ -95,7 +102,7 @@ function verify(model, opts = {}) {
95
102
  rule: "COVERAGE_INVALID",
96
103
  severity: "WARNING",
97
104
  confidence: "CONFIRMED",
98
- message: "Arquivo de cobertura inv\xE1lido \u2014 Verifier sem cobertura neste run.",
105
+ message: "Arquivo de cobertura inv\xE1lido ou acima de 5 MB \u2014 Verifier sem cobertura neste run.",
99
106
  fingerprint: "COVERAGE_INVALID",
100
107
  evidence: [{ id: "ev-coverage", kind: "coverage-parse", summary: "JSON inv\xE1lido", confidence: "CONFIRMED" }]
101
108
  });
@@ -1,5 +1,5 @@
1
1
  // src/drift.ts
2
- import { readFileSync, existsSync } from "fs";
2
+ import { readFileSync, existsSync, statSync } from "fs";
3
3
  function normalizeMode(f) {
4
4
  if (typeof f.fieldPath !== "string" || f.fieldPath.length === 0) return null;
5
5
  if (f.vectorConfig !== void 0 && f.vectorConfig !== null) return { fieldPath: f.fieldPath, mode: "VECTOR" };
@@ -59,10 +59,19 @@ function drift(localFile, remoteFile) {
59
59
  });
60
60
  return { entries: [], findings };
61
61
  }
62
+ const tooLarge = (f) => {
63
+ if (!f || !existsSync(f)) return false;
64
+ try {
65
+ return statSync(f).size > 5 * 1024 * 1024;
66
+ } catch {
67
+ return false;
68
+ }
69
+ };
62
70
  let local = [];
63
71
  let remote = [];
64
72
  if (localFile && existsSync(localFile)) {
65
73
  try {
74
+ if (tooLarge(localFile)) throw new Error("local acima de 5 MB");
66
75
  const raw = JSON.parse(readFileSync(localFile, "utf-8"));
67
76
  const rawIndexes = normalizeList(raw);
68
77
  const parsed = [];
@@ -88,7 +97,7 @@ function drift(localFile, remoteFile) {
88
97
  rule: "DRIFT_LOCAL_INVALID",
89
98
  severity: "WARNING",
90
99
  confidence: "CONFIRMED",
91
- message: "\xCDndices locais inv\xE1lidos \u2014 drift sem base local.",
100
+ message: "\xCDndices locais inv\xE1lidos ou acima de 5 MB \u2014 drift sem base local.",
92
101
  fingerprint: "DRIFT_LOCAL_INVALID",
93
102
  evidence: [{ id: "ev-drift-local", kind: "drift-parse", summary: "local inv\xE1lido", confidence: "CONFIRMED" }]
94
103
  });
@@ -96,6 +105,7 @@ function drift(localFile, remoteFile) {
96
105
  }
97
106
  if (remoteFile && existsSync(remoteFile)) {
98
107
  try {
108
+ if (tooLarge(remoteFile)) throw new Error("remoto acima de 5 MB");
99
109
  const list = normalizeList(JSON.parse(readFileSync(remoteFile, "utf-8")));
100
110
  const parsed = [];
101
111
  let skipped = 0;
@@ -120,7 +130,7 @@ function drift(localFile, remoteFile) {
120
130
  rule: "DRIFT_REMOTE_INVALID",
121
131
  severity: "WARNING",
122
132
  confidence: "CONFIRMED",
123
- message: "\xCDndices remotos inv\xE1lidos \u2014 drift sem base remota.",
133
+ message: "\xCDndices remotos inv\xE1lidos ou acima de 5 MB \u2014 drift sem base remota.",
124
134
  fingerprint: "DRIFT_REMOTE_INVALID",
125
135
  evidence: [{ id: "ev-drift-remote", kind: "drift-parse", summary: "remoto inv\xE1lido", confidence: "CONFIRMED" }]
126
136
  });
@@ -12,7 +12,7 @@ import {
12
12
  } from "./chunk-FECVBQUO.js";
13
13
 
14
14
  // src/scan.ts
15
- import { readFileSync as readFileSync3, existsSync as existsSync3 } from "fs";
15
+ import { readFileSync as readFileSync3, existsSync as existsSync3, statSync as statSync2 } from "fs";
16
16
  import { join as join3 } from "path";
17
17
  import { parse as parseYaml } from "yaml";
18
18
 
@@ -54,6 +54,10 @@ function discover(rootDir) {
54
54
  const first = (v) => Array.isArray(v) ? v[0] : v;
55
55
  const fs = first(raw.firestore);
56
56
  const st = first(raw.storage);
57
+ const fsAny = fs;
58
+ if (fsAny && typeof fsAny.database === "string" && fsAny.database.length > 0 && fsAny.database !== "(default)") {
59
+ databaseId = fsAny.database;
60
+ }
57
61
  if (typeof fs?.rules === "string") {
58
62
  const p = join(rootDir, fs.rules);
59
63
  if (existsSync(p)) rulesFile = p;
@@ -119,6 +123,8 @@ function classifyOrigin(file, content) {
119
123
  if (rel.includes("src/app/api/")) return "SERVER";
120
124
  if (rel.includes("server/api/")) return "SERVER";
121
125
  if (hasSeg(...SERVER_HINTS_SEGMENTS)) return "SERVER";
126
+ const base = segs[segs.length - 1] ?? "";
127
+ if (/^(server|middleware)[.\-]/i.test(base)) return "SERVER";
122
128
  if (content.includes("firebase-admin") || content.includes("firebase-functions")) {
123
129
  return "CLIENT";
124
130
  }
@@ -157,14 +163,14 @@ async function enrichWithGraph(rootDir, d) {
157
163
  const res = await ai.map({ cwd: rootDir, format: "json" });
158
164
  const files = Array.isArray(res.files) ? res.files : [];
159
165
  const code = files.map((f) => String(f.path).replace(/\\/g, "/")).filter((p) => /\.(ts|tsx|js|jsx|mjs|cjs)$/.test(p)).filter((p) => !p.includes("node_modules/") && !/(^|\/)(dist|build|\.next|coverage|lib)\//.test(p)).slice(0, 500);
160
- const { readFileSync: readSync, existsSync: existsSyncFn, statSync: statSync2 } = await import("fs");
166
+ const { readFileSync: readSync, existsSync: existsSyncFn, statSync: statSync3 } = await import("fs");
161
167
  const { join: joinP } = await import("path");
162
168
  for (const rel of code) {
163
169
  let content = "";
164
170
  try {
165
171
  const abs = joinP(rootDir, rel);
166
172
  if (!existsSyncFn(abs)) continue;
167
- if (statSync2(abs).size > 100 * 1024) continue;
173
+ if (statSync3(abs).size > 100 * 1024) continue;
168
174
  content = readSync(abs, "utf-8");
169
175
  } catch {
170
176
  content = "";
@@ -181,6 +187,10 @@ async function enrichWithGraph(rootDir, d) {
181
187
  import { readFileSync as readFileSync2, existsSync as existsSync2 } from "fs";
182
188
  import { join as join2 } from "path";
183
189
  import { executeFind } from "@justmpm/supergrep";
190
+ function isBuildOutput(file) {
191
+ const n = file.replace(/\\/g, "/");
192
+ return /(^|\/)(node_modules|dist|build|\.next|coverage|out)\//.test(n);
193
+ }
184
194
  var IMPLEMENTED_CHECKS = ["FBA001", "FBA002", "FBA003", "FBA004", "FBA009", "FBA010", "FBA011", "FBA012", "FBA013"];
185
195
  function createEvidence() {
186
196
  let counter = 0;
@@ -265,6 +275,26 @@ async function runChecks(model, rootDir, opts = {}) {
265
275
  }
266
276
  }
267
277
  }
278
+ for (const rule of model.rules) {
279
+ if (rule.operations.length === 0) {
280
+ findings.push(
281
+ make(
282
+ "RULES_UNKNOWN_OP",
283
+ "WARNING",
284
+ "CONFIRMED",
285
+ `Opera\xE7\xE3o desconhecida em ${rule.path} (${rule.location.file}:${rule.location.start.line}). Typo em allow? Deploy rejeita.`,
286
+ `RULES_UNKNOWN_OP:${rule.path}:${rule.location.start.line}`,
287
+ {
288
+ file: rule.location.file,
289
+ line: rule.location.start.line,
290
+ resource: rule.path,
291
+ evidence: [ev("unknown-op", rule.condition ?? "allow sem opera\xE7\xE3o v\xE1lida", "CONFIRMED", rule.location.file, rule.location.start.line)],
292
+ fix: "Corrija para read/get/list/write/create/update/delete."
293
+ }
294
+ )
295
+ );
296
+ }
297
+ }
268
298
  const adapterFn = opts.adapterFn ?? "rbac.can";
269
299
  const permissionCalls = await collectPermissionCalls(rootDir, adapterFn);
270
300
  const declared = new Set(model.authorization.permissions.map((p) => p.name));
@@ -563,6 +593,7 @@ async function collectPermissionCalls(rootDir, adapterFn) {
563
593
  }
564
594
  const seen = /* @__PURE__ */ new Set();
565
595
  const pushLiteral = (file, line, permission) => {
596
+ if (isBuildOutput(file)) return;
566
597
  const k = `${file}:${line}:${permission}`;
567
598
  if (seen.has(k)) return;
568
599
  seen.add(k);
@@ -575,10 +606,12 @@ async function collectPermissionCalls(rootDir, adapterFn) {
575
606
  const raw = (m.metaVariables["PERM"] ?? "").trim();
576
607
  const line = m.line + 1;
577
608
  if (raw.includes("${")) {
578
- const k = `${m.file}:${line}:${raw}`;
579
- if (!seen.has(k)) {
580
- seen.add(k);
581
- dynamic.push({ file: m.file, line, text: m.text });
609
+ if (!isBuildOutput(m.file)) {
610
+ const k = `${m.file}:${line}:${raw}`;
611
+ if (!seen.has(k)) {
612
+ seen.add(k);
613
+ dynamic.push({ file: m.file, line, text: m.text });
614
+ }
582
615
  }
583
616
  continue;
584
617
  }
@@ -587,18 +620,22 @@ async function collectPermissionCalls(rootDir, adapterFn) {
587
620
  const perm = raw.replace(/^['"`]|['"`]$/g, "").trim();
588
621
  if (/^[a-z][a-z0-9_-]*(\.[a-z][a-z0-9_-]*)*$/.test(perm)) pushLiteral(m.file, line, perm);
589
622
  else {
590
- const k = `${m.file}:${line}:${raw}:invalid`;
623
+ if (!isBuildOutput(m.file)) {
624
+ const k = `${m.file}:${line}:${raw}:invalid`;
625
+ if (!seen.has(k)) {
626
+ seen.add(k);
627
+ dynamic.push({ file: m.file, line, text: m.text });
628
+ }
629
+ }
630
+ }
631
+ } else if (raw.length > 0) {
632
+ if (!isBuildOutput(m.file)) {
633
+ const k = `${m.file}:${line}:${raw}`;
591
634
  if (!seen.has(k)) {
592
635
  seen.add(k);
593
636
  dynamic.push({ file: m.file, line, text: m.text });
594
637
  }
595
638
  }
596
- } else if (raw.length > 0) {
597
- const k = `${m.file}:${line}:${raw}`;
598
- if (!seen.has(k)) {
599
- seen.add(k);
600
- dynamic.push({ file: m.file, line, text: m.text });
601
- }
602
639
  }
603
640
  }
604
641
  } catch {
@@ -635,6 +672,7 @@ async function collectAdminImports(rootDir) {
635
672
  for (const m of result.matches) {
636
673
  const mod = m.metaVariables["MODULE"] ?? "";
637
674
  if (mod.includes("firebase-admin") || mod.includes("firebase-functions")) {
675
+ if (isBuildOutput(m.file)) continue;
638
676
  const k = `${m.file}:${m.line}:${m.text}`;
639
677
  if (seen.has(k)) continue;
640
678
  seen.add(k);
@@ -960,13 +998,14 @@ async function scan(rootDir, opts = {}) {
960
998
  if (d.firestoreRulesFile && existsSync3(d.firestoreRulesFile)) {
961
999
  const rel = model.firebase.firestore.rulesFile ?? "firestore.rules";
962
1000
  try {
1001
+ if (tooLarge(d.firestoreRulesFile, 1024 * 1024)) throw new Error("rules acima de 1 MB");
963
1002
  model.rules = extractRules(d.firestoreRulesFile, rel);
964
1003
  } catch {
965
1004
  findings_pre.push({
966
1005
  rule: "RULES_UNREADABLE",
967
1006
  severity: "WARNING",
968
1007
  confidence: "CONFIRMED",
969
- message: "firestore.rules existe mas n\xE3o p\xF4de ser lido \u2014 FBA001/FBA002 sem cobertura.",
1008
+ message: "firestore.rules existe mas n\xE3o p\xF4de ser lido (ileg\xEDvel ou acima de 1 MB) \u2014 FBA001/FBA002 sem cobertura.",
970
1009
  fingerprint: "RULES_UNREADABLE",
971
1010
  evidence: [
972
1011
  { id: "ev-rules-unreadable", kind: "rules-read", summary: "leitura falhou", confidence: "CONFIRMED" }
@@ -976,6 +1015,7 @@ async function scan(rootDir, opts = {}) {
976
1015
  }
977
1016
  if (d.firestoreIndexesFile && existsSync3(d.firestoreIndexesFile)) {
978
1017
  try {
1018
+ if (tooLarge(d.firestoreIndexesFile, 1024 * 1024)) throw new Error("indexes acima de 1 MB");
979
1019
  const raw = JSON.parse(readFileSync3(d.firestoreIndexesFile, "utf-8"));
980
1020
  if (Array.isArray(raw.fieldOverrides) && raw.fieldOverrides.length > 0) {
981
1021
  findings_pre.push({
@@ -1046,7 +1086,7 @@ async function scan(rootDir, opts = {}) {
1046
1086
  rule: "INDEXES_INVALID",
1047
1087
  severity: "WARNING",
1048
1088
  confidence: "CONFIRMED",
1049
- message: "firestore.indexes.json inv\xE1lido \u2014 \xEDndices ignorados neste scan.",
1089
+ message: "firestore.indexes.json inv\xE1lido ou acima de 1 MB \u2014 \xEDndices ignorados neste scan.",
1050
1090
  fingerprint: "INDEXES_INVALID",
1051
1091
  evidence: [
1052
1092
  { id: "ev-indexes-invalid", kind: "indexes-parse", summary: "JSON inv\xE1lido", confidence: "CONFIRMED" }
@@ -1056,6 +1096,7 @@ async function scan(rootDir, opts = {}) {
1056
1096
  }
1057
1097
  if (d.auditYamlFile && existsSync3(d.auditYamlFile)) {
1058
1098
  try {
1099
+ if (tooLarge(d.auditYamlFile, 256 * 1024)) throw new Error("yaml acima de 256 KB");
1059
1100
  const text = readFileSync3(d.auditYamlFile, "utf-8");
1060
1101
  const parsed = parseYaml(text);
1061
1102
  const validated = AuditYamlSchema.safeParse(parsed);
@@ -1164,6 +1205,7 @@ async function scan(rootDir, opts = {}) {
1164
1205
  if (d.storageRulesFile && existsSync3(d.storageRulesFile)) {
1165
1206
  checked.push("storage.rules");
1166
1207
  try {
1208
+ if (tooLarge(d.storageRulesFile, 1024 * 1024)) throw new Error("storage acima de 1 MB");
1167
1209
  const raw = readFileSync3(d.storageRulesFile, "utf-8");
1168
1210
  const relStorage = model.firebase.storage?.rulesFile ?? "storage.rules";
1169
1211
  const clean = stripRuleComments(raw);
@@ -1173,6 +1215,9 @@ async function scan(rootDir, opts = {}) {
1173
1215
  return `ev-storage-${prefix}-${evCounter}`;
1174
1216
  };
1175
1217
  for (const m of parseStorageAllows(clean)) {
1218
+ if (m.condition !== void 0 && stripOuterParens(m.condition).toLowerCase().replace(/\s+/g, "") === "false") {
1219
+ continue;
1220
+ }
1176
1221
  const ops = expandStorageOps(m.target);
1177
1222
  const key = conditionKeyForFingerprint(m.condition);
1178
1223
  const normCond = (m.condition ?? "").toLowerCase().replace(/\s+/g, "");
@@ -1269,13 +1314,13 @@ async function scan(rootDir, opts = {}) {
1269
1314
  }
1270
1315
  }
1271
1316
  } catch {
1272
- skipped.push("storage.rules ileg\xEDvel \u2014 Storage sem cobertura neste scan");
1317
+ skipped.push("storage.rules ileg\xEDvel ou acima de 1 MB \u2014 Storage sem cobertura neste scan");
1273
1318
  }
1274
1319
  } else {
1275
1320
  skipped.push("storage.rules n\xE3o observado \u2014 Storage fora deste scan");
1276
1321
  }
1277
1322
  if (d.functionsDir) {
1278
- const appcheck = await countAppCheckEnforced(rootDir);
1323
+ const appcheck = await countAppCheckEnforced(rootDir, d.functionsDir);
1279
1324
  model.functions = { dir: model.functions?.dir, appCheckEnforced: appcheck.occurrences };
1280
1325
  checked.push("functions-appcheck");
1281
1326
  if (appcheck.manual > 0) {
@@ -1365,12 +1410,19 @@ async function scan(rootDir, opts = {}) {
1365
1410
  }
1366
1411
  };
1367
1412
  }
1368
- async function countAppCheckEnforced(rootDir) {
1413
+ function tooLarge(abs, maxBytes) {
1414
+ try {
1415
+ return statSync2(abs).size > maxBytes;
1416
+ } catch {
1417
+ return false;
1418
+ }
1419
+ }
1420
+ async function countAppCheckEnforced(rootDir, functionsDir) {
1369
1421
  try {
1370
1422
  const { readdirSync, readFileSync: readSync, statSync: stat } = await import("fs");
1371
1423
  const { join: joinP } = await import("path");
1372
1424
  const { stripRuleComments: strip } = await import("./rules-W4VUUQM4.js");
1373
- const roots = [joinP(rootDir, "functions")];
1425
+ const roots = [functionsDir ?? joinP(rootDir, "functions")];
1374
1426
  let occurrences = 0;
1375
1427
  const files = /* @__PURE__ */ new Set();
1376
1428
  let manual = 0;
@@ -1384,7 +1436,6 @@ async function countAppCheckEnforced(rootDir) {
1384
1436
  };
1385
1437
  while (stack.length > 0 && guard < 200) {
1386
1438
  guard += 1;
1387
- if (guard >= 200 && stack.length > 0) limitHit = true;
1388
1439
  const cur = stack.pop();
1389
1440
  let entries = [];
1390
1441
  try {
@@ -1405,7 +1456,7 @@ async function countAppCheckEnforced(rootDir) {
1405
1456
  continue;
1406
1457
  }
1407
1458
  if (isDir) {
1408
- if (name === "node_modules" || name === "dist" || name === "lib") continue;
1459
+ if (name === "node_modules" || name === "dist" || name === "lib" || name === "build" || name === ".next" || name === "coverage" || name === ".git" || name === "out") continue;
1409
1460
  stack.push(abs);
1410
1461
  continue;
1411
1462
  }
@@ -1437,6 +1488,7 @@ async function countAppCheckEnforced(rootDir) {
1437
1488
  }
1438
1489
  }
1439
1490
  }
1491
+ if (stack.length > 0) limitHit = true;
1440
1492
  return { occurrences, files: files.size, manual, ignoredOnRequest, limitHit };
1441
1493
  } catch {
1442
1494
  return { occurrences: 0, files: 0, manual: 0, ignoredOnRequest: 0, limitHit: false };
@@ -1503,9 +1555,14 @@ function parseStorageAllows(clean) {
1503
1555
  const re = /allow\s+([^;:]+?)(?::\s*if\s+([^;]+))?;/gi;
1504
1556
  let m;
1505
1557
  while ((m = re.exec(clean)) !== null) {
1506
- const containing = matches.filter((b) => b.start <= m.index && m.index < b.end).sort((a, b) => b.start - a.start);
1558
+ const containing = matches.filter((b) => b.start <= m.index && m.index < b.end).sort((a, b) => a.start - b.start);
1559
+ const parts = [];
1560
+ for (const b of containing) {
1561
+ const p = b.path.trim().replace(/^\/+|\/+$/g, "");
1562
+ if (p.length > 0) parts.push(p);
1563
+ }
1507
1564
  const line = clean.slice(0, m.index).split("\n").length;
1508
- out.push({ target: m[1].trim(), condition: m[2]?.trim(), matchPath: containing[0]?.path ?? "/(unknown)", line });
1565
+ out.push({ target: m[1].trim(), condition: m[2]?.trim(), matchPath: parts.length > 0 ? `/${parts.join("/")}` : "/(unknown)", line });
1509
1566
  }
1510
1567
  return out;
1511
1568
  }
package/dist/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-ME6WO45S.js";
4
+ } from "./chunk-LJW7QXIJ.js";
5
5
  import "./chunk-FECVBQUO.js";
6
6
 
7
7
  // src/cli.ts
@@ -41,13 +41,13 @@ Uso: firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]`);
41
41
  return void 0;
42
42
  };
43
43
  const strictArg = getArg("strict");
44
- const strict = args.includes("--strict") || strictArg === "true" || cmd === "check";
44
+ const strict = (args.includes("--strict") || strictArg === "true") && strictArg !== "false" || cmd === "check";
45
45
  if (strictArg !== void 0 && strictArg !== "true" && strictArg !== "false") {
46
46
  console.error(`Valor inv\xE1lido para --strict: ${strictArg} (use --strict, --strict=true ou --strict=false)`);
47
47
  process.exit(1);
48
48
  }
49
49
  const asJsonArg = getArg("json");
50
- const asJson = args.includes("--json") || asJsonArg === "true";
50
+ const asJson = (args.includes("--json") || asJsonArg === "true") && asJsonArg !== "false";
51
51
  if (asJsonArg !== void 0 && asJsonArg !== "true" && asJsonArg !== "false") {
52
52
  console.error(`Valor inv\xE1lido para --json: ${asJsonArg} (use --json, --json=true ou --json=false)`);
53
53
  process.exit(1);
@@ -81,8 +81,8 @@ Uso: firebase-audit scan|check|verify|drift [--json] ...`);
81
81
  const { resolve: resolveRoot } = await import("path");
82
82
  const rootDir = cwdArg ? resolveRoot(process.cwd(), cwdArg) : process.cwd();
83
83
  if (cmd === "verify") {
84
- const { scan: scanForVerify } = await import("./scan-HE5WAMWJ.js");
85
- const { verify } = await import("./verify-DECP4IUW.js");
84
+ const { scan: scanForVerify } = await import("./scan-A34FDN66.js");
85
+ const { verify } = await import("./verify-GQSR4FQL.js");
86
86
  const { resolve: resolveVerify } = await import("path");
87
87
  const coverageArg = getArg("coverage");
88
88
  const coveragePath = coverageArg ? resolveVerify(rootDir, coverageArg) : void 0;
@@ -116,7 +116,7 @@ ${icon} ${f.rule} [${f.confidence}]
116
116
  return;
117
117
  }
118
118
  if (cmd === "drift") {
119
- const { drift } = await import("./drift-QXALNO2V.js");
119
+ const { drift } = await import("./drift-C3JZI5ZA.js");
120
120
  const { resolve: resolveDrift } = await import("path");
121
121
  const { existsSync: existsDrift } = await import("fs");
122
122
  const localArg = getArg("local");
@@ -130,7 +130,7 @@ ${icon} ${f.rule} [${f.confidence}]
130
130
  for (const [label, p] of [["--local", localPath], ["--remote", remotePath]]) {
131
131
  if (p && !existsDrift(p)) {
132
132
  if (asJson) {
133
- const { drift: driftMissing } = await import("./drift-QXALNO2V.js");
133
+ const { drift: driftMissing } = await import("./drift-C3JZI5ZA.js");
134
134
  const res2 = driftMissing(
135
135
  localPath && existsDrift(localPath) ? localPath : null,
136
136
  remotePath && existsDrift(remotePath) ? remotePath : null
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  drift
3
- } from "./chunk-XZ32YQQC.js";
3
+ } from "./chunk-LD3F73XI.js";
4
4
  export {
5
5
  drift
6
6
  };
package/dist/index.d.ts CHANGED
@@ -753,8 +753,9 @@ interface ScanOptions {
753
753
  adapterFn?: string;
754
754
  strict?: boolean;
755
755
  /**
756
- * Opt-in: enriquece Discovery com grafo do ai-tool (clientFiles/serverFiles).
757
- * Default false (scan estático rápido; nenhum check consome o grafo por padrão).
756
+ * Prévia opt-in: enriquece Discovery com grafo do ai-tool (clientFiles/serverFiles).
757
+ * Nenhum check consome o grafo ainda — resultado idêntico com ou sem.
758
+ * Default false (scan estático rápido).
758
759
  */
759
760
  graph?: boolean;
760
761
  }
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-CHC7ZHUK.js";
4
+ } from "./chunk-BHRZOZYA.js";
5
5
  import {
6
6
  AccessOriginSchema,
7
7
  AuditYamlSchema,
@@ -33,7 +33,7 @@ import {
33
33
  runChecks,
34
34
  scan,
35
35
  validateClaimSamples
36
- } from "./chunk-ME6WO45S.js";
36
+ } from "./chunk-LJW7QXIJ.js";
37
37
  import {
38
38
  buildFullPath,
39
39
  conditionKeyForFingerprint,
@@ -59,10 +59,10 @@ import {
59
59
  import {
60
60
  planVerify,
61
61
  verify
62
- } from "./chunk-DIO36A3I.js";
62
+ } from "./chunk-CSMT4PIP.js";
63
63
  import {
64
64
  drift
65
- } from "./chunk-XZ32YQQC.js";
65
+ } from "./chunk-LD3F73XI.js";
66
66
 
67
67
  // src/index.ts
68
68
  import { createRequire } from "module";
package/dist/mcp-cli.js CHANGED
@@ -1,11 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "./chunk-CHC7ZHUK.js";
5
- import "./chunk-ME6WO45S.js";
4
+ } from "./chunk-BHRZOZYA.js";
5
+ import "./chunk-LJW7QXIJ.js";
6
6
  import "./chunk-FECVBQUO.js";
7
- import "./chunk-DIO36A3I.js";
8
- import "./chunk-XZ32YQQC.js";
7
+ import "./chunk-CSMT4PIP.js";
8
+ import "./chunk-LD3F73XI.js";
9
9
 
10
10
  // src/mcp-cli.ts
11
11
  startMcpServer().catch((err) => {
package/dist/mcp.js CHANGED
@@ -1,11 +1,11 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-CHC7ZHUK.js";
5
- import "./chunk-ME6WO45S.js";
4
+ } from "./chunk-BHRZOZYA.js";
5
+ import "./chunk-LJW7QXIJ.js";
6
6
  import "./chunk-FECVBQUO.js";
7
- import "./chunk-DIO36A3I.js";
8
- import "./chunk-XZ32YQQC.js";
7
+ import "./chunk-CSMT4PIP.js";
8
+ import "./chunk-LD3F73XI.js";
9
9
  export {
10
10
  createMcpServer,
11
11
  startMcpServer
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  scan
3
- } from "./chunk-ME6WO45S.js";
3
+ } from "./chunk-LJW7QXIJ.js";
4
4
  import "./chunk-FECVBQUO.js";
5
5
  export {
6
6
  scan
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  planVerify,
3
3
  verify
4
- } from "./chunk-DIO36A3I.js";
4
+ } from "./chunk-CSMT4PIP.js";
5
5
  export {
6
6
  planVerify,
7
7
  verify
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/firebase-audit",
3
- "version": "0.5.5",
3
+ "version": "0.5.7",
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",