@justmpm/firebase-audit 0.5.14 → 0.5.16
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-4RVGY4EB.js → chunk-DXYPKVMR.js} +32 -7
- package/dist/{chunk-B5CIZS4Y.js → chunk-PVB6GT4F.js} +1 -1
- package/dist/cli.js +2 -2
- package/dist/index.d.ts +2 -1
- package/dist/index.js +2 -2
- package/dist/mcp-cli.js +2 -2
- package/dist/mcp.js +2 -2
- package/dist/{scan-DDGH7LPH.js → scan-6HV5HFKH.js} +1 -1
- package/package.json +1 -1
- package/skill/SKILL.md +1 -1
|
@@ -224,7 +224,7 @@ function make(rule, severity, confidence, message, fingerprint, opts = {}) {
|
|
|
224
224
|
};
|
|
225
225
|
}
|
|
226
226
|
function checksAdminPower(cond, condRaw, rule, helperDetailed) {
|
|
227
|
-
const isInvertedAdmin = /admin\s*==\s*false/i.test(cond) || /admin\s*!=\s*true/i.test(cond) || /admin\s*!==\s*true/i.test(cond) || /!\s*request\.auth\.token\.admin\b/i.test(cond) || /!\s*\(\s*request\.auth\.token\.admin\b/i.test(cond) || /!\s*\([^()]*admin\s*==\s*true/i.test(cond);
|
|
227
|
+
const isInvertedAdmin = /admin\s*==\s*false/i.test(cond) || /admin\s*!=\s*true/i.test(cond) || /admin\s*!==\s*true/i.test(cond) || /!\s*request\.auth\.token\.admin\b/i.test(cond) || /!\s*\(\s*request\.auth\.token\.admin\b/i.test(cond) || /!\s*\([^()]*admin\s*==\s*true/i.test(cond) || /!\s*\([^()]*==\s*['"]admin['"]/i.test(cond);
|
|
228
228
|
const normClaim = (c) => c.toLowerCase().replace(/[_-]/g, "");
|
|
229
229
|
const isAdminCall = /\bisadmin\s*\(([^)]*)\)/i.exec(condRaw) ?? /\bisadmin\s*\(([^)]*)\)/i.exec(cond);
|
|
230
230
|
let isAdminBodyHasAdmin = false;
|
|
@@ -248,13 +248,38 @@ function checksAdminPower(cond, condRaw, rule, helperDetailed) {
|
|
|
248
248
|
const p = part;
|
|
249
249
|
return /token\.admin\b/i.test(p) || /token\.get\(\s*['"]admin['"]/i.test(p) || /token\s*\[\s*['"]admin['"]/i.test(p) || /token\.\w+\s*==\s*['"]admin['"]/i.test(p) || /token\.\w+\s+in\s+[^\n;]*['"]admin['"]/i.test(p);
|
|
250
250
|
}) : true;
|
|
251
|
-
const checksAdminRole = !isInvertedAdmin && hasTopOr ? adminPerDisjunct : /token\.\w+\s*==\s*['"]admin['"]/i.test(cond) || /token\s*\[\s*['"]\w+['"]\s*\]\s*==\s*['"]admin['"]/i.test(cond) || /token\.\w+\s+in\s+[^\n;]*['"]admin['"]/i.test(cond) || /token\.get\(\s*['"]admin['"]/i.test(cond) || isAdminBodyHasAdmin;
|
|
251
|
+
const checksAdminRole = !isInvertedAdmin && (hasTopOr ? adminPerDisjunct : /token\.\w+\s*==\s*['"]admin['"]/i.test(cond) || /token\s*\[\s*['"]\w+['"]\s*\]\s*==\s*['"]admin['"]/i.test(cond) || /token\.\w+\s+in\s+[^\n;]*['"]admin['"]/i.test(cond) || /token\.get\(\s*['"]admin['"]/i.test(cond) || isAdminBodyHasAdmin);
|
|
252
252
|
if (checksAdminClaim || checksAdminRole) {
|
|
253
253
|
if (hasTopOr && !adminPerDisjunct) return false;
|
|
254
254
|
return true;
|
|
255
255
|
}
|
|
256
256
|
return false;
|
|
257
257
|
}
|
|
258
|
+
function branchHasCallerDocGate(branch) {
|
|
259
|
+
const args = [];
|
|
260
|
+
const re = /\b(?:exists|get)\s*\(/gi;
|
|
261
|
+
let m;
|
|
262
|
+
while ((m = re.exec(branch)) !== null) {
|
|
263
|
+
if (/!\s*(?:firestore\.)?$/.test(branch.slice(0, m.index))) continue;
|
|
264
|
+
let depth = 0;
|
|
265
|
+
const start = m.index + m[0].length;
|
|
266
|
+
let end = -1;
|
|
267
|
+
for (let i = start - 1; i < branch.length; i++) {
|
|
268
|
+
if (branch[i] === "(") depth += 1;
|
|
269
|
+
else if (branch[i] === ")") {
|
|
270
|
+
depth -= 1;
|
|
271
|
+
if (depth === 0) {
|
|
272
|
+
end = i;
|
|
273
|
+
break;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
if (end !== -1) args.push(branch.slice(start, end));
|
|
278
|
+
}
|
|
279
|
+
return args.some(
|
|
280
|
+
(arg) => /\/admins\//i.test(arg) && /request\.auth\.(uid|token)/i.test(arg)
|
|
281
|
+
);
|
|
282
|
+
}
|
|
258
283
|
async function runChecks(model, rootDir, opts = {}) {
|
|
259
284
|
const { ev } = createEvidence();
|
|
260
285
|
const findings = [];
|
|
@@ -541,7 +566,8 @@ async function runChecks(model, rootDir, opts = {}) {
|
|
|
541
566
|
}
|
|
542
567
|
for (const rule of model.rules) {
|
|
543
568
|
const segments = rule.path.split("/").filter((s) => s.length > 0).map((s) => s.toLowerCase());
|
|
544
|
-
|
|
569
|
+
const adminIdx = segments.indexOf("admins");
|
|
570
|
+
if (adminIdx === -1 || adminIdx + 1 >= segments.length) continue;
|
|
545
571
|
const ruleOps = new Set(rule.operations.flatMap(expandOp));
|
|
546
572
|
const escalating = ["create", "write", "update"].filter((o) => ruleOps.has(o));
|
|
547
573
|
if (escalating.length === 0) continue;
|
|
@@ -554,8 +580,7 @@ async function runChecks(model, rootDir, opts = {}) {
|
|
|
554
580
|
const condInlined = inlineHelpersInCondition(condRaw, helperDetailed);
|
|
555
581
|
const cond = condResolved !== condRaw ? condResolved : condInlined !== condRaw ? condInlined : condRaw;
|
|
556
582
|
if (checksAdminPower(cond, condRaw, rule, helperDetailed)) continue;
|
|
557
|
-
|
|
558
|
-
if (docGate) continue;
|
|
583
|
+
if (cond.split("||").every(branchHasCallerDocGate)) continue;
|
|
559
584
|
const checksAuth = rule.authReferences.length > 0;
|
|
560
585
|
if (!checksAuth) continue;
|
|
561
586
|
findings.push(
|
|
@@ -563,14 +588,14 @@ async function runChecks(model, rootDir, opts = {}) {
|
|
|
563
588
|
"FBA014",
|
|
564
589
|
"WARNING",
|
|
565
590
|
"PROBABLE",
|
|
566
|
-
`Rule ${rule.path} permite ${escalating.join("/")} sem exigir poder de admin (sem claim/papel de admin nem doc-gate em admins/). Qualquer logado cria/altera o pr\xF3prio doc \u2014 se o cadastro escrever role 'admin', \xE9 auto-promo\xE7\xE3o. Confirme o c\xF3digo de signup e valide no Emulator.`,
|
|
591
|
+
`Rule ${rule.path} permite ${escalating.join("/")} sem exigir poder de admin (sem claim/papel de admin nem doc-gate em /admins/ amarrado ao seu login). Qualquer logado cria/altera o pr\xF3prio doc \u2014 se o cadastro escrever role 'admin', \xE9 auto-promo\xE7\xE3o. Confirme o c\xF3digo de signup e valide no Emulator.`,
|
|
567
592
|
`FBA014:${rule.path}:${normalizeTarget(rule.operations.join(","))}:${conditionKeyForFingerprint(rule.condition)}`,
|
|
568
593
|
{
|
|
569
594
|
file: rule.location.file,
|
|
570
595
|
line: rule.location.start.line,
|
|
571
596
|
resource: rule.path,
|
|
572
597
|
evidence: [ev("self-admin-create", rule.condition ?? "sem condi\xE7\xE3o", "PROBABLE", rule.location.file, rule.location.start.line)],
|
|
573
|
-
fix: "Exigir claim/papel de admin ou doc-gate (exists/get em admins/) no create/update; nunca deixar o cadastro escrever role 'admin' para usu\xE1rio comum. Valide no Emulator (matriz anonymous/user/admin)."
|
|
598
|
+
fix: "Exigir claim/papel de admin ou doc-gate (exists/get em /admins/ amarrado ao request.auth.uid) no create/update; nunca deixar o cadastro escrever role 'admin' para usu\xE1rio comum. Valide no Emulator (matriz anonymous/user/admin)."
|
|
574
599
|
}
|
|
575
600
|
)
|
|
576
601
|
);
|
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-DXYPKVMR.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-
|
|
87
|
+
const { scan: scanForVerify } = await import("./scan-6HV5HFKH.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.d.ts
CHANGED
|
@@ -742,7 +742,8 @@ declare function findPublicAllowsContent(rawContent: string): {
|
|
|
742
742
|
* Checks (FBA001–FBA004, FBA009–FBA014, subconjunto intencional).
|
|
743
743
|
* Fingerprints: FBA001/FBA002 por regra-lógica + hash da condição
|
|
744
744
|
* (estáveis a formatação, únicos por regra duplicada);
|
|
745
|
-
* FBA003/FBA004/FBA010
|
|
745
|
+
* FBA003/FBA004/FBA010 com linha (únicos por ocorrência);
|
|
746
|
+
* FBA012/FBA014 por path + target + hash da condição (estáveis, sem linha).
|
|
746
747
|
*/
|
|
747
748
|
|
|
748
749
|
declare const IMPLEMENTED_CHECKS: readonly ["FBA001", "FBA002", "FBA003", "FBA004", "FBA009", "FBA010", "FBA011", "FBA012", "FBA013", "FBA014"];
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
createMcpServer,
|
|
3
3
|
startMcpServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-PVB6GT4F.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-
|
|
37
|
+
} from "./chunk-DXYPKVMR.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-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-PVB6GT4F.js";
|
|
5
|
+
import "./chunk-DXYPKVMR.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-
|
|
5
|
-
import "./chunk-
|
|
4
|
+
} from "./chunk-PVB6GT4F.js";
|
|
5
|
+
import "./chunk-DXYPKVMR.js";
|
|
6
6
|
import "./chunk-7J3ADEBW.js";
|
|
7
7
|
import "./chunk-CSMT4PIP.js";
|
|
8
8
|
import "./chunk-LD3F73XI.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@justmpm/firebase-audit",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.16",
|
|
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
|
@@ -23,7 +23,7 @@ Skill para agentes usarem o `@justmpm/firebase-audit` do jeito certo.
|
|
|
23
23
|
- Storage resolve funções auxiliares do `.rules` antes de acusar (portaria com auth dentro não é "sem auth", inclui `let` do rules v2); helper não declarado vira PROBABLE com o nome na mensagem, nunca silêncio
|
|
24
24
|
- Storage tolera CRLF (arquivo Windows) e path `/(unknown)` nunca repete fingerprint (sufixo de linha só no degenerado)
|
|
25
25
|
- Regra com `auth != null && exists(admins/uid)` diz "login + documento via exists()", não "qualquer autenticado" — sem claim/provider, anônimo com login passa no conjunto se obtiver o doc
|
|
26
|
-
- FBA014: `allow create/update` próprio em `admins/` sem claim/papel de admin nem doc-gate é auto-promoção provável (caso cadastro que escreve `role: 'admin'`) — confirme o signup e o Emulator; `users/{uid}` + campo role
|
|
26
|
+
- FBA014: `allow create/update` próprio em `admins/` sem claim/papel de admin nem doc-gate é auto-promoção provável (caso cadastro que escreve `role: 'admin'`) — doc-gate só vale amarrado ao uid em todo ramo de `||`; confirme o signup e o Emulator; `users/{uid}` + campo role e `admin` singular estão fora do escopo estático
|
|
27
27
|
- Storage avalia disjunção por ramo (`auth || tamanho` acusa o ramo sem auth) e `return false` via helper é deny silencioso, igual ao `if false`
|
|
28
28
|
- Seeds/fixtures com Admin SDK moram em `scripts/` ou `server/`, nunca em `src/` (senão o scan acusa FBA003 de propósito)
|
|
29
29
|
|