@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/chunk-PIRAC3KT.js +962 -0
- package/dist/cli.js +34 -6
- package/dist/index.d.ts +33 -6
- package/dist/index.js +9 -3
- package/package.json +5 -2
- package/skill/SKILL.md +29 -0
- package/templates/firebase-audit.yaml +39 -0
- package/dist/chunk-VKCDUCYE.js +0 -679
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-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
|
|
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
|
|
24
|
-
|
|
25
|
-
|
|
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(
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
/**
|
|
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–
|
|
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
|
-
|
|
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.
|
|
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
|