@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.
- package/dist/chunk-7EWTBIKJ.js +356 -0
- package/dist/{chunk-EHGW2EX6.js → chunk-AXTYJMA7.js} +3 -3
- package/dist/{chunk-TQZTE2NR.js → chunk-UKEKA2NQ.js} +196 -411
- package/dist/{chunk-NJBCSW7E.js → chunk-XZ32YQQC.js} +10 -0
- package/dist/{chunk-2X3BTSGP.js → chunk-Y4VXWSKX.js} +4 -1
- package/dist/cli.js +13 -10
- package/dist/{drift-KU7HWL7M.js → drift-QXALNO2V.js} +1 -1
- package/dist/index.js +12 -10
- package/dist/mcp-cli.js +5 -4
- package/dist/mcp.js +5 -4
- package/dist/rules-GTGZWN5U.js +44 -0
- package/dist/scan-OEN65GHM.js +7 -0
- package/dist/{verify-GWRLO344.js → verify-WBVSESHD.js} +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +2 -1
- package/dist/scan-FYAWNRIE.js +0 -6
|
@@ -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-
|
|
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-
|
|
68
|
-
const { verify } = await import("./verify-
|
|
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-
|
|
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-
|
|
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
|
`);
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpServer,
|
|
3
3
|
startMcpServer
|
|
4
|
-
} from "./chunk-
|
|
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
|
-
|
|
56
|
-
} from "./chunk-TQZTE2NR.js";
|
|
57
|
+
stripRuleComments
|
|
58
|
+
} from "./chunk-7EWTBIKJ.js";
|
|
57
59
|
import {
|
|
58
60
|
planVerify,
|
|
59
61
|
verify
|
|
60
|
-
} from "./chunk-
|
|
62
|
+
} from "./chunk-Y4VXWSKX.js";
|
|
61
63
|
import {
|
|
62
64
|
drift
|
|
63
|
-
} from "./chunk-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
6
|
-
import "./chunk-
|
|
7
|
-
import "./chunk-
|
|
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
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/firebase-audit",
|
|
3
|
-
"version": "0.5.
|
|
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
|
|
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
|
|