@justmpm/firebase-audit 0.5.0 → 0.5.2

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.
@@ -128,6 +128,16 @@ function drift(localFile, remoteFile) {
128
128
  }
129
129
  const remoteByKey = new Map(remote.map((r) => [keyOf(r), r]));
130
130
  const localByKey = new Map(local.map((l) => [keyOf(l), l]));
131
+ if (local.length !== localByKey.size || remote.length !== remoteByKey.size) {
132
+ findings.push({
133
+ rule: "DRIFT_DUPLICATE_KEY",
134
+ severity: "INFO",
135
+ confidence: "CONFIRMED",
136
+ message: "\xCDndices duplicados colapsados por chave \u2014 revise o arquivo de origem.",
137
+ fingerprint: "DRIFT_DUPLICATE_KEY",
138
+ evidence: [{ id: "ev-drift-dup", kind: "drift-parse", summary: "chave duplicada", confidence: "CONFIRMED" }]
139
+ });
140
+ }
131
141
  const entries = [];
132
142
  for (const [key, l] of localByKey) {
133
143
  if (remoteByKey.has(key)) entries.push({ key, status: "MATCHED", local: l, remote: remoteByKey.get(key) });
@@ -39,7 +39,7 @@ function planVerify(model) {
39
39
  }
40
40
  const seen = /* @__PURE__ */ new Set();
41
41
  return cases.filter((c) => {
42
- const k = `${c.role}:${c.operation}:${c.path}`;
42
+ const k = `${c.role}:${c.operation}:${c.pathTemplate}:${c.path}`;
43
43
  if (seen.has(k)) return false;
44
44
  seen.add(k);
45
45
  return true;
@@ -105,8 +105,11 @@ function verify(model, opts = {}) {
105
105
  if (coveragePct === 100) {
106
106
  return { cases, uncovered: [], findings, coveragePct };
107
107
  }
108
+ const seenPaths = /* @__PURE__ */ new Set();
108
109
  for (const rule of model.rules) {
109
110
  if (rule.path === "/(unknown)") continue;
111
+ if (seenPaths.has(rule.path)) continue;
112
+ seenPaths.add(rule.path);
110
113
  uncovered.push(rule.path);
111
114
  }
112
115
  return { cases, uncovered, findings, coveragePct };
package/dist/cli.js CHANGED
@@ -1,7 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-TQZTE2NR.js";
4
+ } from "./chunk-UKEKA2NQ.js";
5
+ import "./chunk-7EWTBIKJ.js";
5
6
 
6
7
  // src/cli.ts
7
8
  import { createRequire } from "module";
@@ -32,7 +33,7 @@ Uso: firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]`);
32
33
  process.exit(1);
33
34
  }
34
35
  const strict = args.includes("--strict") || cmd === "check";
35
- const asJson = args.includes("--json");
36
+ const asJson = args.includes("--json") || args.includes("--json=true");
36
37
  const getArg = (name) => {
37
38
  const pref = `--${name}=`;
38
39
  const hit = args.find((a) => a.startsWith(pref));
@@ -64,19 +65,21 @@ Uso: firebase-audit scan|check|verify|drift [--json] ...`);
64
65
  const { resolve: resolveRoot } = await import("path");
65
66
  const rootDir = cwdArg ? resolveRoot(process.cwd(), cwdArg) : process.cwd();
66
67
  if (cmd === "verify") {
67
- const { scan: scanForVerify } = await import("./scan-FYAWNRIE.js");
68
- const { verify } = await import("./verify-GWRLO344.js");
68
+ const { scan: scanForVerify } = await import("./scan-OEN65GHM.js");
69
+ const { verify } = await import("./verify-WBVSESHD.js");
69
70
  const { resolve: resolveVerify } = await import("path");
70
71
  const coverageArg = getArg("coverage");
71
72
  const coveragePath = coverageArg ? resolveVerify(rootDir, coverageArg) : void 0;
72
73
  const { model } = await scanForVerify(rootDir, { adapterFn: adapterArg });
73
74
  const res = verify(model, { coverageFile: coveragePath });
75
+ const roles = model.authorization.roles.length > 0 ? model.authorization.roles.map((r) => r.name) : ["anonymous", "user", "admin"];
76
+ const rolesReduced = model.authorization.roles.length === 0;
74
77
  if (asJson) {
75
- console.log(JSON.stringify({ cases: res.cases.slice(0, 50), casesTotal: res.cases.length, truncated: res.cases.length > 50, uncovered: res.uncovered.slice(0, 20), uncoveredTotal: res.uncovered.length, coveragePct: res.coveragePct, findings: res.findings }, null, 2));
78
+ console.log(JSON.stringify({ roles, rolesReduced, cases: res.cases.slice(0, 50), casesTotal: res.cases.length, truncated: res.cases.length > 50, uncovered: res.uncovered.slice(0, 20), uncoveredTotal: res.uncovered.length, coveragePct: res.coveragePct, findings: res.findings }, null, 2));
76
79
  return;
77
80
  }
78
81
  const covStr = res.coveragePct === null ? "sem cobertura observada" : `${res.coveragePct}% de express\xF5es visitadas`;
79
- const rolesNote = "pap\xE9is reduzidos (sem contrato: anonymous/user/admin)";
82
+ const rolesNote = rolesReduced ? "pap\xE9is reduzidos (sem contrato: anonymous/user/admin)" : `${roles.length} pap\xE9is do contrato (${roles.join(", ")})`;
80
83
  console.log(`
81
84
  FIREBASE AUDIT verify \u2014 ${res.cases.length} casos planejados (${rolesNote}), ${res.uncovered.length} paths sem cobertura (${covStr}).`);
82
85
  for (const f of res.findings) {
@@ -95,7 +98,7 @@ ${icon} ${f.rule} [${f.confidence}]
95
98
  return;
96
99
  }
97
100
  if (cmd === "drift") {
98
- const { drift } = await import("./drift-KU7HWL7M.js");
101
+ const { drift } = await import("./drift-QXALNO2V.js");
99
102
  const { resolve: resolveDrift } = await import("path");
100
103
  const { existsSync: existsDrift } = await import("fs");
101
104
  const localArg = getArg("local");
@@ -109,7 +112,7 @@ ${icon} ${f.rule} [${f.confidence}]
109
112
  for (const [label, p] of [["--local", localPath], ["--remote", remotePath]]) {
110
113
  if (p && !existsDrift(p)) {
111
114
  if (asJson) {
112
- const { drift: driftMissing } = await import("./drift-KU7HWL7M.js");
115
+ const { drift: driftMissing } = await import("./drift-QXALNO2V.js");
113
116
  const res2 = driftMissing(
114
117
  localPath && existsDrift(localPath) ? localPath : null,
115
118
  remotePath && existsDrift(remotePath) ? remotePath : null
@@ -146,7 +149,7 @@ FIREBASE AUDIT drift \u2014 MATCHED ${counts("MATCHED")}, LOCAL_ONLY ${counts("L
146
149
  console.log("\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500");
147
150
  if (findings.length === 0) {
148
151
  console.log(`
149
- \u2713 Nenhum problema nos checks implementados (${summary.passedChecks}/${summary.totalChecks} passaram).
152
+ \u2713 Nenhum problema nos checks FBA implementados (${summary.passedChecks}/${summary.totalChecks} passaram).
150
153
  Olhei: ${summary.checked.join(", ")}.
151
154
  Pulei: ${summary.skipped.join(" | ") || "nada"}.
152
155
  `);
@@ -161,7 +164,7 @@ ${icon} ${f.rule} [${f.confidence}]${where}
161
164
  if (f.fix) console.log(` \u2192 ${f.fix}`);
162
165
  }
163
166
  console.log(`
164
- SUMMARY: ${summary.errors} errors, ${summary.warnings} warnings, ${summary.infos} infos (${summary.passedChecks}/${summary.totalChecks} checks passaram)`);
167
+ SUMMARY: ${summary.errors} errors, ${summary.warnings} warnings, ${summary.infos} infos (${summary.passedChecks}/${summary.totalChecks} checks FBA passaram)`);
165
168
  console.log(`Olhei: ${summary.checked.join(", ")}`);
166
169
  console.log(`Pulei: ${summary.skipped.join(" | ") || "nada"}
167
170
  `);
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  drift
3
- } from "./chunk-NJBCSW7E.js";
3
+ } from "./chunk-XZ32YQQC.js";
4
4
  export {
5
5
  drift
6
6
  };
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-EHGW2EX6.js";
4
+ } from "./chunk-AXTYJMA7.js";
5
5
  import {
6
6
  AccessOriginSchema,
7
7
  AuditYamlSchema,
@@ -26,12 +26,17 @@ import {
26
26
  RuleSchema,
27
27
  SERVER_HINTS_SEGMENTS,
28
28
  SeveritySchema,
29
- buildFullPath,
30
29
  classifyOrigin,
31
- conditionKeyForFingerprint,
32
30
  discover,
33
31
  emptyModel,
34
32
  enrichWithGraph,
33
+ runChecks,
34
+ scan,
35
+ validateClaimSamples
36
+ } from "./chunk-UKEKA2NQ.js";
37
+ import {
38
+ buildFullPath,
39
+ conditionKeyForFingerprint,
35
40
  extractRules,
36
41
  extractRulesContent,
37
42
  findPublicAllows,
@@ -48,19 +53,16 @@ import {
48
53
  parseRuleFunctionsDetailed,
49
54
  resolveHelperCondition,
50
55
  resolveHelperConditionDetailed,
51
- runChecks,
52
- scan,
53
56
  stripOuterParens,
54
- stripRuleComments,
55
- validateClaimSamples
56
- } from "./chunk-TQZTE2NR.js";
57
+ stripRuleComments
58
+ } from "./chunk-7EWTBIKJ.js";
57
59
  import {
58
60
  planVerify,
59
61
  verify
60
- } from "./chunk-2X3BTSGP.js";
62
+ } from "./chunk-Y4VXWSKX.js";
61
63
  import {
62
64
  drift
63
- } from "./chunk-NJBCSW7E.js";
65
+ } from "./chunk-XZ32YQQC.js";
64
66
 
65
67
  // src/index.ts
66
68
  import { createRequire } from "module";
package/dist/mcp-cli.js CHANGED
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  startMcpServer
4
- } from "./chunk-EHGW2EX6.js";
5
- import "./chunk-TQZTE2NR.js";
6
- import "./chunk-2X3BTSGP.js";
7
- import "./chunk-NJBCSW7E.js";
4
+ } from "./chunk-AXTYJMA7.js";
5
+ import "./chunk-UKEKA2NQ.js";
6
+ import "./chunk-7EWTBIKJ.js";
7
+ import "./chunk-Y4VXWSKX.js";
8
+ import "./chunk-XZ32YQQC.js";
8
9
 
9
10
  // src/mcp-cli.ts
10
11
  startMcpServer().catch((err) => {
package/dist/mcp.js CHANGED
@@ -1,10 +1,11 @@
1
1
  import {
2
2
  createMcpServer,
3
3
  startMcpServer
4
- } from "./chunk-EHGW2EX6.js";
5
- import "./chunk-TQZTE2NR.js";
6
- import "./chunk-2X3BTSGP.js";
7
- import "./chunk-NJBCSW7E.js";
4
+ } from "./chunk-AXTYJMA7.js";
5
+ import "./chunk-UKEKA2NQ.js";
6
+ import "./chunk-7EWTBIKJ.js";
7
+ import "./chunk-Y4VXWSKX.js";
8
+ import "./chunk-XZ32YQQC.js";
8
9
  export {
9
10
  createMcpServer,
10
11
  startMcpServer
@@ -0,0 +1,44 @@
1
+ import {
2
+ buildFullPath,
3
+ conditionKeyForFingerprint,
4
+ extractRules,
5
+ extractRulesContent,
6
+ findPublicAllows,
7
+ findPublicAllowsContent,
8
+ hashShort,
9
+ helperBodyToCondition,
10
+ inlineHelperArgs,
11
+ inlineHelpersInCondition,
12
+ isPublicCondition,
13
+ normalizeTarget,
14
+ opsFrom,
15
+ parseMatchBlocks,
16
+ parseRuleFunctions,
17
+ parseRuleFunctionsDetailed,
18
+ resolveHelperCondition,
19
+ resolveHelperConditionDetailed,
20
+ stripOuterParens,
21
+ stripRuleComments
22
+ } from "./chunk-7EWTBIKJ.js";
23
+ export {
24
+ buildFullPath,
25
+ conditionKeyForFingerprint,
26
+ extractRules,
27
+ extractRulesContent,
28
+ findPublicAllows,
29
+ findPublicAllowsContent,
30
+ hashShort,
31
+ helperBodyToCondition,
32
+ inlineHelperArgs,
33
+ inlineHelpersInCondition,
34
+ isPublicCondition,
35
+ normalizeTarget,
36
+ opsFrom,
37
+ parseMatchBlocks,
38
+ parseRuleFunctions,
39
+ parseRuleFunctionsDetailed,
40
+ resolveHelperCondition,
41
+ resolveHelperConditionDetailed,
42
+ stripOuterParens,
43
+ stripRuleComments
44
+ };
@@ -0,0 +1,7 @@
1
+ import {
2
+ scan
3
+ } from "./chunk-UKEKA2NQ.js";
4
+ import "./chunk-7EWTBIKJ.js";
5
+ export {
6
+ scan
7
+ };
@@ -1,7 +1,7 @@
1
1
  import {
2
2
  planVerify,
3
3
  verify
4
- } from "./chunk-2X3BTSGP.js";
4
+ } from "./chunk-Y4VXWSKX.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.0",
3
+ "version": "0.5.2",
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
@@ -19,7 +19,8 @@ Skill para agentes usarem o `@justmpm/firebase-audit` do jeito certo.
19
19
  - Nunca dizer "unused" — apenas NOT_OBSERVED
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
- - Logo pública (`get: if true` em `/logos/`) pode ser vitrine proposital — cofre é `write: if true` ou `/{allPaths=**}` aberto
22
+ - Logo pública (`get: if true` em `/logos/`) pode ser vitrine proposital — cofre é `write: if true` ou curinga total aberto
23
+ - Seeds/fixtures com Admin SDK moram em `scripts/` ou `server/`, nunca em `src/` (senão o scan acusa FBA003 de propósito)
23
24
 
24
25
  ## Emulador
25
26
 
@@ -1,6 +0,0 @@
1
- import {
2
- scan
3
- } from "./chunk-TQZTE2NR.js";
4
- export {
5
- scan
6
- };