@justmpm/firebase-audit 0.1.0 → 0.2.0

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/cli.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
3
  scan
4
- } from "./chunk-VKCDUCYE.js";
4
+ } from "./chunk-PIRAC3KT.js";
5
5
 
6
6
  // src/cli.ts
7
7
  import { createRequire } from "module";
@@ -14,15 +14,41 @@ async function main() {
14
14
  console.log(pkg.version);
15
15
  return;
16
16
  }
17
- if (cmd !== "scan") {
17
+ if (cmd === "--help" || cmd === "-h" || cmd === "help") {
18
+ console.log(`firebase-audit v${pkg.version}
19
+
20
+ Uso:
21
+ firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]
22
+ firebase-audit check [--json] [--adapter=fn] [--cwd=path] (alias de scan --strict)
23
+
24
+ Exit codes: 0 ok (ou WARNING/INFO fora do strict); 2 quando --strict encontra ERROR.`);
25
+ return;
26
+ }
27
+ if (cmd !== "scan" && cmd !== "check") {
18
28
  console.error(`Comando desconhecido: ${cmd}
19
29
  Uso: firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]`);
20
30
  process.exit(1);
21
31
  }
32
+ const strict = args.includes("--strict") || cmd === "check";
22
33
  const asJson = args.includes("--json");
23
- const strict = args.includes("--strict");
24
- const adapterArg = args.find((a) => a.startsWith("--adapter="))?.split("=")[1];
25
- const cwdArg = args.find((a) => a.startsWith("--cwd="))?.split("=")[1];
34
+ const getArg = (name) => {
35
+ const pref = `--${name}=`;
36
+ const hit = args.find((a) => a.startsWith(pref));
37
+ if (hit) return hit.slice(pref.length);
38
+ const idx = args.findIndex((a) => a === `--${name}`);
39
+ if (idx !== -1 && args[idx + 1] && !args[idx + 1].startsWith("--")) return args[idx + 1];
40
+ return void 0;
41
+ };
42
+ const knownFlags = /* @__PURE__ */ new Set(["--json", "--strict", "--help", "-h", "help", "--version", "-v"]);
43
+ for (const a of args.slice(1)) {
44
+ if (a.startsWith("--") && !a.startsWith("--adapter=") && !a.startsWith("--cwd=") && a !== "--adapter" && a !== "--cwd" && !knownFlags.has(a)) {
45
+ console.error(`Flag desconhecida: ${a}
46
+ Uso: firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]`);
47
+ process.exit(1);
48
+ }
49
+ }
50
+ const adapterArg = getArg("adapter");
51
+ const cwdArg = getArg("cwd");
26
52
  const rootDir = cwdArg ?? process.cwd();
27
53
  const { findings, summary } = await scan(rootDir, { strict, adapterFn: adapterArg });
28
54
  if (asJson) {
@@ -34,7 +60,9 @@ Uso: firebase-audit scan [--json] [--strict] [--adapter=fn] [--cwd=path]`);
34
60
  FIREBASE AUDIT v${pkg.version}`);
35
61
  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");
36
62
  if (findings.length === 0) {
37
- console.log("\n\u2713 Nenhum problema encontrado (V1 static-first, 10 checks).\n");
63
+ console.log(`
64
+ \u2713 Nenhum problema nos checks V1+V2 implementados (parcial: FBA001-FBA004, FBA009-FBA013).
65
+ `);
38
66
  return;
39
67
  }
40
68
  for (const f of findings) {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import * as z from 'zod';
2
- import z__default from 'zod';
2
+ import { z as z$1 } from 'zod';
3
3
 
4
4
  /**
5
5
  * firebase-audit — schemas centrais (Zod v4).
@@ -19,6 +19,7 @@ declare const ConfidenceSchema: z.ZodEnum<{
19
19
  PROBABLE: "PROBABLE";
20
20
  NOT_OBSERVED: "NOT_OBSERVED";
21
21
  UNKNOWN: "UNKNOWN";
22
+ CONFLICTING: "CONFLICTING";
22
23
  }>;
23
24
  declare const OperationSchema: z.ZodEnum<{
24
25
  read: "read";
@@ -69,6 +70,7 @@ declare const EvidenceSchema: z.ZodObject<{
69
70
  PROBABLE: "PROBABLE";
70
71
  NOT_OBSERVED: "NOT_OBSERVED";
71
72
  UNKNOWN: "UNKNOWN";
73
+ CONFLICTING: "CONFLICTING";
72
74
  }>;
73
75
  value: z.ZodOptional<z.ZodUnknown>;
74
76
  fingerprint: z.ZodOptional<z.ZodString>;
@@ -170,6 +172,7 @@ declare const QueryShapeSchema: z.ZodObject<{
170
172
  PROBABLE: "PROBABLE";
171
173
  NOT_OBSERVED: "NOT_OBSERVED";
172
174
  UNKNOWN: "UNKNOWN";
175
+ CONFLICTING: "CONFLICTING";
173
176
  }>;
174
177
  location: z.ZodObject<{
175
178
  file: z.ZodString;
@@ -222,6 +225,7 @@ declare const RuleSchema: z.ZodObject<{
222
225
  PROBABLE: "PROBABLE";
223
226
  NOT_OBSERVED: "NOT_OBSERVED";
224
227
  UNKNOWN: "UNKNOWN";
228
+ CONFLICTING: "CONFLICTING";
225
229
  }>;
226
230
  location: z.ZodObject<{
227
231
  file: z.ZodString;
@@ -288,6 +292,7 @@ declare const GraphEdgeSchema: z.ZodObject<{
288
292
  PROBABLE: "PROBABLE";
289
293
  NOT_OBSERVED: "NOT_OBSERVED";
290
294
  UNKNOWN: "UNKNOWN";
295
+ CONFLICTING: "CONFLICTING";
291
296
  }>;
292
297
  evidenceIds: z.ZodArray<z.ZodString>;
293
298
  }, z.core.$strict>;
@@ -378,6 +383,7 @@ declare const ProjectModelSchema: z.ZodObject<{
378
383
  PROBABLE: "PROBABLE";
379
384
  NOT_OBSERVED: "NOT_OBSERVED";
380
385
  UNKNOWN: "UNKNOWN";
386
+ CONFLICTING: "CONFLICTING";
381
387
  }>;
382
388
  location: z.ZodObject<{
383
389
  file: z.ZodString;
@@ -413,6 +419,7 @@ declare const ProjectModelSchema: z.ZodObject<{
413
419
  PROBABLE: "PROBABLE";
414
420
  NOT_OBSERVED: "NOT_OBSERVED";
415
421
  UNKNOWN: "UNKNOWN";
422
+ CONFLICTING: "CONFLICTING";
416
423
  }>;
417
424
  location: z.ZodObject<{
418
425
  file: z.ZodString;
@@ -473,6 +480,7 @@ declare const ProjectModelSchema: z.ZodObject<{
473
480
  PROBABLE: "PROBABLE";
474
481
  NOT_OBSERVED: "NOT_OBSERVED";
475
482
  UNKNOWN: "UNKNOWN";
483
+ CONFLICTING: "CONFLICTING";
476
484
  }>;
477
485
  value: z.ZodOptional<z.ZodUnknown>;
478
486
  fingerprint: z.ZodOptional<z.ZodString>;
@@ -494,6 +502,7 @@ declare const ProjectModelSchema: z.ZodObject<{
494
502
  PROBABLE: "PROBABLE";
495
503
  NOT_OBSERVED: "NOT_OBSERVED";
496
504
  UNKNOWN: "UNKNOWN";
505
+ CONFLICTING: "CONFLICTING";
497
506
  }>;
498
507
  evidenceIds: z.ZodArray<z.ZodString>;
499
508
  }, z.core.$strict>>;
@@ -510,6 +519,7 @@ declare const FindingSchema: z.ZodObject<{
510
519
  PROBABLE: "PROBABLE";
511
520
  NOT_OBSERVED: "NOT_OBSERVED";
512
521
  UNKNOWN: "UNKNOWN";
522
+ CONFLICTING: "CONFLICTING";
513
523
  }>;
514
524
  file: z.ZodOptional<z.ZodString>;
515
525
  line: z.ZodOptional<z.ZodNumber>;
@@ -534,6 +544,7 @@ declare const FindingSchema: z.ZodObject<{
534
544
  PROBABLE: "PROBABLE";
535
545
  NOT_OBSERVED: "NOT_OBSERVED";
536
546
  UNKNOWN: "UNKNOWN";
547
+ CONFLICTING: "CONFLICTING";
537
548
  }>;
538
549
  value: z.ZodOptional<z.ZodUnknown>;
539
550
  fingerprint: z.ZodOptional<z.ZodString>;
@@ -597,12 +608,15 @@ interface DiscoveryResult {
597
608
  firestoreIndexesFile: string | null;
598
609
  auditYamlFile: string | null;
599
610
  projectId: string | null;
611
+ /** V2: databaseId nomeado (sempre null na V1). */
600
612
  databaseId: string | null;
613
+ /** V2: preenchido via varredura + ai-tool (sempre [] na V1). */
601
614
  clientFiles: string[];
615
+ /** V2: preenchido via varredura + ai-tool (sempre [] na V1). */
602
616
  serverFiles: string[];
603
617
  }
604
618
  declare function discover(rootDir: string): DiscoveryResult;
605
- /** Heurística simples CLIENT vs SERVER por caminho + import admin. */
619
+ /** Heurística CLIENT vs SERVER: caminho manda primeiro (grafo/voz do ai-tool na V2). */
606
620
  declare function classifyOrigin(file: string, content: string): "CLIENT" | "SERVER" | "UNKNOWN";
607
621
  declare function emptyModel(rootDir: string, d: DiscoveryResult): ProjectModel;
608
622
 
@@ -611,19 +625,32 @@ declare function emptyModel(rootDir: string, d: DiscoveryResult): ProjectModel;
611
625
  * Parser leve e intencionalmente incompleto: avaliação real é do Emulator (V3).
612
626
  */
613
627
 
614
- type Rule = z__default.infer<typeof RuleSchema>;
628
+ type Rule = z$1.infer<typeof RuleSchema>;
629
+ /** Remove comentários // e block preservando strings simples. */
630
+ declare function stripRuleComments(content: string): string;
615
631
  declare function extractRules(rulesFile: string, relFile: string): Rule[];
616
- /** Detecta allow ... if true preservando linha para FBA001/FBA002. */
632
+ /** Normaliza condição para detectar público: sem if, `true`, `(true)`, `|| true`, `auth == null`. */
633
+ declare function isPublicCondition(condition: string | undefined): {
634
+ isPublic: boolean;
635
+ confidence: "CONFIRMED" | "PROBABLE";
636
+ };
637
+ /** Detecta allows públicos preservando linha, path e confiança para FBA001/FBA002. */
617
638
  declare function findPublicAllows(rulesFile: string): {
618
639
  line: number;
619
640
  target: string;
641
+ path: string;
642
+ confidence: "CONFIRMED" | "PROBABLE";
620
643
  }[];
621
644
 
622
645
  /**
623
- * Checks estáticos V1 (FBA001–FBA010, subconjunto intencional).
646
+ * Checks estáticos V1 (FBA001–FBA004, FBA009–FBA011, subconjunto intencional).
624
647
  * Cada check retorna Findings com fingerprint estável para CI.
648
+ * Nota: FBA001/FBA002 sem linha no fingerprint (estável a formatação);
649
+ * FBA003/FBA004/FBA010 com linha (únicos por ocorrência — duplicatas no mesmo
650
+ * arquivo geram findings distintos).
625
651
  */
626
652
 
653
+ declare const IMPLEMENTED_CHECKS: readonly ["FBA001", "FBA002", "FBA003", "FBA004", "FBA009", "FBA010", "FBA011", "FBA012", "FBA013"];
627
654
  declare function runChecks(model: ProjectModel, rootDir: string, opts?: {
628
655
  adapterFn?: string;
629
656
  }): Promise<Finding[]>;
@@ -654,4 +681,4 @@ declare function scan(rootDir: string, opts?: ScanOptions): Promise<ScanResult>;
654
681
 
655
682
  declare const VERSION: string;
656
683
 
657
- export { AccessOriginSchema, type AuditYaml, AuditYamlSchema, ConfidenceSchema, type DiscoveryResult, DynamicValueSchema, type Evidence, EvidenceSchema, type Finding, FindingSchema, FirestoreIndexSchema, GraphEdgeSchema, IndexFieldSchema, LocationSchema, OperationSchema, PermissionSchema, PositionSchema, type ProjectModel, ProjectModelSchema, QueryFilterSchema, QueryOrderSchema, type QueryShape, QueryShapeSchema, RoleSchema, RuleSchema, SeveritySchema, VERSION, classifyOrigin, discover, emptyModel, extractRules, findPublicAllows, runChecks, scan };
684
+ export { AccessOriginSchema, type AuditYaml, AuditYamlSchema, ConfidenceSchema, type DiscoveryResult, DynamicValueSchema, type Evidence, EvidenceSchema, type Finding, FindingSchema, FirestoreIndexSchema, GraphEdgeSchema, IMPLEMENTED_CHECKS, IndexFieldSchema, LocationSchema, OperationSchema, PermissionSchema, PositionSchema, type ProjectModel, ProjectModelSchema, QueryFilterSchema, QueryOrderSchema, type QueryShape, QueryShapeSchema, RoleSchema, RuleSchema, type ScanOptions, type ScanResult, SeveritySchema, VERSION, classifyOrigin, discover, emptyModel, extractRules, findPublicAllows, isPublicCondition, runChecks, scan, stripRuleComments };
package/dist/index.js CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  FindingSchema,
8
8
  FirestoreIndexSchema,
9
9
  GraphEdgeSchema,
10
+ IMPLEMENTED_CHECKS,
10
11
  IndexFieldSchema,
11
12
  LocationSchema,
12
13
  OperationSchema,
@@ -24,9 +25,11 @@ import {
24
25
  emptyModel,
25
26
  extractRules,
26
27
  findPublicAllows,
28
+ isPublicCondition,
27
29
  runChecks,
28
- scan
29
- } from "./chunk-VKCDUCYE.js";
30
+ scan,
31
+ stripRuleComments
32
+ } from "./chunk-PIRAC3KT.js";
30
33
 
31
34
  // src/index.ts
32
35
  import { createRequire } from "module";
@@ -42,6 +45,7 @@ export {
42
45
  FindingSchema,
43
46
  FirestoreIndexSchema,
44
47
  GraphEdgeSchema,
48
+ IMPLEMENTED_CHECKS,
45
49
  IndexFieldSchema,
46
50
  LocationSchema,
47
51
  OperationSchema,
@@ -60,6 +64,8 @@ export {
60
64
  emptyModel,
61
65
  extractRules,
62
66
  findPublicAllows,
67
+ isPublicCondition,
63
68
  runChecks,
64
- scan
69
+ scan,
70
+ stripRuleComments
65
71
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@justmpm/firebase-audit",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
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",
@@ -26,7 +26,9 @@
26
26
  }
27
27
  },
28
28
  "files": [
29
- "dist"
29
+ "dist",
30
+ "templates",
31
+ "skill"
30
32
  ],
31
33
  "scripts": {
32
34
  "build": "tsup src/index.ts src/cli.ts --format esm --dts --clean",
@@ -39,6 +41,7 @@
39
41
  "@justmpm/ai-tool": "^6.1.1",
40
42
  "@justmpm/supergrep": "^0.7.0",
41
43
  "@modelcontextprotocol/sdk": "^1.25.3",
44
+ "yaml": "^2.9.1",
42
45
  "zod": "^4.1.0"
43
46
  },
44
47
  "devDependencies": {
package/skill/SKILL.md ADDED
@@ -0,0 +1,29 @@
1
+ # firebase-audit-skill
2
+
3
+ Skill para agentes usarem o `@justmpm/firebase-audit` do jeito certo.
4
+
5
+ ## Sequência obrigatória
6
+
7
+ 1. Descobrir projeto (`firebase.json`, `firestore.rules`, `firestore.indexes.json`)
8
+ 2. Verificar `firebase-audit.yaml` (se não existe, V1 roda zero-config)
9
+ 3. Inspecionar adapter de autorização (ex: `rbac.can`). Sem adapter resolvido → UNKNOWN, nunca adivinhar
10
+ 4. Executar `firebase-audit scan` (ou `--strict` com contrato)
11
+ 5. Ler findings (CONFIRMED corrige agora; PROBABLE investiga; NOT_OBSERVED/UNKNOWN não afirma)
12
+ 6. Corrigir código, nunca o contrato para esconder finding
13
+ 7. Reexecutar scan antes de declarar pronto
14
+
15
+ ## Travas
16
+
17
+ - `The contract is an assertion of intent. Do not modify the assertion to make implementation violations disappear.`
18
+ - Nunca transformar UNKNOWN em CONFIRMED sem teste no Emulator
19
+ - Nunca dizer "unused" — apenas NOT_OBSERVED
20
+ - Claims: 1000 bytes max, sem chaves OIDC reservadas, só controle de acesso
21
+
22
+ ## Emulador (V3)
23
+
24
+ ```bash
25
+ firebase emulators:exec --only firestore "npm test"
26
+ ```
27
+
28
+ Matriz `anon/user/admin x read/create/update/delete` com `@firebase/rules-unit-testing`.
29
+ Cobertura em `:ruleCoverage` (JSON). Emulator não prova índice.
@@ -0,0 +1,39 @@
1
+ authorization:
2
+ adapter:
3
+ function: "rbac.can"
4
+ roles:
5
+ admin:
6
+ permissions:
7
+ - users.read
8
+ - users.write
9
+ - orders.read
10
+ - orders.delete
11
+ user:
12
+ permissions:
13
+ - users.read
14
+ - orders.read
15
+ - orders.create
16
+ permissions:
17
+ users.read:
18
+ resource: users
19
+ operation: read
20
+ users.write:
21
+ resource: users
22
+ operation: write
23
+ orders.delete:
24
+ resource: orders
25
+ operation: delete
26
+ orders.read:
27
+ resource: orders
28
+ operation: read
29
+ orders.create:
30
+ resource: orders
31
+ operation: create
32
+ claims:
33
+ enabled: true
34
+ roleKey: role
35
+ samples:
36
+ admin:
37
+ role: admin
38
+ user:
39
+ role: user