@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.
- package/dist/{chunk-CHC7ZHUK.js → chunk-BHRZOZYA.js} +3 -3
- package/dist/{chunk-DIO36A3I.js → chunk-CSMT4PIP.js} +13 -6
- package/dist/{chunk-XZ32YQQC.js → chunk-LD3F73XI.js} +13 -3
- package/dist/{chunk-ME6WO45S.js → chunk-LJW7QXIJ.js} +81 -24
- package/dist/cli.js +7 -7
- package/dist/{drift-QXALNO2V.js → drift-C3JZI5ZA.js} +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +4 -4
- package/dist/mcp-cli.js +4 -4
- package/dist/mcp.js +4 -4
- package/dist/{scan-HE5WAMWJ.js → scan-A34FDN66.js} +1 -1
- package/dist/{verify-DECP4IUW.js → verify-GQSR4FQL.js} +1 -1
- package/package.json +1 -1
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
import {
|
|
2
2
|
scan
|
|
3
|
-
} from "./chunk-
|
|
3
|
+
} from "./chunk-LJW7QXIJ.js";
|
|
4
4
|
import {
|
|
5
5
|
verify
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-CSMT4PIP.js";
|
|
7
7
|
import {
|
|
8
8
|
drift
|
|
9
|
-
} from "./chunk-
|
|
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:
|
|
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 (
|
|
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
|
-
|
|
579
|
-
|
|
580
|
-
seen.
|
|
581
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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) =>
|
|
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:
|
|
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-
|
|
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-
|
|
85
|
-
const { verify } = await import("./verify-
|
|
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-
|
|
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-
|
|
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
|
package/dist/index.d.ts
CHANGED
|
@@ -753,8 +753,9 @@ interface ScanOptions {
|
|
|
753
753
|
adapterFn?: string;
|
|
754
754
|
strict?: boolean;
|
|
755
755
|
/**
|
|
756
|
-
*
|
|
757
|
-
*
|
|
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-
|
|
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-
|
|
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-
|
|
62
|
+
} from "./chunk-CSMT4PIP.js";
|
|
63
63
|
import {
|
|
64
64
|
drift
|
|
65
|
-
} from "./chunk-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-BHRZOZYA.js";
|
|
5
|
+
import "./chunk-LJW7QXIJ.js";
|
|
6
6
|
import "./chunk-FECVBQUO.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
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-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-BHRZOZYA.js";
|
|
5
|
+
import "./chunk-LJW7QXIJ.js";
|
|
6
6
|
import "./chunk-FECVBQUO.js";
|
|
7
|
-
import "./chunk-
|
|
8
|
-
import "./chunk-
|
|
7
|
+
import "./chunk-CSMT4PIP.js";
|
|
8
|
+
import "./chunk-LD3F73XI.js";
|
|
9
9
|
export {
|
|
10
10
|
createMcpServer,
|
|
11
11
|
startMcpServer
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/firebase-audit",
|
|
3
|
-
"version": "0.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",
|